Compare commits

...

100 Commits

Author SHA1 Message Date
Lucas Abel
ec35f5b118 pandora: add annotations import 2023-11-06 08:30:12 +00:00
Gilles Boccon-Gibod
c67ca4a09e Merge pull request #324 from google/gbg/hotfix-002
fix typo
2023-10-31 20:58:19 +01:00
Gilles Boccon-Gibod
94506220d3 fix typo 2023-10-31 12:18:28 -07:00
Gilles Boccon-Gibod
dbd865a484 Merge pull request #323 from google/gbg/device-hive
Device hive
2023-10-31 16:44:18 +01:00
Gilles Boccon-Gibod
9d2f3e932a format 2023-10-29 11:32:00 -07:00
Gilles Boccon-Gibod
49d32f5b5b add netsim.ini info 2023-10-29 10:26:34 -07:00
Gilles Boccon-Gibod
f7b74c0bcb add hive to index page 2023-10-29 10:03:31 -07:00
Gilles Boccon-Gibod
c75cb0c7b7 fix css 2023-10-29 09:58:37 -07:00
Gilles Boccon-Gibod
a63b335149 wip 2023-10-29 09:36:17 -07:00
Gilles Boccon-Gibod
d8517ce407 add links 2023-10-29 08:53:25 -07:00
Gilles Boccon-Gibod
ad13b11464 wip 2023-10-29 08:53:23 -07:00
Gilles Boccon-Gibod
99bc92d53d wip (+5 squashed commits)
Squashed commits:
[53c6c53] wip
[66f482c] wip
[b003315] wip
[f6f9d9e] wip
[4c95c7b] wip
2023-10-29 08:50:25 -07:00
Josh Wu
72199f5615 Add address resolution offload to config 2023-10-24 17:04:43 -07:00
skarnataki
78b8b50082 fixed lint errors 2023-10-19 17:19:49 -07:00
skarnataki
3ab64ce00d Fixed lint and pre-commit errors. 2023-10-19 17:19:49 -07:00
skarnataki
651e44e0b6 Submitting review comment fix: header function and extra lines.
Executed formatter on file.
2023-10-19 17:19:49 -07:00
skarnataki
963fa41a49 Submitting review comment fix: header function and extra lines. 2023-10-19 17:19:49 -07:00
skarnataki
493f4f8b95 Submitting review comment fix: header function and spacing 2023-10-19 17:19:49 -07:00
skarnataki
fc1bf36ace Review changes comment fix. Classes/Subclass/dataclass. Enum constants.
Naming conventions
2023-10-19 17:19:49 -07:00
skarnataki
5ddee17411 Commit to fix review comments for dataclass and subclass, shifting contants to Message Class
Commit for enum and dataclass
2023-10-19 17:19:49 -07:00
skarnataki
5ce353bcde Review comment Fix 2023-10-19 17:19:49 -07:00
SneKarnataki
16d33199eb Change in sdp.py file while testing hid profile,
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x.decode('utf8')) changed to
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x)
as we were facing error "UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa1 in position 4: invalid start byte" while fetching sdp records.
2023-10-19 17:19:49 -07:00
SneKarnataki
e02303a448 Submitting the initial version of HID Profile files
Includes:
1. HID Host implementation - hid.py
2. HID application to test Host with 3rd party HID Device application - run_hid_host.py
3. HID supporting files for testing - hid_report_parser.py & hid_key_map.py

Commands to run the application:
Default application:
python run_hid_host.py classic1.json usb:0 <device bd-addr>

Menu options for testing (Get/Set):
python run_hid_host.py classic1.json usb:0 <device bd-addr> test-mode

CuttleFish:tcp-client:127.0.0.1:7300

Application used for testing as Device : Bluetooth Keyboard & Mouse-5.3.0.apk

Note: Change in sdp.py file while testing hid profile,
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x.decode('utf8')) changed to
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x)
as we were facing error "UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa1 in position 4: invalid start byte" while fetching sdp records.
2023-10-19 17:19:49 -07:00
Fahad Afroze
36fc966ad6 Trial checkin code 2023-10-19 17:19:49 -07:00
skarnataki
644f74400d Trial to commit in dhavan repo 2023-10-19 17:19:49 -07:00
dhavan
b7cd451ddb Hid profile implemenation. Empty file 2023-10-19 17:19:49 -07:00
Gilles Boccon-Gibod
88392efca4 Merge pull request #312 from google/gbg/android-remote-hci
remote hci android app
2023-10-17 13:30:49 +02:00
zxzxwu
907f2acc7e Merge pull request #318 from zxzxwu/l2cap_refactor
Cleanup legacy L2CAP API usage
2023-10-17 14:22:45 +08:00
Gilles Boccon-Gibod
6616477bcf Merge pull request #319 from google/gbg/bt-spec-version-5-4
add constant for 5.4
2023-10-11 18:44:03 -07:00
Gilles Boccon-Gibod
5b173cb879 add constant for 5.4 2023-10-11 17:47:21 -07:00
Gilles Boccon-Gibod
dc6b466a42 add intent parameters 2023-10-11 16:52:15 -07:00
zxzxwu
8b04161da3 Merge pull request #317 from zxzxwu/pytest
Add missing @pytest.mark.asyncio decorator
2023-10-11 15:16:35 +08:00
Josh Wu
5a85765360 Cleanup legacy L2CAP API 2023-10-11 14:33:44 +08:00
Josh Wu
333940919b Add missing @pytest.mark.asyncio decorator 2023-10-11 13:52:06 +08:00
Gilles Boccon-Gibod
b9476be9ad Merge pull request #315 from google/gbg/company-ids
update to latest list of company ids
2023-10-10 22:13:16 -07:00
Gilles Boccon-Gibod
704c60491c Merge pull request #313 from benquike/pair_fix
Allow turning on BLE in classic pairing mode
2023-10-10 21:30:24 -07:00
Gilles Boccon-Gibod
4a8e612c6e update rust list 2023-10-10 21:29:39 -07:00
Gilles Boccon-Gibod
5e5c9c2580 fix byte order and packet accounting 2023-10-10 21:17:20 -07:00
Gilles Boccon-Gibod
4e71ec5738 remove stale comment 2023-10-10 20:36:48 -07:00
uael
7255a09705 ci: add python avatar tests 2023-10-09 23:37:23 +02:00
zxzxwu
c2bf6b5f13 Merge pull request #289 from zxzxwu/l2cap_refactor
Refactor L2CAP API
2023-10-09 23:27:25 +08:00
Gilles Boccon-Gibod
d8e699b588 use the new yaml file instead of the previous CSV file 2023-10-07 23:10:49 -07:00
zxzxwu
3e4d4705f5 Merge pull request #314 from zxzxwu/sec_pandora
Pandora: Handle exception in WaitSecurity()
2023-10-08 01:42:45 +08:00
Josh Wu
c8b2804446 Pandora: Handle exception in WaitSecurity() 2023-10-07 21:17:01 +08:00
Josh Wu
e732f2589f Refactor L2CAP API 2023-10-07 20:01:15 +08:00
zxzxwu
aec5543081 Merge pull request #310 from zxzxwu/avdtp
Typing AVDTP
2023-10-07 19:50:56 +08:00
Josh Wu
e03d90ca57 Add typing for MediaCodecCapabilities members 2023-10-07 19:32:19 +08:00
Josh Wu
495ce62d9c Typing AVDTP 2023-10-07 19:32:19 +08:00
Hui Peng
fbc3959a5a Allow turning on BLE in classic pairing mode 2023-10-06 19:54:18 -07:00
Gilles Boccon-Gibod
246b11925c add remote hci android app 2023-10-06 14:10:51 -07:00
Gilles Boccon-Gibod
dfa9131192 Merge pull request #311 from zxzxwu/rust
Fix Rust lints
2023-10-06 13:37:47 -07:00
Josh Wu
88c801b4c2 Replace or_insert_with with or_default 2023-10-06 18:02:46 +08:00
Gilles Boccon-Gibod
a1b55b94e0 Merge pull request #301 from whitevegagabriel/simplify-event-loop-copy
Remove unncecesary steps for injecting Python event loop
2023-10-02 12:12:41 -07:00
Gilles Boccon-Gibod
80db9e2e2f Merge pull request #303 from whitevegagabriel/hci-command-rs
Ability to send HCI commands from Rust
2023-10-02 12:12:05 -07:00
Gabriel White-Vega
ce74690420 Update pdl to 0.2.0
- Allows removing impl PartialEq for pdl Error
2023-10-02 11:20:44 -04:00
Gilles Boccon-Gibod
50de4dfb5d Merge pull request #307 from google/gbg/hotfix-001
don't delete advertising prefs on disconnection
2023-09-30 17:46:53 -07:00
Gilles Boccon-Gibod
9bcdf860f4 don't delete advertising prefs on disconnection 2023-09-30 17:41:18 -07:00
Gabriel White-Vega
511ab4b630 Add python async wrapper, move hci non-wrapper to internal, add hci::internal tests 2023-09-29 10:23:19 -04:00
Gilles Boccon-Gibod
6f2b623e3c Merge pull request #290 from google/gbg/netsim-transport-injectable-channels
make grpc channels injectable
2023-09-27 22:16:05 -07:00
Gilles Boccon-Gibod
fa12165cd3 Merge pull request #298 from google/gbg/use-address-to-string
use Address.to_string instead of manual suffix replacement
2023-09-27 21:59:32 -07:00
Gilles Boccon-Gibod
c0c6f3329d minor cleanup 2023-09-27 21:53:54 -07:00
Gilles Boccon-Gibod
406a932467 make grpc channels injectable 2023-09-27 21:37:36 -07:00
Gilles Boccon-Gibod
cc96d4245f address PR comments 2023-09-27 21:25:13 -07:00
Sparkling Diva
c6cdca8923 device: return the psm value from register_l2cap 2023-09-27 16:41:38 -07:00
Gabriel White-Vega
7e331c2944 Ability to send HCI commands from Rust
* Autogenerate packet code in Rust from PDL (packet file copied from rootcanal)
* Implement parsing of packets that have a type header
* Expose Python APIs for sending HCI commands
* Expose Python APIs for instantiating a local controller
2023-09-27 11:17:47 -04:00
Gilles Boccon-Gibod
10347765cb Merge pull request #302 from google/gbg/netsim-with-instance-num
support netsim instance numbers
2023-09-26 09:34:28 -07:00
Gilles Boccon-Gibod
c12dee4e76 Merge pull request #294 from mauricelam/wasm-cryptography
Make cryptography a valid dependency for emscripten targets
2023-09-25 19:29:09 -07:00
Maurice Lam
772c188674 Fix typo 2023-09-25 18:08:52 -07:00
Maurice Lam
7c1a3bb8f9 Separate version specifier for cryptography in Emscripten builds 2023-09-22 16:43:40 -07:00
Maurice Lam
8c3c0b1e13 Make cryptography a valid dependency for emscripten targets
Since only the special cryptography package bundled with pyodide can be
used, relax the version requirement to anything that's version 39.*.

Fix #284
2023-09-22 16:43:40 -07:00
Gilles Boccon-Gibod
1ad84ad51c fix linter errors 2023-09-22 15:08:10 -07:00
Gilles Boccon-Gibod
64937c3f77 support netsim instance numbers 2023-09-22 14:22:04 -07:00
Gabriel White-Vega
50fd2218fa Remove unncecesary steps for injecting Python event loop
* Context vars can be injected directly into Rust future and spawned with tokio
2023-09-22 15:23:01 -04:00
Gilles Boccon-Gibod
4c29a16271 Merge pull request #297 from google/gbg/websocket-full-url
ws-client: make implementation match the doc
2023-09-22 11:41:24 -07:00
Gilles Boccon-Gibod
762d3e92de Merge pull request #300 from google/gbg/issue-299
use correct own_address_type when restarting advertising
2023-09-22 11:41:04 -07:00
uael
2f97531d78 pandora: use public identity address for public addresses 2023-09-22 20:08:34 +02:00
Gilles Boccon-Gibod
f6c7cae661 use correct own_address_type when restarting advertising 2023-09-22 10:33:36 -07:00
Gilles Boccon-Gibod
f1777a5bd2 use .to_string instead of a manual suffix replacement 2023-09-21 19:03:54 -07:00
Gilles Boccon-Gibod
78a06ae8cf make implementation match the doc 2023-09-21 19:01:40 -07:00
zxzxwu
d290df4aa9 Merge pull request #278 from zxzxwu/gatt2
Typing GATT
2023-09-21 16:09:36 +08:00
Josh Wu
e559744f32 Typing att 2023-09-21 15:52:07 +08:00
zxzxwu
67418e649a Merge pull request #288 from zxzxwu/l2cap_states
L2CAP: Refactor states to enums
2023-09-21 15:42:21 +08:00
Gilles Boccon-Gibod
5adf9fab53 Merge pull request #275 from whitevegagabriel/file-header
Add license header check for rust files
2023-09-20 16:21:38 -07:00
Josh Wu
2491b686fa Handle SMP_Security_Request 2023-09-20 23:13:08 +02:00
Josh Wu
efd02b2f3e Adopt reviews 2023-09-20 23:03:23 +02:00
Josh Wu
3b14078646 Overload signatures 2023-09-20 23:03:23 +02:00
Josh Wu
eb9d5632bc Add utils_test type hint 2023-09-20 23:03:23 +02:00
Josh Wu
45f60edbb6 Pyee watcher context 2023-09-20 23:03:23 +02:00
David Duarte
393ea6a7bb pandora_server: Load server config
Pandora server has it's own config that we load from the 'server'
property of the current bumble config file
2023-09-18 14:28:42 -07:00
Gabriel White-Vega
6ec6f1efe5 Add license header check for rust files
Added binary that can check for and add Apache 2.0 licenses.
Run this binary during the build-rust workflow.
2023-09-14 14:29:47 -04:00
Josh Wu
5d9598ea51 L2CAP: Refactor states to enums 2023-09-14 20:52:33 +08:00
Gilles Boccon-Gibod
0d36d99a73 Merge pull request #287 from google/revert-286-gbg/package-depencencies-for-wasm
Revert "make cryptography a valid dependency for emscripten targets"
2023-09-13 23:37:42 -07:00
Gilles Boccon-Gibod
d8a9f5a724 Revert "make cryptography a valid dependency for emscripten targets" 2023-09-13 23:36:33 -07:00
Gilles Boccon-Gibod
2c66e1a042 Merge pull request #285 from google/gbg/fix-mypy-errors
mypy: ignore false positive errors
2023-09-13 23:30:50 -07:00
Gilles Boccon-Gibod
d5eccdb00f Merge pull request #286 from google/gbg/package-depencencies-for-wasm
make cryptography a valid dependency for emscripten targets
2023-09-13 23:30:28 -07:00
Gilles Boccon-Gibod
32626573a6 ignore false positive errors 2023-09-13 23:17:00 -07:00
Gilles Boccon-Gibod
caa82b8f7e make cryptography a valid dependency for emscripten targets 2023-09-13 22:38:28 -07:00
Gilles Boccon-Gibod
5af347b499 Merge pull request #282 from google/gbg/multi-python-pre-commit-check
run pre-commit tests with all supported Python versions
2023-09-13 07:47:32 -07:00
zxzxwu
4ed5bb5a9e Merge pull request #281 from zxzxwu/cleanup-transport
Replace | typing usage with Optional and Union
2023-09-13 13:31:41 +08:00
Josh Wu
f39f5f531c Replace | typing usage with Optional and Union 2023-09-12 15:50:51 +08:00
195 changed files with 18750 additions and 1587 deletions

43
.github/workflows/python-avatar.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Python Avatar
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
test:
name: Avatar [${{ matrix.shard }}]
runs-on: ubuntu-latest
strategy:
matrix:
shard: [
1/24, 2/24, 3/24, 4/24,
5/24, 6/24, 7/24, 8/24,
9/24, 10/24, 11/24, 12/24,
13/24, 14/24, 15/24, 16/24,
17/24, 18/24, 19/24, 20/24,
21/24, 22/24, 23/24, 24/24,
]
steps:
- uses: actions/checkout@v3
- name: Set Up Python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install
run: |
python -m pip install --upgrade pip
python -m pip install .[avatar]
- name: Rootcanal
run: nohup python -m rootcanal > rootcanal.log &
- name: Test
run: |
avatar --list | grep -Ev '^=' > test-names.txt
timeout 5m avatar --test-beds bumble.bumbles --tests $(split test-names.txt -n l/${{ matrix.shard }})
- name: Rootcanal Logs
run: cat rootcanal.log

View File

@@ -65,6 +65,8 @@ jobs:
with:
components: clippy,rustfmt
toolchain: ${{ matrix.rust-version }}
- name: Check License Headers
run: cd rust && cargo run --features dev-tools --bin file-header check-all
- name: Rust Build
run: cd rust && cargo build --all-targets && cargo build --all-features --all-targets
# Lints after build so what clippy needs is already built

View File

@@ -39,12 +39,15 @@
"libusb",
"MITM",
"NDIS",
"netsim",
"NONBLOCK",
"NONCONN",
"OXIMETER",
"popleft",
"protobuf",
"psms",
"pyee",
"Pyodide",
"pyusb",
"rfcomm",
"ROHC",

View File

@@ -24,6 +24,7 @@ import time
import click
from bumble import l2cap
from bumble.core import (
BT_BR_EDR_TRANSPORT,
BT_LE_TRANSPORT,
@@ -85,6 +86,7 @@ DEFAULT_LINGER_TIME = 1.0
DEFAULT_RFCOMM_CHANNEL = 8
# -----------------------------------------------------------------------------
# Utils
# -----------------------------------------------------------------------------
@@ -197,6 +199,7 @@ class PacketType(enum.IntEnum):
PACKET_FLAG_LAST = 1
# -----------------------------------------------------------------------------
# Sender
# -----------------------------------------------------------------------------
@@ -659,17 +662,19 @@ class L2capClient(StreamedPacketIO):
self.mps = mps
self.ready = asyncio.Event()
async def on_connection(self, connection):
async def on_connection(self, connection: Connection) -> None:
connection.on('disconnection', self.on_disconnection)
# Connect a new L2CAP channel
print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
try:
l2cap_channel = await connection.open_l2cap_channel(
psm=self.psm,
max_credits=self.max_credits,
mtu=self.mtu,
mps=self.mps,
l2cap_channel = await connection.create_l2cap_channel(
spec=l2cap.LeCreditBasedChannelSpec(
psm=self.psm,
max_credits=self.max_credits,
mtu=self.mtu,
mps=self.mps,
)
)
print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
except Exception as error:
@@ -695,7 +700,7 @@ class L2capClient(StreamedPacketIO):
class L2capServer(StreamedPacketIO):
def __init__(
self,
device,
device: Device,
psm=DEFAULT_L2CAP_PSM,
max_credits=DEFAULT_L2CAP_MAX_CREDITS,
mtu=DEFAULT_L2CAP_MTU,
@@ -706,12 +711,11 @@ class L2capServer(StreamedPacketIO):
self.ready = asyncio.Event()
# Listen for incoming L2CAP CoC connections
device.register_l2cap_channel_server(
psm=psm,
server=self.on_l2cap_channel,
max_credits=max_credits,
mtu=mtu,
mps=mps,
device.create_l2cap_server(
spec=l2cap.LeCreditBasedChannelSpec(
psm=psm, mtu=mtu, mps=mps, max_credits=max_credits
),
handler=self.on_l2cap_channel,
)
print(color(f'### Listening for CoC connection on PSM {psm}', 'yellow'))

View File

@@ -1172,7 +1172,7 @@ class ScanResult:
name = ''
# Remove any '/P' qualifier suffix from the address string
address_str = str(self.address).replace('/P', '')
address_str = self.address.to_string(with_type_qualifier=False)
# RSSI bar
bar_string = rssi_bar(self.rssi)

View File

@@ -63,7 +63,8 @@ async def get_classic_info(host):
if command_succeeded(response):
print()
print(
color('Classic Address:', 'yellow'), response.return_parameters.bd_addr
color('Classic Address:', 'yellow'),
response.return_parameters.bd_addr.to_string(False),
)
if host.supports_command(HCI_READ_LOCAL_NAME_COMMAND):

View File

@@ -21,6 +21,7 @@ import struct
import logging
import click
from bumble import l2cap
from bumble.colors import color
from bumble.device import Device, Peer
from bumble.core import AdvertisingData
@@ -204,7 +205,7 @@ class GattlinkHubBridge(GattlinkL2capEndpoint, Device.Listener):
# -----------------------------------------------------------------------------
class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
def __init__(self, device):
def __init__(self, device: Device):
super().__init__()
self.device = device
self.peer = None
@@ -218,7 +219,12 @@ class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
# Listen for incoming L2CAP CoC connections
psm = 0xFB
device.register_l2cap_channel_server(0xFB, self.on_coc)
device.create_l2cap_server(
spec=l2cap.LeCreditBasedChannelSpec(
psm=0xFB,
),
handler=self.on_coc,
)
print(f'### Listening for CoC connection on PSM {psm}')
# Setup the Gattlink service

View File

@@ -20,6 +20,7 @@ import logging
import os
import click
from bumble import l2cap
from bumble.colors import color
from bumble.transport import open_transport_or_link
from bumble.device import Device
@@ -47,14 +48,13 @@ class ServerBridge:
self.tcp_host = tcp_host
self.tcp_port = tcp_port
async def start(self, device):
async def start(self, device: Device) -> None:
# Listen for incoming L2CAP CoC connections
device.register_l2cap_channel_server(
psm=self.psm,
server=self.on_coc,
max_credits=self.max_credits,
mtu=self.mtu,
mps=self.mps,
device.create_l2cap_server(
spec=l2cap.LeCreditBasedChannelSpec(
psm=self.psm, mtu=self.mtu, mps=self.mps, max_credits=self.max_credits
),
handler=self.on_coc,
)
print(color(f'### Listening for CoC connection on PSM {self.psm}', 'yellow'))
@@ -195,11 +195,13 @@ class ClientBridge:
# Connect a new L2CAP channel
print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
try:
l2cap_channel = await connection.open_l2cap_channel(
psm=self.psm,
max_credits=self.max_credits,
mtu=self.mtu,
mps=self.mps,
l2cap_channel = await connection.create_l2cap_channel(
spec=l2cap.LeCreditBasedChannelSpec(
psm=self.psm,
max_credits=self.max_credits,
mtu=self.mtu,
mps=self.mps,
)
)
print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
except Exception as error:

View File

@@ -306,6 +306,7 @@ async def pair(
# Expose a GATT characteristic that can be used to trigger pairing by
# responding with an authentication error when read
if mode == 'le':
device.le_enabled = True
device.add_service(
Service(
'50DB505C-8AC4-4738-8448-3B1D9CC09CC5',
@@ -326,7 +327,6 @@ async def pair(
# Select LE or Classic
if mode == 'classic':
device.classic_enabled = True
device.le_enabled = False
device.classic_smp_enabled = ctkd
# Get things going

View File

@@ -3,7 +3,7 @@ import click
import logging
import json
from bumble.pandora import PandoraDevice, serve
from bumble.pandora import PandoraDevice, Config, serve
from typing import Dict, Any
BUMBLE_SERVER_GRPC_PORT = 7999
@@ -29,12 +29,14 @@ def main(grpc_port: int, rootcanal_port: int, transport: str, config: str) -> No
transport = transport.replace('<rootcanal-port>', str(rootcanal_port))
bumble_config = retrieve_config(config)
if 'transport' not in bumble_config.keys():
bumble_config.update({'transport': transport})
bumble_config.setdefault('transport', transport)
device = PandoraDevice(bumble_config)
server_config = Config()
server_config.load_from_dict(bumble_config.get('server', {}))
logging.basicConfig(level=logging.DEBUG)
asyncio.run(serve(device, port=grpc_port))
asyncio.run(serve(device, config=server_config, port=grpc_port))
def retrieve_config(config: str) -> Dict[str, Any]:

View File

@@ -195,7 +195,7 @@ class WebSocketOutput(QueuedOutput):
except HCI_StatusError:
pass
peer_name = '' if connection.peer_name is None else connection.peer_name
peer_address = str(connection.peer_address).replace('/P', '')
peer_address = connection.peer_address.to_string(False)
await self.send_message(
'connection',
peer_address=peer_address,
@@ -376,7 +376,7 @@ class UiServer:
if connection := self.speaker().connection:
await self.send_message(
'connection',
peer_address=str(connection.peer_address).replace('/P', ''),
peer_address=connection.peer_address.to_string(False),
peer_name=connection.peer_name,
)
@@ -641,7 +641,7 @@ class Speaker:
self.device.on('connection', self.on_bluetooth_connection)
# Create a listener to wait for AVDTP connections
self.listener = Listener(Listener.create_registrar(self.device))
self.listener = Listener.for_device(self.device)
self.listener.on('connection', self.on_avdtp_connection)
print(f'Speaker ready to play, codec={color(self.codec, "cyan")}')

View File

@@ -23,13 +23,14 @@
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations
import enum
import functools
import struct
from pyee import EventEmitter
from typing import Dict, Type, TYPE_CHECKING
from typing import Dict, Type, List, Protocol, Union, Optional, Any, TYPE_CHECKING
from bumble.core import UUID, name_or_number, get_dict_key_by_value, ProtocolError
from bumble.hci import HCI_Object, key_with_value, HCI_Constant
from bumble.core import UUID, name_or_number, ProtocolError
from bumble.hci import HCI_Object, key_with_value
from bumble.colors import color
if TYPE_CHECKING:
@@ -182,6 +183,7 @@ UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
# pylint: enable=line-too-long
# pylint: disable=invalid-name
# -----------------------------------------------------------------------------
# Exceptions
# -----------------------------------------------------------------------------
@@ -209,7 +211,7 @@ class ATT_PDU:
pdu_classes: Dict[int, Type[ATT_PDU]] = {}
op_code = 0
name = None
name: str
@staticmethod
def from_bytes(pdu):
@@ -719,48 +721,68 @@ class ATT_Handle_Value_Confirmation(ATT_PDU):
'''
# -----------------------------------------------------------------------------
class ConnectionValue(Protocol):
def read(self, connection) -> bytes:
...
def write(self, connection, value: bytes) -> None:
...
# -----------------------------------------------------------------------------
class Attribute(EventEmitter):
# Permission flags
READABLE = 0x01
WRITEABLE = 0x02
READ_REQUIRES_ENCRYPTION = 0x04
WRITE_REQUIRES_ENCRYPTION = 0x08
READ_REQUIRES_AUTHENTICATION = 0x10
WRITE_REQUIRES_AUTHENTICATION = 0x20
READ_REQUIRES_AUTHORIZATION = 0x40
WRITE_REQUIRES_AUTHORIZATION = 0x80
class Permissions(enum.IntFlag):
READABLE = 0x01
WRITEABLE = 0x02
READ_REQUIRES_ENCRYPTION = 0x04
WRITE_REQUIRES_ENCRYPTION = 0x08
READ_REQUIRES_AUTHENTICATION = 0x10
WRITE_REQUIRES_AUTHENTICATION = 0x20
READ_REQUIRES_AUTHORIZATION = 0x40
WRITE_REQUIRES_AUTHORIZATION = 0x80
PERMISSION_NAMES = {
READABLE: 'READABLE',
WRITEABLE: 'WRITEABLE',
READ_REQUIRES_ENCRYPTION: 'READ_REQUIRES_ENCRYPTION',
WRITE_REQUIRES_ENCRYPTION: 'WRITE_REQUIRES_ENCRYPTION',
READ_REQUIRES_AUTHENTICATION: 'READ_REQUIRES_AUTHENTICATION',
WRITE_REQUIRES_AUTHENTICATION: 'WRITE_REQUIRES_AUTHENTICATION',
READ_REQUIRES_AUTHORIZATION: 'READ_REQUIRES_AUTHORIZATION',
WRITE_REQUIRES_AUTHORIZATION: 'WRITE_REQUIRES_AUTHORIZATION',
}
@classmethod
def from_string(cls, permissions_str: str) -> Attribute.Permissions:
try:
return functools.reduce(
lambda x, y: x | Attribute.Permissions[y],
permissions_str.replace('|', ',').split(","),
Attribute.Permissions(0),
)
except TypeError as exc:
# The check for `p.name is not None` here is needed because for InFlag
# enums, the .name property can be None, when the enum value is 0,
# so the type hint for .name is Optional[str].
enum_list: List[str] = [p.name for p in cls if p.name is not None]
enum_list_str = ",".join(enum_list)
raise TypeError(
f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {enum_list_str }\nGot: {permissions_str}"
) from exc
@staticmethod
def string_to_permissions(permissions_str: str):
try:
return functools.reduce(
lambda x, y: x | get_dict_key_by_value(Attribute.PERMISSION_NAMES, y),
permissions_str.split(","),
0,
)
except TypeError as exc:
raise TypeError(
f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {','.join(Attribute.PERMISSION_NAMES.values())}\nGot: {permissions_str}"
) from exc
# Permission flags(legacy-use only)
READABLE = Permissions.READABLE
WRITEABLE = Permissions.WRITEABLE
READ_REQUIRES_ENCRYPTION = Permissions.READ_REQUIRES_ENCRYPTION
WRITE_REQUIRES_ENCRYPTION = Permissions.WRITE_REQUIRES_ENCRYPTION
READ_REQUIRES_AUTHENTICATION = Permissions.READ_REQUIRES_AUTHENTICATION
WRITE_REQUIRES_AUTHENTICATION = Permissions.WRITE_REQUIRES_AUTHENTICATION
READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
def __init__(self, attribute_type, permissions, value=b''):
value: Union[str, bytes, ConnectionValue]
def __init__(
self,
attribute_type: Union[str, bytes, UUID],
permissions: Union[str, Attribute.Permissions],
value: Union[str, bytes, ConnectionValue] = b'',
) -> None:
EventEmitter.__init__(self)
self.handle = 0
self.end_group_handle = 0
if isinstance(permissions, str):
self.permissions = self.string_to_permissions(permissions)
self.permissions = Attribute.Permissions.from_string(permissions)
else:
self.permissions = permissions
@@ -778,22 +800,26 @@ class Attribute(EventEmitter):
else:
self.value = value
def encode_value(self, value):
def encode_value(self, value: Any) -> bytes:
return value
def decode_value(self, value_bytes):
def decode_value(self, value_bytes: bytes) -> Any:
return value_bytes
def read_value(self, connection: Connection):
def read_value(self, connection: Optional[Connection]) -> bytes:
if (
self.permissions & self.READ_REQUIRES_ENCRYPTION
) and not connection.encryption:
(self.permissions & self.READ_REQUIRES_ENCRYPTION)
and connection is not None
and not connection.encryption
):
raise ATT_Error(
error_code=ATT_INSUFFICIENT_ENCRYPTION_ERROR, att_handle=self.handle
)
if (
self.permissions & self.READ_REQUIRES_AUTHENTICATION
) and not connection.authenticated:
(self.permissions & self.READ_REQUIRES_AUTHENTICATION)
and connection is not None
and not connection.authenticated
):
raise ATT_Error(
error_code=ATT_INSUFFICIENT_AUTHENTICATION_ERROR, att_handle=self.handle
)
@@ -803,9 +829,9 @@ class Attribute(EventEmitter):
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
)
if read := getattr(self.value, 'read', None):
if hasattr(self.value, 'read'):
try:
value = read(connection) # pylint: disable=not-callable
value = self.value.read(connection)
except ATT_Error as error:
raise ATT_Error(
error_code=error.error_code, att_handle=self.handle
@@ -815,7 +841,7 @@ class Attribute(EventEmitter):
return self.encode_value(value)
def write_value(self, connection: Connection, value_bytes):
def write_value(self, connection: Connection, value_bytes: bytes) -> None:
if (
self.permissions & self.WRITE_REQUIRES_ENCRYPTION
) and not connection.encryption:
@@ -836,9 +862,9 @@ class Attribute(EventEmitter):
value = self.decode_value(value_bytes)
if write := getattr(self.value, 'write', None):
if hasattr(self.value, 'write'):
try:
write(connection, value) # pylint: disable=not-callable
self.value.write(connection, value) # pylint: disable=not-callable
except ATT_Error as error:
raise ATT_Error(
error_code=error.error_code, att_handle=self.handle

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -80,7 +80,7 @@ class BaseError(Exception):
def __init__(
self,
error_code: int | None,
error_code: Optional[int],
error_namespace: str = '',
error_name: str = '',
details: str = '',

View File

@@ -33,6 +33,8 @@ from typing import (
Tuple,
Type,
Union,
cast,
overload,
TYPE_CHECKING,
)
@@ -151,6 +153,7 @@ from .utils import (
CompositeEventEmitter,
setup_event_forwarding,
composite_listener,
deprecated,
)
from .keys import (
KeyStore,
@@ -670,9 +673,7 @@ class Connection(CompositeEventEmitter):
def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
self.device.send_l2cap_pdu(self.handle, cid, pdu)
def create_l2cap_connector(self, psm):
return self.device.create_l2cap_connector(self, psm)
@deprecated("Please use create_l2cap_channel()")
async def open_l2cap_channel(
self,
psm,
@@ -682,6 +683,23 @@ class Connection(CompositeEventEmitter):
):
return await self.device.open_l2cap_channel(self, psm, max_credits, mtu, mps)
@overload
async def create_l2cap_channel(
self, spec: l2cap.ClassicChannelSpec
) -> l2cap.ClassicChannel:
...
@overload
async def create_l2cap_channel(
self, spec: l2cap.LeCreditBasedChannelSpec
) -> l2cap.LeCreditBasedChannel:
...
async def create_l2cap_channel(
self, spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec]
) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
return await self.device.create_l2cap_channel(connection=self, spec=spec)
async def disconnect(
self, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
) -> None:
@@ -829,6 +847,9 @@ class DeviceConfiguration:
self.connectable = config.get('connectable', self.connectable)
self.discoverable = config.get('discoverable', self.discoverable)
self.gatt_services = config.get('gatt_services', self.gatt_services)
self.address_resolution_offload = config.get(
'address_resolution_offload', self.address_resolution_offload
)
# Load or synthesize an IRK
irk = config.get('irk')
@@ -1180,15 +1201,11 @@ class Device(CompositeEventEmitter):
return None
def create_l2cap_connector(self, connection, psm):
return lambda: self.l2cap_channel_manager.connect(connection, psm)
def create_l2cap_registrar(self, psm):
return lambda handler: self.register_l2cap_server(psm, handler)
def register_l2cap_server(self, psm, server):
self.l2cap_channel_manager.register_server(psm, server)
@deprecated("Please use create_l2cap_server()")
def register_l2cap_server(self, psm, server) -> int:
return self.l2cap_channel_manager.register_server(psm, server)
@deprecated("Please use create_l2cap_server()")
def register_l2cap_channel_server(
self,
psm,
@@ -1201,6 +1218,7 @@ class Device(CompositeEventEmitter):
psm, server, max_credits, mtu, mps
)
@deprecated("Please use create_l2cap_channel()")
async def open_l2cap_channel(
self,
connection,
@@ -1213,6 +1231,74 @@ class Device(CompositeEventEmitter):
connection, psm, max_credits, mtu, mps
)
@overload
async def create_l2cap_channel(
self,
connection: Connection,
spec: l2cap.ClassicChannelSpec,
) -> l2cap.ClassicChannel:
...
@overload
async def create_l2cap_channel(
self,
connection: Connection,
spec: l2cap.LeCreditBasedChannelSpec,
) -> l2cap.LeCreditBasedChannel:
...
async def create_l2cap_channel(
self,
connection: Connection,
spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
if isinstance(spec, l2cap.ClassicChannelSpec):
return await self.l2cap_channel_manager.create_classic_channel(
connection=connection, spec=spec
)
if isinstance(spec, l2cap.LeCreditBasedChannelSpec):
return await self.l2cap_channel_manager.create_le_credit_based_channel(
connection=connection, spec=spec
)
@overload
def create_l2cap_server(
self,
spec: l2cap.ClassicChannelSpec,
handler: Optional[Callable[[l2cap.ClassicChannel], Any]] = None,
) -> l2cap.ClassicChannelServer:
...
@overload
def create_l2cap_server(
self,
spec: l2cap.LeCreditBasedChannelSpec,
handler: Optional[Callable[[l2cap.LeCreditBasedChannel], Any]] = None,
) -> l2cap.LeCreditBasedChannelServer:
...
def create_l2cap_server(
self,
spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
handler: Union[
Callable[[l2cap.ClassicChannel], Any],
Callable[[l2cap.LeCreditBasedChannel], Any],
None,
] = None,
) -> Union[l2cap.ClassicChannelServer, l2cap.LeCreditBasedChannelServer]:
if isinstance(spec, l2cap.ClassicChannelSpec):
return self.l2cap_channel_manager.create_classic_server(
spec=spec,
handler=cast(Callable[[l2cap.ClassicChannel], Any], handler),
)
elif isinstance(spec, l2cap.LeCreditBasedChannelSpec):
return self.l2cap_channel_manager.create_le_credit_based_server(
handler=cast(Callable[[l2cap.LeCreditBasedChannel], Any], handler),
spec=spec,
)
else:
raise ValueError(f'Unexpected mode {spec}')
def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
self.host.send_l2cap_pdu(connection_handle, cid, pdu)
@@ -1222,7 +1308,7 @@ class Device(CompositeEventEmitter):
self.host.send_command(command, check_result), self.command_timeout
)
except asyncio.TimeoutError as error:
logger.warning('!!! Command timed out')
logger.warning(f'!!! Command {command.name} timed out')
raise CommandTimeoutError() from error
async def power_on(self) -> None:
@@ -1323,6 +1409,9 @@ class Device(CompositeEventEmitter):
# Done
self.powered_on = True
async def reset(self) -> None:
await self.host.reset()
async def power_off(self) -> None:
if self.powered_on:
await self.host.flush()
@@ -1425,10 +1514,10 @@ class Device(CompositeEventEmitter):
check_result=True,
)
self.advertising_own_address_type = own_address_type
self.auto_restart_advertising = auto_restart
self.advertising_type = advertising_type
self.advertising_own_address_type = own_address_type
self.advertising = True
self.auto_restart_advertising = auto_restart
async def stop_advertising(self) -> None:
# Disable advertising
@@ -1438,9 +1527,9 @@ class Device(CompositeEventEmitter):
check_result=True,
)
self.advertising_type = None
self.advertising_own_address_type = None
self.advertising = False
self.advertising_type = None
self.auto_restart_advertising = False
@property
@@ -2630,7 +2719,6 @@ class Device(CompositeEventEmitter):
own_address_type = self.advertising_own_address_type
# We are no longer advertising
self.advertising_own_address_type = None
self.advertising = False
if own_address_type in (
@@ -2687,7 +2775,6 @@ class Device(CompositeEventEmitter):
and self.advertising
and self.advertising_type.is_directed
):
self.advertising_own_address_type = None
self.advertising = False
# Notify listeners
@@ -2758,7 +2845,9 @@ class Device(CompositeEventEmitter):
self.abort_on(
'flush',
self.start_advertising(
advertising_type=self.advertising_type, auto_restart=True
advertising_type=self.advertising_type,
own_address_type=self.advertising_own_address_type,
auto_restart=True,
),
)

View File

@@ -28,7 +28,7 @@ import enum
import functools
import logging
import struct
from typing import Optional, Sequence, List
from typing import Optional, Sequence, Iterable, List, Union
from .colors import color
from .core import UUID, get_dict_key_by_value
@@ -187,7 +187,7 @@ GATT_CENTRAL_ADDRESS_RESOLUTION__CHARACTERISTIC = UUID.from_16_bi
# -----------------------------------------------------------------------------
def show_services(services):
def show_services(services: Iterable[Service]) -> None:
for service in services:
print(color(str(service), 'cyan'))
@@ -210,11 +210,11 @@ class Service(Attribute):
def __init__(
self,
uuid,
uuid: Union[str, UUID],
characteristics: List[Characteristic],
primary=True,
included_services: List[Service] = [],
):
) -> None:
# Convert the uuid to a UUID object if it isn't already
if isinstance(uuid, str):
uuid = UUID(uuid)
@@ -239,7 +239,7 @@ class Service(Attribute):
"""
return None
def __str__(self):
def __str__(self) -> str:
return (
f'Service(handle=0x{self.handle:04X}, '
f'end=0x{self.end_group_handle:04X}, '
@@ -255,9 +255,11 @@ class TemplateService(Service):
to expose their UUID as a class property
'''
UUID: Optional[UUID] = None
UUID: UUID
def __init__(self, characteristics, primary=True):
def __init__(
self, characteristics: List[Characteristic], primary: bool = True
) -> None:
super().__init__(self.UUID, characteristics, primary)
@@ -269,7 +271,7 @@ class IncludedServiceDeclaration(Attribute):
service: Service
def __init__(self, service):
def __init__(self, service: Service) -> None:
declaration_bytes = struct.pack(
'<HH2s', service.handle, service.end_group_handle, service.uuid.to_bytes()
)
@@ -278,7 +280,7 @@ class IncludedServiceDeclaration(Attribute):
)
self.service = service
def __str__(self):
def __str__(self) -> str:
return (
f'IncludedServiceDefinition(handle=0x{self.handle:04X}, '
f'group_starting_handle=0x{self.service.handle:04X}, '
@@ -326,7 +328,7 @@ class Characteristic(Attribute):
f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}"
)
def __str__(self):
def __str__(self) -> str:
# NOTE: we override this method to offer a consistent result between python
# versions: the value returned by IntFlag.__str__() changed in version 11.
return '|'.join(
@@ -348,10 +350,10 @@ class Characteristic(Attribute):
def __init__(
self,
uuid,
uuid: Union[str, bytes, UUID],
properties: Characteristic.Properties,
permissions,
value=b'',
permissions: Union[str, Attribute.Permissions],
value: Union[str, bytes, CharacteristicValue] = b'',
descriptors: Sequence[Descriptor] = (),
):
super().__init__(uuid, permissions, value)
@@ -369,7 +371,7 @@ class Characteristic(Attribute):
def has_properties(self, properties: Characteristic.Properties) -> bool:
return self.properties & properties == properties
def __str__(self):
def __str__(self) -> str:
return (
f'Characteristic(handle=0x{self.handle:04X}, '
f'end=0x{self.end_group_handle:04X}, '
@@ -386,7 +388,7 @@ class CharacteristicDeclaration(Attribute):
characteristic: Characteristic
def __init__(self, characteristic, value_handle):
def __init__(self, characteristic: Characteristic, value_handle: int) -> None:
declaration_bytes = (
struct.pack('<BH', characteristic.properties, value_handle)
+ characteristic.uuid.to_pdu_bytes()
@@ -397,7 +399,7 @@ class CharacteristicDeclaration(Attribute):
self.value_handle = value_handle
self.characteristic = characteristic
def __str__(self):
def __str__(self) -> str:
return (
f'CharacteristicDeclaration(handle=0x{self.handle:04X}, '
f'value_handle=0x{self.value_handle:04X}, '
@@ -520,7 +522,7 @@ class CharacteristicAdapter:
return self.wrapped_characteristic.unsubscribe(subscriber)
def __str__(self):
def __str__(self) -> str:
wrapped = str(self.wrapped_characteristic)
return f'{self.__class__.__name__}({wrapped})'
@@ -600,10 +602,10 @@ class UTF8CharacteristicAdapter(CharacteristicAdapter):
Adapter that converts strings to/from bytes using UTF-8 encoding
'''
def encode_value(self, value):
def encode_value(self, value: str) -> bytes:
return value.encode('utf-8')
def decode_value(self, value):
def decode_value(self, value: bytes) -> str:
return value.decode('utf-8')
@@ -613,7 +615,7 @@ class Descriptor(Attribute):
See Vol 3, Part G - 3.3.3 Characteristic Descriptor Declarations
'''
def __str__(self):
def __str__(self) -> str:
return (
f'Descriptor(handle=0x{self.handle:04X}, '
f'type={self.type}, '

View File

@@ -28,7 +28,18 @@ import asyncio
import logging
import struct
from datetime import datetime
from typing import List, Optional, Dict, Tuple, Callable, Union, Any
from typing import (
List,
Optional,
Dict,
Tuple,
Callable,
Union,
Any,
Iterable,
Type,
TYPE_CHECKING,
)
from pyee import EventEmitter
@@ -66,8 +77,12 @@ from .gatt import (
GATT_INCLUDE_ATTRIBUTE_TYPE,
Characteristic,
ClientCharacteristicConfigurationBits,
TemplateService,
)
if TYPE_CHECKING:
from bumble.device import Connection
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
@@ -78,16 +93,16 @@ logger = logging.getLogger(__name__)
# Proxies
# -----------------------------------------------------------------------------
class AttributeProxy(EventEmitter):
client: Client
def __init__(self, client, handle, end_group_handle, attribute_type):
def __init__(
self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
) -> None:
EventEmitter.__init__(self)
self.client = client
self.handle = handle
self.end_group_handle = end_group_handle
self.type = attribute_type
async def read_value(self, no_long_read=False):
async def read_value(self, no_long_read: bool = False) -> bytes:
return self.decode_value(
await self.client.read_value(self.handle, no_long_read)
)
@@ -97,13 +112,13 @@ class AttributeProxy(EventEmitter):
self.handle, self.encode_value(value), with_response
)
def encode_value(self, value):
def encode_value(self, value: Any) -> bytes:
return value
def decode_value(self, value_bytes):
def decode_value(self, value_bytes: bytes) -> Any:
return value_bytes
def __str__(self):
def __str__(self) -> str:
return f'Attribute(handle=0x{self.handle:04X}, type={self.type})'
@@ -136,14 +151,14 @@ class ServiceProxy(AttributeProxy):
def get_characteristics_by_uuid(self, uuid):
return self.client.get_characteristics_by_uuid(uuid, self)
def __str__(self):
def __str__(self) -> str:
return f'Service(handle=0x{self.handle:04X}, uuid={self.uuid})'
class CharacteristicProxy(AttributeProxy):
properties: Characteristic.Properties
descriptors: List[DescriptorProxy]
subscribers: Dict[Any, Callable]
subscribers: Dict[Any, Callable[[bytes], Any]]
def __init__(
self,
@@ -171,7 +186,9 @@ class CharacteristicProxy(AttributeProxy):
return await self.client.discover_descriptors(self)
async def subscribe(
self, subscriber: Optional[Callable] = None, prefer_notify=True
self,
subscriber: Optional[Callable[[bytes], Any]] = None,
prefer_notify: bool = True,
):
if subscriber is not None:
if subscriber in self.subscribers:
@@ -195,7 +212,7 @@ class CharacteristicProxy(AttributeProxy):
return await self.client.unsubscribe(self, subscriber)
def __str__(self):
def __str__(self) -> str:
return (
f'Characteristic(handle=0x{self.handle:04X}, '
f'uuid={self.uuid}, '
@@ -207,7 +224,7 @@ class DescriptorProxy(AttributeProxy):
def __init__(self, client, handle, descriptor_type):
super().__init__(client, handle, 0, descriptor_type)
def __str__(self):
def __str__(self) -> str:
return f'Descriptor(handle=0x{self.handle:04X}, type={self.type})'
@@ -216,8 +233,10 @@ class ProfileServiceProxy:
Base class for profile-specific service proxies
'''
SERVICE_CLASS: Type[TemplateService]
@classmethod
def from_client(cls, client):
def from_client(cls, client: Client) -> ProfileServiceProxy:
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
@@ -227,8 +246,12 @@ class ProfileServiceProxy:
class Client:
services: List[ServiceProxy]
cached_values: Dict[int, Tuple[datetime, bytes]]
notification_subscribers: Dict[int, Callable[[bytes], Any]]
indication_subscribers: Dict[int, Callable[[bytes], Any]]
pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
pending_request: Optional[ATT_PDU]
def __init__(self, connection):
def __init__(self, connection: Connection) -> None:
self.connection = connection
self.mtu_exchange_done = False
self.request_semaphore = asyncio.Semaphore(1)
@@ -241,16 +264,16 @@ class Client:
self.services = []
self.cached_values = {}
def send_gatt_pdu(self, pdu):
def send_gatt_pdu(self, pdu: bytes) -> None:
self.connection.send_l2cap_pdu(ATT_CID, pdu)
async def send_command(self, command):
async def send_command(self, command: ATT_PDU) -> None:
logger.debug(
f'GATT Command from client: [0x{self.connection.handle:04X}] {command}'
)
self.send_gatt_pdu(command.to_bytes())
async def send_request(self, request):
async def send_request(self, request: ATT_PDU):
logger.debug(
f'GATT Request from client: [0x{self.connection.handle:04X}] {request}'
)
@@ -279,14 +302,14 @@ class Client:
return response
def send_confirmation(self, confirmation):
def send_confirmation(self, confirmation: ATT_Handle_Value_Confirmation) -> None:
logger.debug(
f'GATT Confirmation from client: [0x{self.connection.handle:04X}] '
f'{confirmation}'
)
self.send_gatt_pdu(confirmation.to_bytes())
async def request_mtu(self, mtu):
async def request_mtu(self, mtu: int) -> int:
# Check the range
if mtu < ATT_DEFAULT_MTU:
raise ValueError(f'MTU must be >= {ATT_DEFAULT_MTU}')
@@ -313,10 +336,12 @@ class Client:
return self.connection.att_mtu
def get_services_by_uuid(self, uuid):
def get_services_by_uuid(self, uuid: UUID) -> List[ServiceProxy]:
return [service for service in self.services if service.uuid == uuid]
def get_characteristics_by_uuid(self, uuid, service=None):
def get_characteristics_by_uuid(
self, uuid: UUID, service: Optional[ServiceProxy] = None
) -> List[CharacteristicProxy]:
services = [service] if service else self.services
return [
c
@@ -363,7 +388,7 @@ class Client:
if not already_known:
self.services.append(service)
async def discover_services(self, uuids=None) -> List[ServiceProxy]:
async def discover_services(self, uuids: Iterable[UUID] = []) -> List[ServiceProxy]:
'''
See Vol 3, Part G - 4.4.1 Discover All Primary Services
'''
@@ -435,7 +460,7 @@ class Client:
return services
async def discover_service(self, uuid):
async def discover_service(self, uuid: Union[str, UUID]) -> List[ServiceProxy]:
'''
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
'''
@@ -468,7 +493,7 @@ class Client:
f'{HCI_Constant.error_name(response.error_code)}'
)
# TODO raise appropriate exception
return
return []
break
for attribute_handle, end_group_handle in response.handles_information:
@@ -480,7 +505,7 @@ class Client:
logger.warning(
f'bogus handle values: {attribute_handle} {end_group_handle}'
)
return
return []
# Create a service proxy for this service
service = ServiceProxy(
@@ -721,7 +746,7 @@ class Client:
return descriptors
async def discover_attributes(self):
async def discover_attributes(self) -> List[AttributeProxy]:
'''
Discover all attributes, regardless of type
'''
@@ -844,7 +869,9 @@ class Client:
# No more subscribers left
await self.write_value(cccd, b'\x00\x00', with_response=True)
async def read_value(self, attribute, no_long_read=False):
async def read_value(
self, attribute: Union[int, AttributeProxy], no_long_read: bool = False
) -> Any:
'''
See Vol 3, Part G - 4.8.1 Read Characteristic Value
@@ -905,7 +932,9 @@ class Client:
# Return the value as bytes
return attribute_value
async def read_characteristics_by_uuid(self, uuid, service):
async def read_characteristics_by_uuid(
self, uuid: UUID, service: Optional[ServiceProxy]
) -> List[bytes]:
'''
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
'''
@@ -960,7 +989,12 @@ class Client:
return characteristics_values
async def write_value(self, attribute, value, with_response=False):
async def write_value(
self,
attribute: Union[int, AttributeProxy],
value: bytes,
with_response: bool = False,
) -> None:
'''
See Vol 3, Part G - 4.9.1 Write Without Response & 4.9.3 Write Characteristic
Value
@@ -990,7 +1024,7 @@ class Client:
)
)
def on_gatt_pdu(self, att_pdu):
def on_gatt_pdu(self, att_pdu: ATT_PDU) -> None:
logger.debug(
f'GATT Response to client: [0x{self.connection.handle:04X}] {att_pdu}'
)
@@ -1013,6 +1047,7 @@ class Client:
return
# Return the response to the coroutine that is waiting for it
assert self.pending_response is not None
self.pending_response.set_result(att_pdu)
else:
handler_name = f'on_{att_pdu.name.lower()}'
@@ -1060,7 +1095,7 @@ class Client:
# Confirm that we received the indication
self.send_confirmation(ATT_Handle_Value_Confirmation())
def cache_value(self, attribute_handle: int, value: bytes):
def cache_value(self, attribute_handle: int, value: bytes) -> None:
self.cached_values[attribute_handle] = (
datetime.now(),
value,

View File

@@ -23,11 +23,12 @@
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations
import asyncio
import logging
from collections import defaultdict
import struct
from typing import List, Tuple, Optional, TypeVar, Type
from typing import List, Tuple, Optional, TypeVar, Type, Dict, Iterable, TYPE_CHECKING
from pyee import EventEmitter
from .colors import color
@@ -42,6 +43,7 @@ from .att import (
ATT_INVALID_OFFSET_ERROR,
ATT_REQUEST_NOT_SUPPORTED_ERROR,
ATT_REQUESTS,
ATT_PDU,
ATT_UNLIKELY_ERROR_ERROR,
ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
ATT_Error,
@@ -73,6 +75,8 @@ from .gatt import (
Service,
)
if TYPE_CHECKING:
from bumble.device import Device, Connection
# -----------------------------------------------------------------------------
# Logging
@@ -91,8 +95,13 @@ GATT_SERVER_DEFAULT_MAX_MTU = 517
# -----------------------------------------------------------------------------
class Server(EventEmitter):
attributes: List[Attribute]
services: List[Service]
attributes_by_handle: Dict[int, Attribute]
subscribers: Dict[int, Dict[int, bytes]]
indication_semaphores: defaultdict[int, asyncio.Semaphore]
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
def __init__(self, device):
def __init__(self, device: Device) -> None:
super().__init__()
self.device = device
self.services = []
@@ -107,16 +116,16 @@ class Server(EventEmitter):
self.indication_semaphores = defaultdict(lambda: asyncio.Semaphore(1))
self.pending_confirmations = defaultdict(lambda: None)
def __str__(self):
def __str__(self) -> str:
return "\n".join(map(str, self.attributes))
def send_gatt_pdu(self, connection_handle, pdu):
def send_gatt_pdu(self, connection_handle: int, pdu: bytes) -> None:
self.device.send_l2cap_pdu(connection_handle, ATT_CID, pdu)
def next_handle(self):
def next_handle(self) -> int:
return 1 + len(self.attributes)
def get_advertising_service_data(self):
def get_advertising_service_data(self) -> Dict[Attribute, bytes]:
return {
attribute: data
for attribute in self.attributes
@@ -124,7 +133,7 @@ class Server(EventEmitter):
and (data := attribute.get_advertising_data())
}
def get_attribute(self, handle):
def get_attribute(self, handle: int) -> Optional[Attribute]:
attribute = self.attributes_by_handle.get(handle)
if attribute:
return attribute
@@ -173,12 +182,17 @@ class Server(EventEmitter):
return next(
(
(attribute, self.get_attribute(attribute.characteristic.handle))
(
attribute,
self.get_attribute(attribute.characteristic.handle),
) # type: ignore
for attribute in map(
self.get_attribute,
range(service_handle.handle, service_handle.end_group_handle + 1),
)
if attribute.type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE
if attribute is not None
and attribute.type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE
and isinstance(attribute, CharacteristicDeclaration)
and attribute.characteristic.uuid == characteristic_uuid
),
None,
@@ -197,7 +211,7 @@ class Server(EventEmitter):
return next(
(
attribute
attribute # type: ignore
for attribute in map(
self.get_attribute,
range(
@@ -205,12 +219,12 @@ class Server(EventEmitter):
characteristic_value.end_group_handle + 1,
),
)
if attribute.type == descriptor_uuid
if attribute is not None and attribute.type == descriptor_uuid
),
None,
)
def add_attribute(self, attribute):
def add_attribute(self, attribute: Attribute) -> None:
# Assign a handle to this attribute
attribute.handle = self.next_handle()
attribute.end_group_handle = (
@@ -220,7 +234,7 @@ class Server(EventEmitter):
# Add this attribute to the list
self.attributes.append(attribute)
def add_service(self, service: Service):
def add_service(self, service: Service) -> None:
# Add the service attribute to the DB
self.add_attribute(service)
@@ -285,11 +299,13 @@ class Server(EventEmitter):
service.end_group_handle = self.attributes[-1].handle
self.services.append(service)
def add_services(self, services):
def add_services(self, services: Iterable[Service]) -> None:
for service in services:
self.add_service(service)
def read_cccd(self, connection, characteristic):
def read_cccd(
self, connection: Optional[Connection], characteristic: Characteristic
) -> bytes:
if connection is None:
return bytes([0, 0])
@@ -300,7 +316,12 @@ class Server(EventEmitter):
return cccd or bytes([0, 0])
def write_cccd(self, connection, characteristic, value):
def write_cccd(
self,
connection: Connection,
characteristic: Characteristic,
value: bytes,
) -> None:
logger.debug(
f'Subscription update for connection=0x{connection.handle:04X}, '
f'handle=0x{characteristic.handle:04X}: {value.hex()}'
@@ -327,13 +348,19 @@ class Server(EventEmitter):
indicate_enabled,
)
def send_response(self, connection, response):
def send_response(self, connection: Connection, response: ATT_PDU) -> None:
logger.debug(
f'GATT Response from server: [0x{connection.handle:04X}] {response}'
)
self.send_gatt_pdu(connection.handle, response.to_bytes())
async def notify_subscriber(self, connection, attribute, value=None, force=False):
async def notify_subscriber(
self,
connection: Connection,
attribute: Attribute,
value: Optional[bytes] = None,
force: bool = False,
) -> None:
# Check if there's a subscriber
if not force:
subscribers = self.subscribers.get(connection.handle)
@@ -370,7 +397,13 @@ class Server(EventEmitter):
)
self.send_gatt_pdu(connection.handle, bytes(notification))
async def indicate_subscriber(self, connection, attribute, value=None, force=False):
async def indicate_subscriber(
self,
connection: Connection,
attribute: Attribute,
value: Optional[bytes] = None,
force: bool = False,
) -> None:
# Check if there's a subscriber
if not force:
subscribers = self.subscribers.get(connection.handle)
@@ -411,15 +444,13 @@ class Server(EventEmitter):
assert self.pending_confirmations[connection.handle] is None
# Create a future value to hold the eventual response
self.pending_confirmations[
pending_confirmation = self.pending_confirmations[
connection.handle
] = asyncio.get_running_loop().create_future()
try:
self.send_gatt_pdu(connection.handle, indication.to_bytes())
await asyncio.wait_for(
self.pending_confirmations[connection.handle], GATT_REQUEST_TIMEOUT
)
await asyncio.wait_for(pending_confirmation, GATT_REQUEST_TIMEOUT)
except asyncio.TimeoutError as error:
logger.warning(color('!!! GATT Indicate timeout', 'red'))
raise TimeoutError(f'GATT timeout for {indication.name}') from error
@@ -427,8 +458,12 @@ class Server(EventEmitter):
self.pending_confirmations[connection.handle] = None
async def notify_or_indicate_subscribers(
self, indicate, attribute, value=None, force=False
):
self,
indicate: bool,
attribute: Attribute,
value: Optional[bytes] = None,
force: bool = False,
) -> None:
# Get all the connections for which there's at least one subscription
connections = [
connection
@@ -450,13 +485,23 @@ class Server(EventEmitter):
]
)
async def notify_subscribers(self, attribute, value=None, force=False):
async def notify_subscribers(
self,
attribute: Attribute,
value: Optional[bytes] = None,
force: bool = False,
):
return await self.notify_or_indicate_subscribers(False, attribute, value, force)
async def indicate_subscribers(self, attribute, value=None, force=False):
async def indicate_subscribers(
self,
attribute: Attribute,
value: Optional[bytes] = None,
force: bool = False,
):
return await self.notify_or_indicate_subscribers(True, attribute, value, force)
def on_disconnection(self, connection):
def on_disconnection(self, connection: Connection) -> None:
if connection.handle in self.subscribers:
del self.subscribers[connection.handle]
if connection.handle in self.indication_semaphores:
@@ -464,7 +509,7 @@ class Server(EventEmitter):
if connection.handle in self.pending_confirmations:
del self.pending_confirmations[connection.handle]
def on_gatt_pdu(self, connection, att_pdu):
def on_gatt_pdu(self, connection: Connection, att_pdu: ATT_PDU) -> None:
logger.debug(f'GATT Request to server: [0x{connection.handle:04X}] {att_pdu}')
handler_name = f'on_{att_pdu.name.lower()}'
handler = getattr(self, handler_name, None)
@@ -506,7 +551,7 @@ class Server(EventEmitter):
#######################################################
# ATT handlers
#######################################################
def on_att_request(self, connection, pdu):
def on_att_request(self, connection: Connection, pdu: ATT_PDU) -> None:
'''
Handler for requests without a more specific handler
'''
@@ -679,7 +724,6 @@ class Server(EventEmitter):
and attribute.handle <= request.ending_handle
and pdu_space_available
):
try:
attribute_value = attribute.read_value(connection)
except ATT_Error as error:

View File

@@ -121,6 +121,7 @@ HCI_VERSION_BLUETOOTH_CORE_5_0 = 9
HCI_VERSION_BLUETOOTH_CORE_5_1 = 10
HCI_VERSION_BLUETOOTH_CORE_5_2 = 11
HCI_VERSION_BLUETOOTH_CORE_5_3 = 12
HCI_VERSION_BLUETOOTH_CORE_5_4 = 13
HCI_VERSION_NAMES = {
HCI_VERSION_BLUETOOTH_CORE_1_0B: 'HCI_VERSION_BLUETOOTH_CORE_1_0B',
@@ -135,7 +136,8 @@ HCI_VERSION_NAMES = {
HCI_VERSION_BLUETOOTH_CORE_5_0: 'HCI_VERSION_BLUETOOTH_CORE_5_0',
HCI_VERSION_BLUETOOTH_CORE_5_1: 'HCI_VERSION_BLUETOOTH_CORE_5_1',
HCI_VERSION_BLUETOOTH_CORE_5_2: 'HCI_VERSION_BLUETOOTH_CORE_5_2',
HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3'
HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3',
HCI_VERSION_BLUETOOTH_CORE_5_4: 'HCI_VERSION_BLUETOOTH_CORE_5_4',
}
# LMP Version
@@ -4397,7 +4399,7 @@ class HCI_Event(HCI_Packet):
if len(parameters) != length:
raise ValueError('invalid packet length')
cls: Type[HCI_Event | HCI_LE_Meta_Event] | None
cls: Any
if event_code == HCI_LE_META_EVENT:
# We do this dispatch here and not in the subclass in order to avoid call
# loops

332
bumble/hid.py Normal file
View File

@@ -0,0 +1,332 @@
# Copyright 2021-2022 Google LLC
#
# Licensed under the Apache License, Version 2.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.
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import dataclass
import logging
import asyncio
import enum
from pyee import EventEmitter
from typing import Optional, Tuple, Callable, Dict, Union, TYPE_CHECKING
from . import core, l2cap # type: ignore
from .colors import color # type: ignore
from .core import BT_BR_EDR_TRANSPORT, InvalidStateError, ProtocolError # type: ignore
if TYPE_CHECKING:
from bumble.device import Device, Connection
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------
# fmt: on
HID_CONTROL_PSM = 0x0011
HID_INTERRUPT_PSM = 0x0013
class Message:
message_type: MessageType
# Report types
class ReportType(enum.IntEnum):
OTHER_REPORT = 0x00
INPUT_REPORT = 0x01
OUTPUT_REPORT = 0x02
FEATURE_REPORT = 0x03
# Handshake parameters
class Handshake(enum.IntEnum):
SUCCESSFUL = 0x00
NOT_READY = 0x01
ERR_INVALID_REPORT_ID = 0x02
ERR_UNSUPPORTED_REQUEST = 0x03
ERR_UNKNOWN = 0x0E
ERR_FATAL = 0x0F
# Message Type
class MessageType(enum.IntEnum):
HANDSHAKE = 0x00
CONTROL = 0x01
GET_REPORT = 0x04
SET_REPORT = 0x05
GET_PROTOCOL = 0x06
SET_PROTOCOL = 0x07
DATA = 0x0A
# Protocol modes
class ProtocolMode(enum.IntEnum):
BOOT_PROTOCOL = 0x00
REPORT_PROTOCOL = 0x01
# Control Operations
class ControlCommand(enum.IntEnum):
SUSPEND = 0x03
EXIT_SUSPEND = 0x04
VIRTUAL_CABLE_UNPLUG = 0x05
# Class Method to derive header
@classmethod
def header(cls, lower_bits: int = 0x00) -> bytes:
return bytes([(cls.message_type << 4) | lower_bits])
# HIDP messages
@dataclass
class GetReportMessage(Message):
report_type: int
report_id: int
buffer_size: int
message_type = Message.MessageType.GET_REPORT
def __bytes__(self) -> bytes:
packet_bytes = bytearray()
packet_bytes.append(self.report_id)
packet_bytes.extend(
[(self.buffer_size & 0xFF), ((self.buffer_size >> 8) & 0xFF)]
)
if self.report_type == Message.ReportType.OTHER_REPORT:
return self.header(self.report_type) + packet_bytes
else:
return self.header(0x08 | self.report_type) + packet_bytes
@dataclass
class SetReportMessage(Message):
report_type: int
data: bytes
message_type = Message.MessageType.SET_REPORT
def __bytes__(self) -> bytes:
return self.header(self.report_type) + self.data
@dataclass
class GetProtocolMessage(Message):
message_type = Message.MessageType.GET_PROTOCOL
def __bytes__(self) -> bytes:
return self.header()
@dataclass
class SetProtocolMessage(Message):
protocol_mode: int
message_type = Message.MessageType.SET_PROTOCOL
def __bytes__(self) -> bytes:
return self.header(self.protocol_mode)
@dataclass
class Suspend(Message):
message_type = Message.MessageType.CONTROL
def __bytes__(self) -> bytes:
return self.header(Message.ControlCommand.SUSPEND)
@dataclass
class ExitSuspend(Message):
message_type = Message.MessageType.CONTROL
def __bytes__(self) -> bytes:
return self.header(Message.ControlCommand.EXIT_SUSPEND)
@dataclass
class VirtualCableUnplug(Message):
message_type = Message.MessageType.CONTROL
def __bytes__(self) -> bytes:
return self.header(Message.ControlCommand.VIRTUAL_CABLE_UNPLUG)
@dataclass
class SendData(Message):
data: bytes
message_type = Message.MessageType.DATA
def __bytes__(self) -> bytes:
return self.header(Message.ReportType.OUTPUT_REPORT) + self.data
# -----------------------------------------------------------------------------
class Host(EventEmitter):
l2cap_ctrl_channel: Optional[l2cap.ClassicChannel]
l2cap_intr_channel: Optional[l2cap.ClassicChannel]
def __init__(self, device: Device, connection: Connection) -> None:
super().__init__()
self.device = device
self.connection = connection
self.l2cap_ctrl_channel = None
self.l2cap_intr_channel = None
# Register ourselves with the L2CAP channel manager
device.register_l2cap_server(HID_CONTROL_PSM, self.on_connection)
device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_connection)
async def connect_control_channel(self) -> None:
# Create a new L2CAP connection - control channel
try:
self.l2cap_ctrl_channel = await self.device.l2cap_channel_manager.connect(
self.connection, HID_CONTROL_PSM
)
except ProtocolError:
logging.exception(f'L2CAP connection failed.')
raise
assert self.l2cap_ctrl_channel is not None
# Become a sink for the L2CAP channel
self.l2cap_ctrl_channel.sink = self.on_ctrl_pdu
async def connect_interrupt_channel(self) -> None:
# Create a new L2CAP connection - interrupt channel
try:
self.l2cap_intr_channel = await self.device.l2cap_channel_manager.connect(
self.connection, HID_INTERRUPT_PSM
)
except ProtocolError:
logging.exception(f'L2CAP connection failed.')
raise
assert self.l2cap_intr_channel is not None
# Become a sink for the L2CAP channel
self.l2cap_intr_channel.sink = self.on_intr_pdu
async def disconnect_interrupt_channel(self) -> None:
if self.l2cap_intr_channel is None:
raise InvalidStateError('invalid state')
channel = self.l2cap_intr_channel
self.l2cap_intr_channel = None
await channel.disconnect()
async def disconnect_control_channel(self) -> None:
if self.l2cap_ctrl_channel is None:
raise InvalidStateError('invalid state')
channel = self.l2cap_ctrl_channel
self.l2cap_ctrl_channel = None
await channel.disconnect()
def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug(f'+++ New L2CAP connection: {l2cap_channel}')
l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
if l2cap_channel.psm == HID_CONTROL_PSM:
self.l2cap_ctrl_channel = l2cap_channel
self.l2cap_ctrl_channel.sink = self.on_ctrl_pdu
else:
self.l2cap_intr_channel = l2cap_channel
self.l2cap_intr_channel.sink = self.on_intr_pdu
logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
def on_ctrl_pdu(self, pdu: bytes) -> None:
logger.debug(f'<<< HID CONTROL PDU: {pdu.hex()}')
# Here we will receive all kinds of packets, parse and then call respective callbacks
message_type = pdu[0] >> 4
param = pdu[0] & 0x0F
if message_type == Message.MessageType.HANDSHAKE:
logger.debug(f'<<< HID HANDSHAKE: {Message.Handshake(param).name}')
self.emit('handshake', Message.Handshake(param))
elif message_type == Message.MessageType.DATA:
logger.debug('<<< HID CONTROL DATA')
self.emit('data', pdu)
elif message_type == Message.MessageType.CONTROL:
if param == Message.ControlCommand.SUSPEND:
logger.debug('<<< HID SUSPEND')
self.emit('suspend', pdu)
elif param == Message.ControlCommand.EXIT_SUSPEND:
logger.debug('<<< HID EXIT SUSPEND')
self.emit('exit_suspend', pdu)
elif param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
self.emit('virtual_cable_unplug')
else:
logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
else:
logger.debug('<<< HID CONTROL DATA')
self.emit('data', pdu)
def on_intr_pdu(self, pdu: bytes) -> None:
logger.debug(f'<<< HID INTERRUPT PDU: {pdu.hex()}')
self.emit("data", pdu)
def get_report(self, report_type: int, report_id: int, buffer_size: int) -> None:
msg = GetReportMessage(
report_type=report_type, report_id=report_id, buffer_size=buffer_size
)
hid_message = bytes(msg)
logger.debug(f'>>> HID CONTROL GET REPORT, PDU: {hid_message.hex()}')
self.send_pdu_on_ctrl(hid_message)
def set_report(self, report_type: int, data: bytes):
msg = SetReportMessage(report_type=report_type, data=data)
hid_message = bytes(msg)
logger.debug(f'>>> HID CONTROL SET REPORT, PDU:{hid_message.hex()}')
self.send_pdu_on_ctrl(hid_message)
def get_protocol(self):
msg = GetProtocolMessage()
hid_message = bytes(msg)
logger.debug(f'>>> HID CONTROL GET PROTOCOL, PDU: {hid_message.hex()}')
self.send_pdu_on_ctrl(hid_message)
def set_protocol(self, protocol_mode: int):
msg = SetProtocolMessage(protocol_mode=protocol_mode)
hid_message = bytes(msg)
logger.debug(f'>>> HID CONTROL SET PROTOCOL, PDU: {hid_message.hex()}')
self.send_pdu_on_ctrl(hid_message)
def send_pdu_on_ctrl(self, msg: bytes) -> None:
self.l2cap_ctrl_channel.send_pdu(msg) # type: ignore
def send_pdu_on_intr(self, msg: bytes) -> None:
self.l2cap_intr_channel.send_pdu(msg) # type: ignore
def send_data(self, data):
msg = SendData(data)
hid_message = bytes(msg)
logger.debug(f'>>> HID INTERRUPT SEND DATA, PDU: {hid_message.hex()}')
self.send_pdu_on_intr(hid_message)
def suspend(self):
msg = Suspend()
hid_message = bytes(msg)
logger.debug(f'>>> HID CONTROL SUSPEND, PDU:{hid_message.hex()}')
self.send_pdu_on_ctrl(msg)
def exit_suspend(self):
msg = ExitSuspend()
hid_message = bytes(msg)
logger.debug(f'>>> HID CONTROL EXIT SUSPEND, PDU:{hid_message.hex()}')
self.send_pdu_on_ctrl(msg)
def virtual_cable_unplug(self):
msg = VirtualCableUnplug()
hid_message = bytes(msg)
logger.debug(f'>>> HID CONTROL VIRTUAL CABLE UNPLUG, PDU: {hid_message.hex()}')
self.send_pdu_on_ctrl(msg)

View File

@@ -17,6 +17,8 @@
# -----------------------------------------------------------------------------
from __future__ import annotations
import asyncio
import dataclasses
import enum
import logging
import struct
@@ -37,6 +39,7 @@ from typing import (
TYPE_CHECKING,
)
from .utils import deprecated
from .colors import color
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
from .hci import (
@@ -166,6 +169,34 @@ L2CAP_MTU_CONFIGURATION_PARAMETER_TYPE = 0x01
# pylint: disable=invalid-name
@dataclasses.dataclass
class ClassicChannelSpec:
psm: Optional[int] = None
mtu: int = L2CAP_MIN_BR_EDR_MTU
@dataclasses.dataclass
class LeCreditBasedChannelSpec:
psm: Optional[int] = None
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS
def __post_init__(self):
if (
self.max_credits < 1
or self.max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
):
raise ValueError('max credits out of range')
if self.mtu < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MTU:
raise ValueError('MTU too small')
if (
self.mps < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MPS
or self.mps > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MPS
):
raise ValueError('MPS out of range')
class L2CAP_PDU:
'''
See Bluetooth spec @ Vol 3, Part A - 3 DATA PACKET FORMAT
@@ -675,57 +706,36 @@ class L2CAP_LE_Flow_Control_Credit(L2CAP_Control_Frame):
# -----------------------------------------------------------------------------
class Channel(EventEmitter):
# States
CLOSED = 0x00
WAIT_CONNECT = 0x01
WAIT_CONNECT_RSP = 0x02
OPEN = 0x03
WAIT_DISCONNECT = 0x04
WAIT_CREATE = 0x05
WAIT_CREATE_RSP = 0x06
WAIT_MOVE = 0x07
WAIT_MOVE_RSP = 0x08
WAIT_MOVE_CONFIRM = 0x09
WAIT_CONFIRM_RSP = 0x0A
class ClassicChannel(EventEmitter):
class State(enum.IntEnum):
# States
CLOSED = 0x00
WAIT_CONNECT = 0x01
WAIT_CONNECT_RSP = 0x02
OPEN = 0x03
WAIT_DISCONNECT = 0x04
WAIT_CREATE = 0x05
WAIT_CREATE_RSP = 0x06
WAIT_MOVE = 0x07
WAIT_MOVE_RSP = 0x08
WAIT_MOVE_CONFIRM = 0x09
WAIT_CONFIRM_RSP = 0x0A
# CONFIG substates
WAIT_CONFIG = 0x10
WAIT_SEND_CONFIG = 0x11
WAIT_CONFIG_REQ_RSP = 0x12
WAIT_CONFIG_RSP = 0x13
WAIT_CONFIG_REQ = 0x14
WAIT_IND_FINAL_RSP = 0x15
WAIT_FINAL_RSP = 0x16
WAIT_CONTROL_IND = 0x17
STATE_NAMES = {
CLOSED: 'CLOSED',
WAIT_CONNECT: 'WAIT_CONNECT',
WAIT_CONNECT_RSP: 'WAIT_CONNECT_RSP',
OPEN: 'OPEN',
WAIT_DISCONNECT: 'WAIT_DISCONNECT',
WAIT_CREATE: 'WAIT_CREATE',
WAIT_CREATE_RSP: 'WAIT_CREATE_RSP',
WAIT_MOVE: 'WAIT_MOVE',
WAIT_MOVE_RSP: 'WAIT_MOVE_RSP',
WAIT_MOVE_CONFIRM: 'WAIT_MOVE_CONFIRM',
WAIT_CONFIRM_RSP: 'WAIT_CONFIRM_RSP',
WAIT_CONFIG: 'WAIT_CONFIG',
WAIT_SEND_CONFIG: 'WAIT_SEND_CONFIG',
WAIT_CONFIG_REQ_RSP: 'WAIT_CONFIG_REQ_RSP',
WAIT_CONFIG_RSP: 'WAIT_CONFIG_RSP',
WAIT_CONFIG_REQ: 'WAIT_CONFIG_REQ',
WAIT_IND_FINAL_RSP: 'WAIT_IND_FINAL_RSP',
WAIT_FINAL_RSP: 'WAIT_FINAL_RSP',
WAIT_CONTROL_IND: 'WAIT_CONTROL_IND',
}
# CONFIG substates
WAIT_CONFIG = 0x10
WAIT_SEND_CONFIG = 0x11
WAIT_CONFIG_REQ_RSP = 0x12
WAIT_CONFIG_RSP = 0x13
WAIT_CONFIG_REQ = 0x14
WAIT_IND_FINAL_RSP = 0x15
WAIT_FINAL_RSP = 0x16
WAIT_CONTROL_IND = 0x17
connection_result: Optional[asyncio.Future[None]]
disconnection_result: Optional[asyncio.Future[None]]
response: Optional[asyncio.Future[bytes]]
sink: Optional[Callable[[bytes], Any]]
state: int
state: State
connection: Connection
def __init__(
@@ -741,7 +751,7 @@ class Channel(EventEmitter):
self.manager = manager
self.connection = connection
self.signaling_cid = signaling_cid
self.state = Channel.CLOSED
self.state = self.State.CLOSED
self.mtu = mtu
self.psm = psm
self.source_cid = source_cid
@@ -751,13 +761,11 @@ class Channel(EventEmitter):
self.disconnection_result = None
self.sink = None
def change_state(self, new_state: int) -> None:
logger.debug(
f'{self} state change -> {color(Channel.STATE_NAMES[new_state], "cyan")}'
)
def _change_state(self, new_state: State) -> None:
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
self.state = new_state
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
@@ -767,7 +775,7 @@ class Channel(EventEmitter):
# Check that there isn't already a request pending
if self.response:
raise InvalidStateError('request already pending')
if self.state != Channel.OPEN:
if self.state != self.State.OPEN:
raise InvalidStateError('channel not open')
self.response = asyncio.get_running_loop().create_future()
@@ -787,14 +795,14 @@ class Channel(EventEmitter):
)
async def connect(self) -> None:
if self.state != Channel.CLOSED:
if self.state != self.State.CLOSED:
raise InvalidStateError('invalid state')
# Check that we can start a new connection
if self.connection_result:
raise RuntimeError('connection already pending')
self.change_state(Channel.WAIT_CONNECT_RSP)
self._change_state(self.State.WAIT_CONNECT_RSP)
self.send_control_frame(
L2CAP_Connection_Request(
identifier=self.manager.next_identifier(self.connection),
@@ -814,10 +822,10 @@ class Channel(EventEmitter):
self.connection_result = None
async def disconnect(self) -> None:
if self.state != Channel.OPEN:
if self.state != self.State.OPEN:
raise InvalidStateError('invalid state')
self.change_state(Channel.WAIT_DISCONNECT)
self._change_state(self.State.WAIT_DISCONNECT)
self.send_control_frame(
L2CAP_Disconnection_Request(
identifier=self.manager.next_identifier(self.connection),
@@ -832,8 +840,8 @@ class Channel(EventEmitter):
return await self.disconnection_result
def abort(self) -> None:
if self.state == self.OPEN:
self.change_state(self.CLOSED)
if self.state == self.State.OPEN:
self._change_state(self.State.CLOSED)
self.emit('close')
def send_configure_request(self) -> None:
@@ -856,7 +864,7 @@ class Channel(EventEmitter):
def on_connection_request(self, request) -> None:
self.destination_cid = request.source_cid
self.change_state(Channel.WAIT_CONNECT)
self._change_state(self.State.WAIT_CONNECT)
self.send_control_frame(
L2CAP_Connection_Response(
identifier=request.identifier,
@@ -866,24 +874,24 @@ class Channel(EventEmitter):
status=0x0000,
)
)
self.change_state(Channel.WAIT_CONFIG)
self._change_state(self.State.WAIT_CONFIG)
self.send_configure_request()
self.change_state(Channel.WAIT_CONFIG_REQ_RSP)
self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
def on_connection_response(self, response):
if self.state != Channel.WAIT_CONNECT_RSP:
if self.state != self.State.WAIT_CONNECT_RSP:
logger.warning(color('invalid state', 'red'))
return
if response.result == L2CAP_Connection_Response.CONNECTION_SUCCESSFUL:
self.destination_cid = response.destination_cid
self.change_state(Channel.WAIT_CONFIG)
self._change_state(self.State.WAIT_CONFIG)
self.send_configure_request()
self.change_state(Channel.WAIT_CONFIG_REQ_RSP)
self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
elif response.result == L2CAP_Connection_Response.CONNECTION_PENDING:
pass
else:
self.change_state(Channel.CLOSED)
self._change_state(self.State.CLOSED)
self.connection_result.set_exception(
ProtocolError(
response.result,
@@ -895,9 +903,9 @@ class Channel(EventEmitter):
def on_configure_request(self, request) -> None:
if self.state not in (
Channel.WAIT_CONFIG,
Channel.WAIT_CONFIG_REQ,
Channel.WAIT_CONFIG_REQ_RSP,
self.State.WAIT_CONFIG,
self.State.WAIT_CONFIG_REQ,
self.State.WAIT_CONFIG_REQ_RSP,
):
logger.warning(color('invalid state', 'red'))
return
@@ -918,25 +926,28 @@ class Channel(EventEmitter):
options=request.options, # TODO: don't accept everything blindly
)
)
if self.state == Channel.WAIT_CONFIG:
self.change_state(Channel.WAIT_SEND_CONFIG)
if self.state == self.State.WAIT_CONFIG:
self._change_state(self.State.WAIT_SEND_CONFIG)
self.send_configure_request()
self.change_state(Channel.WAIT_CONFIG_RSP)
elif self.state == Channel.WAIT_CONFIG_REQ:
self.change_state(Channel.OPEN)
self._change_state(self.State.WAIT_CONFIG_RSP)
elif self.state == self.State.WAIT_CONFIG_REQ:
self._change_state(self.State.OPEN)
if self.connection_result:
self.connection_result.set_result(None)
self.connection_result = None
self.emit('open')
elif self.state == Channel.WAIT_CONFIG_REQ_RSP:
self.change_state(Channel.WAIT_CONFIG_RSP)
elif self.state == self.State.WAIT_CONFIG_REQ_RSP:
self._change_state(self.State.WAIT_CONFIG_RSP)
def on_configure_response(self, response) -> None:
if response.result == L2CAP_Configure_Response.SUCCESS:
if self.state == Channel.WAIT_CONFIG_REQ_RSP:
self.change_state(Channel.WAIT_CONFIG_REQ)
elif self.state in (Channel.WAIT_CONFIG_RSP, Channel.WAIT_CONTROL_IND):
self.change_state(Channel.OPEN)
if self.state == self.State.WAIT_CONFIG_REQ_RSP:
self._change_state(self.State.WAIT_CONFIG_REQ)
elif self.state in (
self.State.WAIT_CONFIG_RSP,
self.State.WAIT_CONTROL_IND,
):
self._change_state(self.State.OPEN)
if self.connection_result:
self.connection_result.set_result(None)
self.connection_result = None
@@ -966,7 +977,7 @@ class Channel(EventEmitter):
# TODO: decide how to fail gracefully
def on_disconnection_request(self, request) -> None:
if self.state in (Channel.OPEN, Channel.WAIT_DISCONNECT):
if self.state in (self.State.OPEN, self.State.WAIT_DISCONNECT):
self.send_control_frame(
L2CAP_Disconnection_Response(
identifier=request.identifier,
@@ -974,14 +985,14 @@ class Channel(EventEmitter):
source_cid=request.source_cid,
)
)
self.change_state(Channel.CLOSED)
self._change_state(self.State.CLOSED)
self.emit('close')
self.manager.on_channel_closed(self)
else:
logger.warning(color('invalid state', 'red'))
def on_disconnection_response(self, response) -> None:
if self.state != Channel.WAIT_DISCONNECT:
if self.state != self.State.WAIT_DISCONNECT:
logger.warning(color('invalid state', 'red'))
return
@@ -992,7 +1003,7 @@ class Channel(EventEmitter):
logger.warning('unexpected source or destination CID')
return
self.change_state(Channel.CLOSED)
self._change_state(self.State.CLOSED)
if self.disconnection_result:
self.disconnection_result.set_result(None)
self.disconnection_result = None
@@ -1004,42 +1015,32 @@ class Channel(EventEmitter):
f'Channel({self.source_cid}->{self.destination_cid}, '
f'PSM={self.psm}, '
f'MTU={self.mtu}, '
f'state={Channel.STATE_NAMES[self.state]})'
f'state={self.state.name})'
)
# -----------------------------------------------------------------------------
class LeConnectionOrientedChannel(EventEmitter):
class LeCreditBasedChannel(EventEmitter):
"""
LE Credit-based Connection Oriented Channel
"""
INIT = 0
CONNECTED = 1
CONNECTING = 2
DISCONNECTING = 3
DISCONNECTED = 4
CONNECTION_ERROR = 5
STATE_NAMES = {
INIT: 'INIT',
CONNECTED: 'CONNECTED',
CONNECTING: 'CONNECTING',
DISCONNECTING: 'DISCONNECTING',
DISCONNECTED: 'DISCONNECTED',
CONNECTION_ERROR: 'CONNECTION_ERROR',
}
class State(enum.IntEnum):
INIT = 0
CONNECTED = 1
CONNECTING = 2
DISCONNECTING = 3
DISCONNECTED = 4
CONNECTION_ERROR = 5
out_queue: Deque[bytes]
connection_result: Optional[asyncio.Future[LeConnectionOrientedChannel]]
connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
disconnection_result: Optional[asyncio.Future[None]]
in_sdu: Optional[bytes]
out_sdu: Optional[bytes]
state: int
state: State
connection: Connection
@staticmethod
def state_name(state: int) -> str:
return name_or_number(LeConnectionOrientedChannel.STATE_NAMES, state)
sink: Optional[Callable[[bytes], Any]]
def __init__(
self,
@@ -1083,30 +1084,28 @@ class LeConnectionOrientedChannel(EventEmitter):
self.drained.set()
if connected:
self.state = LeConnectionOrientedChannel.CONNECTED
self.state = self.State.CONNECTED
else:
self.state = LeConnectionOrientedChannel.INIT
self.state = self.State.INIT
def change_state(self, new_state: int) -> None:
logger.debug(
f'{self} state change -> {color(self.state_name(new_state), "cyan")}'
)
def _change_state(self, new_state: State) -> None:
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
self.state = new_state
if new_state == self.CONNECTED:
if new_state == self.State.CONNECTED:
self.emit('open')
elif new_state == self.DISCONNECTED:
elif new_state == self.State.DISCONNECTED:
self.emit('close')
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
self.manager.send_control_frame(self.connection, L2CAP_LE_SIGNALING_CID, frame)
async def connect(self) -> LeConnectionOrientedChannel:
async def connect(self) -> LeCreditBasedChannel:
# Check that we're in the right state
if self.state != self.INIT:
if self.state != self.State.INIT:
raise InvalidStateError('not in a connectable state')
# Check that we can start a new connection
@@ -1114,7 +1113,7 @@ class LeConnectionOrientedChannel(EventEmitter):
if identifier in self.manager.le_coc_requests:
raise RuntimeError('too many concurrent connection requests')
self.change_state(self.CONNECTING)
self._change_state(self.State.CONNECTING)
request = L2CAP_LE_Credit_Based_Connection_Request(
identifier=identifier,
le_psm=self.le_psm,
@@ -1134,10 +1133,10 @@ class LeConnectionOrientedChannel(EventEmitter):
async def disconnect(self) -> None:
# Check that we're connected
if self.state != self.CONNECTED:
if self.state != self.State.CONNECTED:
raise InvalidStateError('not connected')
self.change_state(self.DISCONNECTING)
self._change_state(self.State.DISCONNECTING)
self.flush_output()
self.send_control_frame(
L2CAP_Disconnection_Request(
@@ -1153,15 +1152,15 @@ class LeConnectionOrientedChannel(EventEmitter):
return await self.disconnection_result
def abort(self) -> None:
if self.state == self.CONNECTED:
self.change_state(self.DISCONNECTED)
if self.state == self.State.CONNECTED:
self._change_state(self.State.DISCONNECTED)
def on_pdu(self, pdu: bytes) -> None:
if self.sink is None:
logger.warning('received pdu without a sink')
return
if self.state != self.CONNECTED:
if self.state != self.State.CONNECTED:
logger.warning('received PDU while not connected, dropping')
# Manage the peer credits
@@ -1240,7 +1239,7 @@ class LeConnectionOrientedChannel(EventEmitter):
self.credits = response.initial_credits
self.connected = True
self.connection_result.set_result(self)
self.change_state(self.CONNECTED)
self._change_state(self.State.CONNECTED)
else:
self.connection_result.set_exception(
ProtocolError(
@@ -1251,7 +1250,7 @@ class LeConnectionOrientedChannel(EventEmitter):
),
)
)
self.change_state(self.CONNECTION_ERROR)
self._change_state(self.State.CONNECTION_ERROR)
# Cleanup
self.connection_result = None
@@ -1271,11 +1270,11 @@ class LeConnectionOrientedChannel(EventEmitter):
source_cid=request.source_cid,
)
)
self.change_state(self.DISCONNECTED)
self._change_state(self.State.DISCONNECTED)
self.flush_output()
def on_disconnection_response(self, response) -> None:
if self.state != self.DISCONNECTING:
if self.state != self.State.DISCONNECTING:
logger.warning(color('invalid state', 'red'))
return
@@ -1286,7 +1285,7 @@ class LeConnectionOrientedChannel(EventEmitter):
logger.warning('unexpected source or destination CID')
return
self.change_state(self.DISCONNECTED)
self._change_state(self.State.DISCONNECTED)
if self.disconnection_result:
self.disconnection_result.set_result(None)
self.disconnection_result = None
@@ -1339,7 +1338,7 @@ class LeConnectionOrientedChannel(EventEmitter):
return
def write(self, data: bytes) -> None:
if self.state != self.CONNECTED:
if self.state != self.State.CONNECTED:
logger.warning('not connected, dropping data')
return
@@ -1367,7 +1366,7 @@ class LeConnectionOrientedChannel(EventEmitter):
def __str__(self) -> str:
return (
f'CoC({self.source_cid}->{self.destination_cid}, '
f'State={self.state_name(self.state)}, '
f'State={self.state.name}, '
f'PSM={self.le_psm}, '
f'MTU={self.mtu}/{self.peer_mtu}, '
f'MPS={self.mps}/{self.peer_mps}, '
@@ -1375,15 +1374,67 @@ class LeConnectionOrientedChannel(EventEmitter):
)
# -----------------------------------------------------------------------------
class ClassicChannelServer(EventEmitter):
def __init__(
self,
manager: ChannelManager,
psm: int,
handler: Optional[Callable[[ClassicChannel], Any]],
mtu: int,
) -> None:
super().__init__()
self.manager = manager
self.handler = handler
self.psm = psm
self.mtu = mtu
def on_connection(self, channel: ClassicChannel) -> None:
self.emit('connection', channel)
if self.handler:
self.handler(channel)
def close(self) -> None:
if self.psm in self.manager.servers:
del self.manager.servers[self.psm]
# -----------------------------------------------------------------------------
class LeCreditBasedChannelServer(EventEmitter):
def __init__(
self,
manager: ChannelManager,
psm: int,
handler: Optional[Callable[[LeCreditBasedChannel], Any]],
max_credits: int,
mtu: int,
mps: int,
) -> None:
super().__init__()
self.manager = manager
self.handler = handler
self.psm = psm
self.max_credits = max_credits
self.mtu = mtu
self.mps = mps
def on_connection(self, channel: LeCreditBasedChannel) -> None:
self.emit('connection', channel)
if self.handler:
self.handler(channel)
def close(self) -> None:
if self.psm in self.manager.le_coc_servers:
del self.manager.le_coc_servers[self.psm]
# -----------------------------------------------------------------------------
class ChannelManager:
identifiers: Dict[int, int]
channels: Dict[int, Dict[int, Union[Channel, LeConnectionOrientedChannel]]]
servers: Dict[int, Callable[[Channel], Any]]
le_coc_channels: Dict[int, Dict[int, LeConnectionOrientedChannel]]
le_coc_servers: Dict[
int, Tuple[Callable[[LeConnectionOrientedChannel], Any], int, int, int]
]
channels: Dict[int, Dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
servers: Dict[int, ClassicChannelServer]
le_coc_channels: Dict[int, Dict[int, LeCreditBasedChannel]]
le_coc_servers: Dict[int, LeCreditBasedChannelServer]
le_coc_requests: Dict[int, L2CAP_LE_Credit_Based_Connection_Request]
fixed_channels: Dict[int, Optional[Callable[[int, bytes], Any]]]
_host: Optional[Host]
@@ -1462,21 +1513,6 @@ class ChannelManager:
raise RuntimeError('no free CID')
@staticmethod
def check_le_coc_parameters(max_credits: int, mtu: int, mps: int) -> None:
if (
max_credits < 1
or max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
):
raise ValueError('max credits out of range')
if mtu < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MTU:
raise ValueError('MTU too small')
if (
mps < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MPS
or mps > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MPS
):
raise ValueError('MPS out of range')
def next_identifier(self, connection: Connection) -> int:
identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
self.identifiers[connection.handle] = identifier
@@ -1491,8 +1527,22 @@ class ChannelManager:
if cid in self.fixed_channels:
del self.fixed_channels[cid]
def register_server(self, psm: int, server: Callable[[Channel], Any]) -> int:
if psm == 0:
@deprecated("Please use create_classic_server")
def register_server(
self,
psm: int,
server: Callable[[ClassicChannel], Any],
) -> int:
return self.create_classic_server(
handler=server, spec=ClassicChannelSpec(psm=psm)
).psm
def create_classic_server(
self,
spec: ClassicChannelSpec,
handler: Optional[Callable[[ClassicChannel], Any]] = None,
) -> ClassicChannelServer:
if not spec.psm:
# Find a free PSM
for candidate in range(
L2CAP_PSM_DYNAMIC_RANGE_START, L2CAP_PSM_DYNAMIC_RANGE_END + 1, 2
@@ -1501,62 +1551,75 @@ class ChannelManager:
continue
if candidate in self.servers:
continue
psm = candidate
spec.psm = candidate
break
else:
raise InvalidStateError('no free PSM')
else:
# Check that the PSM isn't already in use
if psm in self.servers:
if spec.psm in self.servers:
raise ValueError('PSM already in use')
# Check that the PSM is valid
if psm % 2 == 0:
if spec.psm % 2 == 0:
raise ValueError('invalid PSM (not odd)')
check = psm >> 8
check = spec.psm >> 8
while check:
if check % 2 != 0:
raise ValueError('invalid PSM')
check >>= 8
self.servers[psm] = server
self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec.mtu)
return psm
return self.servers[spec.psm]
@deprecated("Please use create_le_credit_based_server()")
def register_le_coc_server(
self,
psm: int,
server: Callable[[LeConnectionOrientedChannel], Any],
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
server: Callable[[LeCreditBasedChannel], Any],
max_credits: int,
mtu: int,
mps: int,
) -> int:
self.check_le_coc_parameters(max_credits, mtu, mps)
return self.create_le_credit_based_server(
spec=LeCreditBasedChannelSpec(
psm=None if psm == 0 else psm, mtu=mtu, mps=mps, max_credits=max_credits
),
handler=server,
).psm
if psm == 0:
def create_le_credit_based_server(
self,
spec: LeCreditBasedChannelSpec,
handler: Optional[Callable[[LeCreditBasedChannel], Any]] = None,
) -> LeCreditBasedChannelServer:
if not spec.psm:
# Find a free PSM
for candidate in range(
L2CAP_LE_PSM_DYNAMIC_RANGE_START, L2CAP_LE_PSM_DYNAMIC_RANGE_END + 1
):
if candidate in self.le_coc_servers:
continue
psm = candidate
spec.psm = candidate
break
else:
raise InvalidStateError('no free PSM')
else:
# Check that the PSM isn't already in use
if psm in self.le_coc_servers:
if spec.psm in self.le_coc_servers:
raise ValueError('PSM already in use')
self.le_coc_servers[psm] = (
server,
max_credits or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
mtu or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
mps or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
self.le_coc_servers[spec.psm] = LeCreditBasedChannelServer(
self,
spec.psm,
handler,
max_credits=spec.max_credits,
mtu=spec.mtu,
mps=spec.mps,
)
return psm
return self.le_coc_servers[spec.psm]
def on_disconnection(self, connection_handle: int, _reason: int) -> None:
logger.debug(f'disconnection from {connection_handle}, cleaning up channels')
@@ -1571,7 +1634,7 @@ class ChannelManager:
if connection_handle in self.identifiers:
del self.identifiers[connection_handle]
def send_pdu(self, connection, cid: int, pdu: SupportsBytes | bytes) -> None:
def send_pdu(self, connection, cid: int, pdu: Union[SupportsBytes, bytes]) -> None:
pdu_str = pdu.hex() if isinstance(pdu, bytes) else str(pdu)
logger.debug(
f'{color(">>> Sending L2CAP PDU", "blue")} '
@@ -1683,13 +1746,13 @@ class ChannelManager:
logger.debug(
f'creating server channel with cid={source_cid} for psm {request.psm}'
)
channel = Channel(
self, connection, cid, request.psm, source_cid, L2CAP_MIN_BR_EDR_MTU
channel = ClassicChannel(
self, connection, cid, request.psm, source_cid, server.mtu
)
connection_channels[source_cid] = channel
# Notify
server(channel)
server.on_connection(channel)
channel.on_connection_request(request)
else:
logger.warning(
@@ -1911,7 +1974,7 @@ class ChannelManager:
self, connection: Connection, cid: int, request
) -> None:
if request.le_psm in self.le_coc_servers:
(server, max_credits, mtu, mps) = self.le_coc_servers[request.le_psm]
server = self.le_coc_servers[request.le_psm]
# Check that the CID isn't already used
le_connection_channels = self.le_coc_channels.setdefault(
@@ -1925,8 +1988,8 @@ class ChannelManager:
L2CAP_LE_Credit_Based_Connection_Response(
identifier=request.identifier,
destination_cid=0,
mtu=mtu,
mps=mps,
mtu=server.mtu,
mps=server.mps,
initial_credits=0,
# pylint: disable=line-too-long
result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
@@ -1944,8 +2007,8 @@ class ChannelManager:
L2CAP_LE_Credit_Based_Connection_Response(
identifier=request.identifier,
destination_cid=0,
mtu=mtu,
mps=mps,
mtu=server.mtu,
mps=server.mps,
initial_credits=0,
# pylint: disable=line-too-long
result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
@@ -1958,18 +2021,18 @@ class ChannelManager:
f'creating LE CoC server channel with cid={source_cid} for psm '
f'{request.le_psm}'
)
channel = LeConnectionOrientedChannel(
channel = LeCreditBasedChannel(
self,
connection,
request.le_psm,
source_cid,
request.source_cid,
mtu,
mps,
server.mtu,
server.mps,
request.initial_credits,
request.mtu,
request.mps,
max_credits,
server.max_credits,
True,
)
connection_channels[source_cid] = channel
@@ -1982,16 +2045,16 @@ class ChannelManager:
L2CAP_LE_Credit_Based_Connection_Response(
identifier=request.identifier,
destination_cid=source_cid,
mtu=mtu,
mps=mps,
initial_credits=max_credits,
mtu=server.mtu,
mps=server.mps,
initial_credits=server.max_credits,
# pylint: disable=line-too-long
result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL,
),
)
# Notify
server(channel)
server.on_connection(channel)
else:
logger.info(
f'No LE server for connection 0x{connection.handle:04X} '
@@ -2046,37 +2109,51 @@ class ChannelManager:
channel.on_credits(credit.credits)
def on_channel_closed(self, channel: Channel) -> None:
def on_channel_closed(self, channel: ClassicChannel) -> None:
connection_channels = self.channels.get(channel.connection.handle)
if connection_channels:
if channel.source_cid in connection_channels:
del connection_channels[channel.source_cid]
@deprecated("Please use create_le_credit_based_channel()")
async def open_le_coc(
self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int
) -> LeConnectionOrientedChannel:
self.check_le_coc_parameters(max_credits, mtu, mps)
) -> LeCreditBasedChannel:
return await self.create_le_credit_based_channel(
connection=connection,
spec=LeCreditBasedChannelSpec(
psm=psm, max_credits=max_credits, mtu=mtu, mps=mps
),
)
async def create_le_credit_based_channel(
self,
connection: Connection,
spec: LeCreditBasedChannelSpec,
) -> LeCreditBasedChannel:
# Find a free CID for the new channel
connection_channels = self.channels.setdefault(connection.handle, {})
source_cid = self.find_free_le_cid(connection_channels)
if source_cid is None: # Should never happen!
raise RuntimeError('all CIDs already in use')
if spec.psm is None:
raise ValueError('PSM cannot be None')
# Create the channel
logger.debug(f'creating coc channel with cid={source_cid} for psm {psm}')
channel = LeConnectionOrientedChannel(
logger.debug(f'creating coc channel with cid={source_cid} for psm {spec.psm}')
channel = LeCreditBasedChannel(
manager=self,
connection=connection,
le_psm=psm,
le_psm=spec.psm,
source_cid=source_cid,
destination_cid=0,
mtu=mtu,
mps=mps,
mtu=spec.mtu,
mps=spec.mps,
credits=0,
peer_mtu=0,
peer_mps=0,
peer_credits=max_credits,
peer_credits=spec.max_credits,
connected=False,
)
connection_channels[source_cid] = channel
@@ -2095,7 +2172,15 @@ class ChannelManager:
return channel
async def connect(self, connection: Connection, psm: int) -> Channel:
@deprecated("Please use create_classic_channel()")
async def connect(self, connection: Connection, psm: int) -> ClassicChannel:
return await self.create_classic_channel(
connection=connection, spec=ClassicChannelSpec(psm=psm)
)
async def create_classic_channel(
self, connection: Connection, spec: ClassicChannelSpec
) -> ClassicChannel:
# NOTE: this implementation hard-codes BR/EDR
# Find a free CID for a new channel
@@ -2104,10 +2189,20 @@ class ChannelManager:
if source_cid is None: # Should never happen!
raise RuntimeError('all CIDs already in use')
if spec.psm is None:
raise ValueError('PSM cannot be None')
# Create the channel
logger.debug(f'creating client channel with cid={source_cid} for psm {psm}')
channel = Channel(
self, connection, L2CAP_SIGNALING_CID, psm, source_cid, L2CAP_MIN_BR_EDR_MTU
logger.debug(
f'creating client channel with cid={source_cid} for psm {spec.psm}'
)
channel = ClassicChannel(
self,
connection,
L2CAP_SIGNALING_CID,
spec.psm,
source_cid,
spec.mtu,
)
connection_channels[source_cid] = channel
@@ -2119,3 +2214,20 @@ class ChannelManager:
raise e
return channel
# -----------------------------------------------------------------------------
# Deprecated Classes
# -----------------------------------------------------------------------------
class Channel(ClassicChannel):
@deprecated("Please use ClassicChannel")
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
class LeConnectionOrientedChannel(LeCreditBasedChannel):
@deprecated("Please use LeCreditBasedChannel")
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from bumble.pairing import PairingConfig, PairingDelegate
from dataclasses import dataclass
from typing import Any, Dict

View File

@@ -14,6 +14,7 @@
"""Generic & dependency free Bumble (reference) device."""
from __future__ import annotations
from bumble import transport
from bumble.core import (
BT_GENERIC_AUDIO_SERVICE,

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import asyncio
import bumble.device
import grpc

View File

@@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import asyncio
import contextlib
import grpc
import logging
@@ -27,8 +29,8 @@ from bumble.core import (
)
from bumble.device import Connection as BumbleConnection, Device
from bumble.hci import HCI_Error
from bumble.utils import EventWatcher
from bumble.pairing import PairingConfig, PairingDelegate as BasePairingDelegate
from contextlib import suppress
from google.protobuf import any_pb2 # pytype: disable=pyi-error
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
from google.protobuf import wrappers_pb2 # pytype: disable=pyi-error
@@ -232,7 +234,11 @@ class SecurityService(SecurityServicer):
sc=config.pairing_sc_enable,
mitm=config.pairing_mitm_enable,
bonding=config.pairing_bonding_enable,
identity_address_type=config.identity_address_type,
identity_address_type=(
PairingConfig.AddressType.PUBLIC
if connection.self_address.is_public
else config.identity_address_type
),
delegate=PairingDelegate(
connection,
self,
@@ -294,23 +300,35 @@ class SecurityService(SecurityServicer):
try:
self.log.debug('Pair...')
if (
connection.transport == BT_LE_TRANSPORT
and connection.role == BT_PERIPHERAL_ROLE
):
wait_for_security: asyncio.Future[
bool
] = asyncio.get_running_loop().create_future()
connection.on("pairing", lambda *_: wait_for_security.set_result(True)) # type: ignore
connection.on("pairing_failure", wait_for_security.set_exception)
security_result = asyncio.get_running_loop().create_future()
connection.request_pairing()
with contextlib.closing(EventWatcher()) as watcher:
await wait_for_security
else:
await connection.pair()
@watcher.on(connection, 'pairing')
def on_pairing(*_: Any) -> None:
security_result.set_result('success')
self.log.debug('Paired')
@watcher.on(connection, 'pairing_failure')
def on_pairing_failure(*_: Any) -> None:
security_result.set_result('pairing_failure')
@watcher.on(connection, 'disconnection')
def on_disconnection(*_: Any) -> None:
security_result.set_result('connection_died')
if (
connection.transport == BT_LE_TRANSPORT
and connection.role == BT_PERIPHERAL_ROLE
):
connection.request_pairing()
else:
await connection.pair()
result = await security_result
self.log.debug(f'Pairing session complete, status={result}')
if result != 'success':
return SecureResponse(**{result: empty_pb2.Empty()})
except asyncio.CancelledError:
self.log.warning("Connection died during encryption")
return SecureResponse(connection_died=empty_pb2.Empty())
@@ -369,6 +387,7 @@ class SecurityService(SecurityServicer):
str
] = asyncio.get_running_loop().create_future()
authenticate_task: Optional[asyncio.Future[None]] = None
pair_task: Optional[asyncio.Future[None]] = None
async def authenticate() -> None:
assert connection
@@ -415,6 +434,10 @@ class SecurityService(SecurityServicer):
if authenticate_task is None:
authenticate_task = asyncio.create_task(authenticate())
def pair(*_: Any) -> None:
if self.need_pairing(connection, level):
pair_task = asyncio.create_task(connection.pair())
listeners: Dict[str, Callable[..., None]] = {
'disconnection': set_failure('connection_died'),
'pairing_failure': set_failure('pairing_failure'),
@@ -425,23 +448,21 @@ class SecurityService(SecurityServicer):
'connection_encryption_change': on_encryption_change,
'classic_pairing': try_set_success,
'classic_pairing_failure': set_failure('pairing_failure'),
'security_request': pair,
}
# register event handlers
for event, listener in listeners.items():
connection.on(event, listener)
with contextlib.closing(EventWatcher()) as watcher:
# register event handlers
for event, listener in listeners.items():
watcher.on(connection, event, listener)
# security level already reached
if self.reached_security_level(connection, level):
return WaitSecurityResponse(success=empty_pb2.Empty())
# security level already reached
if self.reached_security_level(connection, level):
return WaitSecurityResponse(success=empty_pb2.Empty())
self.log.debug('Wait for security...')
kwargs = {}
kwargs[await wait_for_security] = empty_pb2.Empty()
# remove event handlers
for event, listener in listeners.items():
connection.remove_listener(event, listener) # type: ignore
self.log.debug('Wait for security...')
kwargs = {}
kwargs[await wait_for_security] = empty_pb2.Empty()
# wait for `authenticate` to finish if any
if authenticate_task is not None:
@@ -452,6 +473,15 @@ class SecurityService(SecurityServicer):
pass
self.log.debug('Authenticated')
# wait for `pair` to finish if any
if pair_task is not None:
self.log.debug('Wait for authentication...')
try:
await pair_task # type: ignore
except:
pass
self.log.debug('paired')
return WaitSecurityResponse(**kwargs)
def reached_security_level(
@@ -523,7 +553,7 @@ class SecurityStorageService(SecurityStorageServicer):
self.log.debug(f"DeleteBond: {address}")
if self.device.keystore is not None:
with suppress(KeyError):
with contextlib.suppress(KeyError):
await self.device.keystore.delete(str(address))
return empty_pb2.Empty()

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import contextlib
import functools
import grpc

View File

@@ -19,6 +19,8 @@
import struct
import logging
from typing import List
from bumble import l2cap
from ..core import AdvertisingData
from ..device import Device, Connection
from ..gatt import (
@@ -149,7 +151,10 @@ class AshaService(TemplateService):
channel.sink = on_data
# let the server find a free PSM
self.psm = self.device.register_l2cap_channel_server(self.psm, on_coc, 8)
self.psm = device.create_l2cap_server(
spec=l2cap.LeCreditBasedChannelSpec(psm=self.psm, max_credits=8),
handler=on_coc,
).psm
self.le_psm_out_characteristic = Characteristic(
GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
Characteristic.Properties.READ,

View File

@@ -42,12 +42,12 @@ class HeartRateService(TemplateService):
RESET_ENERGY_EXPENDED = 0x01
class BodySensorLocation(IntEnum):
OTHER = (0,)
CHEST = (1,)
WRIST = (2,)
FINGER = (3,)
HAND = (4,)
EAR_LOBE = (5,)
OTHER = 0
CHEST = 1
WRIST = 2
FINGER = 3
HAND = 4
EAR_LOBE = 5
FOOT = 6
class HeartRateMeasurement:

View File

@@ -674,7 +674,7 @@ class Multiplexer(EventEmitter):
acceptor: Optional[Callable[[int], bool]]
dlcs: Dict[int, DLC]
def __init__(self, l2cap_channel: l2cap.Channel, role: Role) -> None:
def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
super().__init__()
self.role = role
self.l2cap_channel = l2cap_channel
@@ -887,7 +887,7 @@ class Multiplexer(EventEmitter):
# -----------------------------------------------------------------------------
class Client:
multiplexer: Optional[Multiplexer]
l2cap_channel: Optional[l2cap.Channel]
l2cap_channel: Optional[l2cap.ClassicChannel]
def __init__(self, device: Device, connection: Connection) -> None:
self.device = device
@@ -898,8 +898,8 @@ class Client:
async def start(self) -> Multiplexer:
# Create a new L2CAP connection
try:
self.l2cap_channel = await self.device.l2cap_channel_manager.connect(
self.connection, RFCOMM_PSM
self.l2cap_channel = await self.connection.create_l2cap_channel(
spec=l2cap.ClassicChannelSpec(RFCOMM_PSM)
)
except ProtocolError as error:
logger.warning(f'L2CAP connection failed: {error}')
@@ -936,7 +936,9 @@ class Server(EventEmitter):
self.acceptors = {}
# Register ourselves with the L2CAP channel manager
device.register_l2cap_server(RFCOMM_PSM, self.on_connection)
device.create_l2cap_server(
spec=l2cap.ClassicChannelSpec(psm=RFCOMM_PSM), handler=self.on_connection
)
def listen(self, acceptor: Callable[[DLC], None], channel: int = 0) -> int:
if channel:
@@ -960,11 +962,11 @@ class Server(EventEmitter):
self.acceptors[channel] = acceptor
return channel
def on_connection(self, l2cap_channel: l2cap.Channel) -> None:
def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug(f'+++ new L2CAP connection: {l2cap_channel}')
l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
def on_l2cap_channel_open(self, l2cap_channel: l2cap.Channel) -> None:
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
# Create a new multiplexer for the channel

View File

@@ -167,7 +167,7 @@ class DataElement:
UUID: lambda x: DataElement(
DataElement.UUID, core.UUID.from_bytes(bytes(reversed(x)))
),
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x.decode('utf8')),
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x),
BOOLEAN: lambda x: DataElement(DataElement.BOOLEAN, x[0] == 1),
SEQUENCE: lambda x: DataElement(
DataElement.SEQUENCE, DataElement.list_from_bytes(x)
@@ -229,7 +229,7 @@ class DataElement:
return DataElement(DataElement.UUID, value)
@staticmethod
def text_string(value: str) -> DataElement:
def text_string(value: bytes) -> DataElement:
return DataElement(DataElement.TEXT_STRING, value)
@staticmethod
@@ -376,7 +376,7 @@ class DataElement:
raise ValueError('invalid value_size')
elif self.type == DataElement.UUID:
data = bytes(reversed(bytes(self.value)))
elif self.type in (DataElement.TEXT_STRING, DataElement.URL):
elif self.type == DataElement.URL:
data = self.value.encode('utf8')
elif self.type == DataElement.BOOLEAN:
data = bytes([1 if self.value else 0])
@@ -758,7 +758,7 @@ class SDP_ServiceSearchAttributeResponse(SDP_PDU):
# -----------------------------------------------------------------------------
class Client:
channel: Optional[l2cap.Channel]
channel: Optional[l2cap.ClassicChannel]
def __init__(self, device: Device) -> None:
self.device = device
@@ -766,8 +766,9 @@ class Client:
self.channel = None
async def connect(self, connection: Connection) -> None:
result = await self.device.l2cap_channel_manager.connect(connection, SDP_PSM)
self.channel = result
self.channel = await connection.create_l2cap_channel(
spec=l2cap.ClassicChannelSpec(SDP_PSM)
)
async def disconnect(self) -> None:
if self.channel:
@@ -921,7 +922,7 @@ class Client:
# -----------------------------------------------------------------------------
class Server:
CONTINUATION_STATE = bytes([0x01, 0x43])
channel: Optional[l2cap.Channel]
channel: Optional[l2cap.ClassicChannel]
Service = NewType('Service', List[ServiceAttribute])
service_records: Dict[int, Service]
current_response: Union[None, bytes, Tuple[int, List[int]]]
@@ -933,7 +934,9 @@ class Server:
self.current_response = None
def register(self, l2cap_channel_manager: l2cap.ChannelManager) -> None:
l2cap_channel_manager.register_server(SDP_PSM, self.on_connection)
l2cap_channel_manager.create_classic_server(
spec=l2cap.ClassicChannelSpec(psm=SDP_PSM), handler=self.on_connection
)
def send_response(self, response):
logger.debug(f'{color(">>> Sending SDP Response", "blue")}: {response}')

View File

@@ -37,6 +37,7 @@ from typing import (
Optional,
Tuple,
Type,
cast,
)
from pyee import EventEmitter
@@ -1771,7 +1772,26 @@ class Manager(EventEmitter):
cid = SMP_BR_CID if connection.transport == BT_BR_EDR_TRANSPORT else SMP_CID
connection.send_l2cap_pdu(cid, command.to_bytes())
def on_smp_security_request_command(
self, connection: Connection, request: SMP_Security_Request_Command
) -> None:
connection.emit('security_request', request.auth_req)
def on_smp_pdu(self, connection: Connection, pdu: bytes) -> None:
# Parse the L2CAP payload into an SMP Command object
command = SMP_Command.from_bytes(pdu)
logger.debug(
f'<<< Received SMP Command on connection [0x{connection.handle:04X}] '
f'{connection.peer_address}: {command}'
)
# Security request is more than just pairing, so let applications handle them
if command.code == SMP_SECURITY_REQUEST_COMMAND:
self.on_smp_security_request_command(
connection, cast(SMP_Security_Request_Command, command)
)
return
# Look for a session with this connection, and create one if none exists
if not (session := self.sessions.get(connection.handle)):
if connection.role == BT_CENTRAL_ROLE:
@@ -1782,13 +1802,6 @@ class Manager(EventEmitter):
)
self.sessions[connection.handle] = session
# Parse the L2CAP payload into an SMP Command object
command = SMP_Command.from_bytes(pdu)
logger.debug(
f'<<< Received SMP Command on connection [0x{connection.handle:04X}] '
f'{connection.peer_address}: {command}'
)
# Delegate the handling of the command to the session
session.on_smp_command(command)

View File

@@ -18,6 +18,8 @@
import logging
import grpc.aio
from typing import Optional, Union
from .common import PumpedTransport, PumpedPacketSource, PumpedPacketSink, Transport
# pylint: disable=no-name-in-module
@@ -33,7 +35,7 @@ logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
async def open_android_emulator_transport(spec: str | None) -> Transport:
async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
'''
Open a transport connection to an Android emulator via its gRPC interface.
The parameter string has this syntax:
@@ -82,7 +84,7 @@ async def open_android_emulator_transport(spec: str | None) -> Transport:
logger.debug(f'connecting to gRPC server at {server_address}')
channel = grpc.aio.insecure_channel(server_address)
service: EmulatedBluetoothServiceStub | VhciForwardingServiceStub
service: Union[EmulatedBluetoothServiceStub, VhciForwardingServiceStub]
if mode == 'host':
# Connect as a host
service = EmulatedBluetoothServiceStub(channel)
@@ -95,10 +97,13 @@ async def open_android_emulator_transport(spec: str | None) -> Transport:
raise ValueError('invalid mode')
# Create the transport object
transport = PumpedTransport(
PumpedPacketSource(hci_device.read),
PumpedPacketSink(hci_device.write),
channel.close,
class EmulatorTransport(PumpedTransport):
async def close(self):
await super().close()
await channel.close()
transport = EmulatorTransport(
PumpedPacketSource(hci_device.read), PumpedPacketSink(hci_device.write)
)
transport.start()

View File

@@ -18,11 +18,12 @@
import asyncio
import atexit
import logging
import grpc.aio
import os
import pathlib
import sys
from typing import Optional
from typing import Dict, Optional
import grpc.aio
from .common import (
ParserSource,
@@ -33,8 +34,8 @@ from .common import (
)
# pylint: disable=no-name-in-module
from .grpc_protobuf.packet_streamer_pb2_grpc import PacketStreamerStub
from .grpc_protobuf.packet_streamer_pb2_grpc import (
PacketStreamerStub,
PacketStreamerServicer,
add_PacketStreamerServicer_to_server,
)
@@ -43,6 +44,7 @@ from .grpc_protobuf.hci_packet_pb2 import HCIPacket
from .grpc_protobuf.startup_pb2 import Chip, ChipInfo
from .grpc_protobuf.common_pb2 import ChipKind
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
@@ -74,14 +76,20 @@ def get_ini_dir() -> Optional[pathlib.Path]:
# -----------------------------------------------------------------------------
def find_grpc_port() -> int:
def ini_file_name(instance_number: int) -> str:
suffix = f'_{instance_number}' if instance_number > 0 else ''
return f'netsim{suffix}.ini'
# -----------------------------------------------------------------------------
def find_grpc_port(instance_number: int) -> int:
if not (ini_dir := get_ini_dir()):
logger.debug('no known directory for .ini file')
return 0
ini_file = ini_dir / 'netsim.ini'
ini_file = ini_dir / ini_file_name(instance_number)
logger.debug(f'Looking for .ini file at {ini_file}')
if ini_file.is_file():
logger.debug(f'Found .ini file at {ini_file}')
with open(ini_file, 'r') as ini_file_data:
for line in ini_file_data.readlines():
if '=' in line:
@@ -90,12 +98,14 @@ def find_grpc_port() -> int:
logger.debug(f'gRPC port = {value}')
return int(value)
logger.debug('no grpc.port property found in .ini file')
# Not found
return 0
# -----------------------------------------------------------------------------
def publish_grpc_port(grpc_port) -> bool:
def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
if not (ini_dir := get_ini_dir()):
logger.debug('no known directory for .ini file')
return False
@@ -104,7 +114,7 @@ def publish_grpc_port(grpc_port) -> bool:
logger.debug('ini directory does not exist')
return False
ini_file = ini_dir / 'netsim.ini'
ini_file = ini_dir / ini_file_name(instance_number)
try:
ini_file.write_text(f'grpc.port={grpc_port}\n')
logger.debug(f"published gRPC port at {ini_file}")
@@ -122,14 +132,15 @@ def publish_grpc_port(grpc_port) -> bool:
# -----------------------------------------------------------------------------
async def open_android_netsim_controller_transport(
server_host: str | None, server_port: int
server_host: Optional[str], server_port: int, options: Dict[str, str]
) -> Transport:
if not server_port:
raise ValueError('invalid port')
if server_host == '_' or not server_host:
server_host = 'localhost'
if not publish_grpc_port(server_port):
instance_number = int(options.get('instance', "0"))
if not publish_grpc_port(server_port, instance_number):
logger.warning("unable to publish gRPC port")
class HciDevice:
@@ -186,15 +197,12 @@ async def open_android_netsim_controller_transport(
logger.debug(f'<<< PACKET: {data.hex()}')
self.on_data_received(data)
def send_packet(self, data):
async def send():
await self.context.write(
PacketResponse(
hci_packet=HCIPacket(packet_type=data[0], packet=data[1:])
)
async def send_packet(self, data):
return await self.context.write(
PacketResponse(
hci_packet=HCIPacket(packet_type=data[0], packet=data[1:])
)
self.loop.create_task(send())
)
def terminate(self):
self.task.cancel()
@@ -228,17 +236,17 @@ async def open_android_netsim_controller_transport(
logger.debug('gRPC server cancelled')
await self.grpc_server.stop(None)
def on_packet(self, packet):
async def send_packet(self, packet):
if not self.device:
logger.debug('no device, dropping packet')
return
self.device.send_packet(packet)
return await self.device.send_packet(packet)
async def StreamPackets(self, _request_iterator, context):
logger.debug('StreamPackets request')
# Check that we won't already have a device
# Check that we don't already have a device
if self.device:
logger.debug('busy, already serving a device')
return PacketResponse(error='Busy')
@@ -261,15 +269,42 @@ async def open_android_netsim_controller_transport(
await server.start()
asyncio.get_running_loop().create_task(server.serve())
class GrpcServerTransport(Transport):
async def close(self):
await super().close()
return GrpcServerTransport(server, server)
sink = PumpedPacketSink(server.send_packet)
sink.start()
return Transport(server, sink)
# -----------------------------------------------------------------------------
async def open_android_netsim_host_transport(server_host, server_port, options):
async def open_android_netsim_host_transport_with_address(
server_host: Optional[str],
server_port: int,
options: Optional[Dict[str, str]] = None,
):
if server_host == '_' or not server_host:
server_host = 'localhost'
if not server_port:
# Look for the gRPC config in a .ini file
instance_number = 0 if options is None else int(options.get('instance', '0'))
server_port = find_grpc_port(instance_number)
if not server_port:
raise RuntimeError('gRPC server port not found')
# Connect to the gRPC server
server_address = f'{server_host}:{server_port}'
logger.debug(f'Connecting to gRPC server at {server_address}')
channel = grpc.aio.insecure_channel(server_address)
return await open_android_netsim_host_transport_with_channel(
channel,
options,
)
# -----------------------------------------------------------------------------
async def open_android_netsim_host_transport_with_channel(
channel, options: Optional[Dict[str, str]] = None
):
# Wrapper for I/O operations
class HciDevice:
def __init__(self, name, manufacturer, hci_device):
@@ -288,10 +323,12 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
async def read(self):
response = await self.hci_device.read()
response_type = response.WhichOneof('response_type')
if response_type == 'error':
logger.warning(f'received error: {response.error}')
raise RuntimeError(response.error)
elif response_type == 'hci_packet':
if response_type == 'hci_packet':
return (
bytes([response.hci_packet.packet_type])
+ response.hci_packet.packet
@@ -306,24 +343,9 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
)
)
name = options.get('name', DEFAULT_NAME)
name = DEFAULT_NAME if options is None else options.get('name', DEFAULT_NAME)
manufacturer = DEFAULT_MANUFACTURER
if server_host == '_' or not server_host:
server_host = 'localhost'
if not server_port:
# Look for the gRPC config in a .ini file
server_host = 'localhost'
server_port = find_grpc_port()
if not server_port:
raise RuntimeError('gRPC server port not found')
# Connect to the gRPC server
server_address = f'{server_host}:{server_port}'
logger.debug(f'Connecting to gRPC server at {server_address}')
channel = grpc.aio.insecure_channel(server_address)
# Connect as a host
service = PacketStreamerStub(channel)
hci_device = HciDevice(
@@ -334,10 +356,14 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
await hci_device.start()
# Create the transport object
transport = PumpedTransport(
class GrpcTransport(PumpedTransport):
async def close(self):
await super().close()
await channel.close()
transport = GrpcTransport(
PumpedPacketSource(hci_device.read),
PumpedPacketSink(hci_device.write),
channel.close,
)
transport.start()
@@ -345,7 +371,7 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
# -----------------------------------------------------------------------------
async def open_android_netsim_transport(spec):
async def open_android_netsim_transport(spec: Optional[str]) -> Transport:
'''
Open a transport connection as a client or server, implementing Android's `netsim`
simulator protocol over gRPC.
@@ -359,6 +385,11 @@ async def open_android_netsim_transport(spec):
to connect *to* a netsim server (netsim is the controller), or accept
connections *as* a netsim-compatible server.
instance=<n>
Specifies an instance number, with <n> > 0. This is used to determine which
.init file to use. In `host` mode, it is ignored when the <host>:<port>
specifier is present, since in that case no .ini file is used.
In `host` mode:
The <host>:<port> part is optional. When not specified, the transport
looks for a netsim .ini file, from which it will read the `grpc.backend.port`
@@ -387,14 +418,15 @@ async def open_android_netsim_transport(spec):
params = spec.split(',') if spec else []
if params and ':' in params[0]:
# Explicit <host>:<port>
host, port = params[0].split(':')
host, port_str = params[0].split(':')
port = int(port_str)
params_offset = 1
else:
host = None
port = 0
params_offset = 0
options = {}
options: Dict[str, str] = {}
for param in params[params_offset:]:
if '=' not in param:
raise ValueError('invalid parameter, expected <name>=<value>')
@@ -403,10 +435,12 @@ async def open_android_netsim_transport(spec):
mode = options.get('mode', 'host')
if mode == 'host':
return await open_android_netsim_host_transport(host, port, options)
return await open_android_netsim_host_transport_with_address(
host, port, options
)
if mode == 'controller':
if host is None:
raise ValueError('<host>:<port> missing')
return await open_android_netsim_controller_transport(host, port)
return await open_android_netsim_controller_transport(host, port, options)
raise ValueError('invalid mode option')

View File

@@ -339,8 +339,9 @@ class PumpedPacketSource(ParserSource):
try:
packet = await self.receive_function()
self.parser.feed_data(packet)
except asyncio.exceptions.CancelledError:
except asyncio.CancelledError:
logger.debug('source pump task done')
self.terminated.set_result(None)
break
except Exception as error:
logger.warning(f'exception while waiting for packet: {error}')
@@ -370,7 +371,7 @@ class PumpedPacketSink:
try:
packet = await self.packet_queue.get()
await self.send_function(packet)
except asyncio.exceptions.CancelledError:
except asyncio.CancelledError:
logger.debug('sink pump task done')
break
except Exception as error:
@@ -393,19 +394,13 @@ class PumpedTransport(Transport):
self,
source: PumpedPacketSource,
sink: PumpedPacketSink,
close_function,
) -> None:
super().__init__(source, sink)
self.close_function = close_function
def start(self) -> None:
self.source.start()
self.sink.start()
async def close(self) -> None:
await super().close()
await self.close_function()
# -----------------------------------------------------------------------------
class SnoopingTransport(Transport):

View File

@@ -23,6 +23,8 @@ import socket
import ctypes
import collections
from typing import Optional
from .common import Transport, ParserSource
@@ -33,7 +35,7 @@ logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
async def open_hci_socket_transport(spec: str | None) -> Transport:
async def open_hci_socket_transport(spec: Optional[str]) -> Transport:
'''
Open an HCI Socket (only available on some platforms).
The parameter string is either empty (to use the first/default Bluetooth adapter)
@@ -45,9 +47,9 @@ async def open_hci_socket_transport(spec: str | None) -> Transport:
# Create a raw HCI socket
try:
hci_socket = socket.socket(
socket.AF_BLUETOOTH,
socket.SOCK_RAW | socket.SOCK_NONBLOCK,
socket.BTPROTO_HCI, # type: ignore
socket.AF_BLUETOOTH, # type: ignore[attr-defined]
socket.SOCK_RAW | socket.SOCK_NONBLOCK, # type: ignore[attr-defined]
socket.BTPROTO_HCI, # type: ignore[attr-defined]
)
except AttributeError as error:
# Not supported on this platform
@@ -78,7 +80,7 @@ async def open_hci_socket_transport(spec: str | None) -> Transport:
bind_address = struct.pack(
# pylint: disable=no-member
'<HHH',
socket.AF_BLUETOOTH,
socket.AF_BLUETOOTH, # type: ignore[attr-defined]
adapter_index,
HCI_CHANNEL_USER,
)

View File

@@ -23,6 +23,8 @@ import atexit
import os
import logging
from typing import Optional
from .common import Transport, StreamPacketSource, StreamPacketSink
# -----------------------------------------------------------------------------
@@ -32,7 +34,7 @@ logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
async def open_pty_transport(spec: str | None) -> Transport:
async def open_pty_transport(spec: Optional[str]) -> Transport:
'''
Open a PTY transport.
The parameter string may be empty, or a path name where a symbolic link

View File

@@ -17,6 +17,8 @@
# -----------------------------------------------------------------------------
import logging
from typing import Optional
from .common import Transport
from .file import open_file_transport
@@ -27,7 +29,7 @@ logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
async def open_vhci_transport(spec: str | None) -> Transport:
async def open_vhci_transport(spec: Optional[str]) -> Transport:
'''
Open a VHCI transport (only available on some platforms).
The parameter string is either empty (to use the default VHCI device

View File

@@ -31,19 +31,21 @@ async def open_ws_client_transport(spec: str) -> Transport:
'''
Open a WebSocket client transport.
The parameter string has this syntax:
<remote-host>:<remote-port>
<websocket-url>
Example: 127.0.0.1:9001
Example: ws://localhost:7681/v1/websocket/bt
'''
remote_host, remote_port = spec.split(':')
uri = f'ws://{remote_host}:{remote_port}'
websocket = await websockets.client.connect(uri)
websocket = await websockets.client.connect(spec)
transport = PumpedTransport(
class WsTransport(PumpedTransport):
async def close(self):
await super().close()
await websocket.close()
transport = WsTransport(
PumpedPacketSource(websocket.recv),
PumpedPacketSink(websocket.send),
websocket.close,
)
transport.start()
return transport

View File

@@ -15,13 +15,26 @@
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
from __future__ import annotations
import asyncio
import logging
import traceback
import collections
import sys
from typing import Awaitable, Set, TypeVar
from functools import wraps
import warnings
from typing import (
Awaitable,
Set,
TypeVar,
List,
Tuple,
Callable,
Any,
Optional,
Union,
overload,
)
from functools import wraps, partial
from pyee import EventEmitter
from .colors import color
@@ -64,6 +77,102 @@ def composite_listener(cls):
return cls
# -----------------------------------------------------------------------------
_Handler = TypeVar('_Handler', bound=Callable)
class EventWatcher:
'''A wrapper class to control the lifecycle of event handlers better.
Usage:
```
watcher = EventWatcher()
def on_foo():
...
watcher.on(emitter, 'foo', on_foo)
@watcher.on(emitter, 'bar')
def on_bar():
...
# Close all event handlers watching through this watcher
watcher.close()
```
As context:
```
with contextlib.closing(EventWatcher()) as context:
@context.on(emitter, 'foo')
def on_foo():
...
# on_foo() has been removed here!
```
'''
handlers: List[Tuple[EventEmitter, str, Callable[..., Any]]]
def __init__(self) -> None:
self.handlers = []
@overload
def on(self, emitter: EventEmitter, event: str) -> Callable[[_Handler], _Handler]:
...
@overload
def on(self, emitter: EventEmitter, event: str, handler: _Handler) -> _Handler:
...
def on(
self, emitter: EventEmitter, event: str, handler: Optional[_Handler] = None
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
'''Watch an event until the context is closed.
Args:
emitter: EventEmitter to watch
event: Event name
handler: (Optional) Event handler. When nothing is passed, this method works as a decorator.
'''
def wrapper(f: _Handler) -> _Handler:
self.handlers.append((emitter, event, f))
emitter.on(event, f)
return f
return wrapper if handler is None else wrapper(handler)
@overload
def once(self, emitter: EventEmitter, event: str) -> Callable[[_Handler], _Handler]:
...
@overload
def once(self, emitter: EventEmitter, event: str, handler: _Handler) -> _Handler:
...
def once(
self, emitter: EventEmitter, event: str, handler: Optional[_Handler] = None
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
'''Watch an event for once.
Args:
emitter: EventEmitter to watch
event: Event name
handler: (Optional) Event handler. When nothing passed, this method works as a decorator.
'''
def wrapper(f: _Handler) -> _Handler:
self.handlers.append((emitter, event, f))
emitter.once(event, f)
return f
return wrapper if handler is None else wrapper(handler)
def close(self) -> None:
for emitter, event, handler in self.handlers:
if handler in emitter.listeners(event):
emitter.remove_listener(event, handler)
# -----------------------------------------------------------------------------
_T = TypeVar('_T')
@@ -302,3 +411,36 @@ class FlowControlAsyncPipe:
self.resume_source()
self.check_pump()
async def async_call(function, *args, **kwargs):
"""
Immediately calls the function with provided args and kwargs, wrapping it in an async function.
Rust's `pyo3_asyncio` library needs functions to be marked async to properly inject a running loop.
result = await async_call(some_function, ...)
"""
return function(*args, **kwargs)
def wrap_async(function):
"""
Wraps the provided function in an async function.
"""
return partial(async_call, function)
def deprecated(msg: str):
"""
Throw deprecation warning before execution
"""
def wrapper(function):
@wraps(function)
def inner(*args, **kwargs):
warnings.warn(msg, DeprecationWarning)
return function(*args, **kwargs)
return inner
return wrapper

View File

@@ -10,7 +10,7 @@ nav:
- Contributing: development/contributing.md
- Code Style: development/code_style.md
- Use Cases:
- Overview: use_cases/index.md
- use_cases/index.md
- Use Case 1: use_cases/use_case_1.md
- Use Case 2: use_cases/use_case_2.md
- Use Case 3: use_cases/use_case_3.md
@@ -23,7 +23,7 @@ nav:
- GATT: components/gatt.md
- Security Manager: components/security_manager.md
- Transports:
- Overview: transports/index.md
- transports/index.md
- Serial: transports/serial.md
- USB: transports/usb.md
- PTY: transports/pty.md
@@ -37,14 +37,14 @@ nav:
- Android Emulator: transports/android_emulator.md
- File: transports/file.md
- Drivers:
- Overview: drivers/index.md
- drivers/index.md
- Realtek: drivers/realtek.md
- API:
- Guide: api/guide.md
- Examples: api/examples.md
- Reference: api/reference.md
- Apps & Tools:
- Overview: apps_and_tools/index.md
- apps_and_tools/index.md
- Console: apps_and_tools/console.md
- Bench: apps_and_tools/bench.md
- Speaker: apps_and_tools/speaker.md
@@ -57,16 +57,24 @@ nav:
- USB Probe: apps_and_tools/usb_probe.md
- Link Relay: apps_and_tools/link_relay.md
- Hardware:
- Overview: hardware/index.md
- hardware/index.md
- Platforms:
- Overview: platforms/index.md
- platforms/index.md
- macOS: platforms/macos.md
- Linux: platforms/linux.md
- Windows: platforms/windows.md
- Android: platforms/android.md
- Zephyr: platforms/zephyr.md
- Examples:
- Overview: examples/index.md
- examples/index.md
- Extras:
- extras/index.md
- Android Remote HCI: extras/android_remote_hci.md
- Hive:
- hive/index.md
- Speaker: hive/web/speaker/speaker.html
- Scanner: hive/web/scanner/scanner.html
- Heart Rate Monitor: hive/web/heart_rate_monitor/heart_rate_monitor.html
copyright: Copyright 2021-2023 Google LLC
@@ -75,6 +83,8 @@ theme:
logo: 'images/logo.png'
favicon: 'images/favicon.ico'
custom_dir: 'theme'
features:
- navigation.indexes
plugins:
- mkdocstrings:
@@ -99,6 +109,8 @@ markdown_extensions:
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
- pymdownx.tabbed:
alternate_style: true
- codehilite:
guess_lang: false
- toc:

View File

@@ -0,0 +1,141 @@
ANDROID REMOTE HCI APP
======================
This application allows using an android phone's built-in Bluetooth controller with
a Bumble host stack running outside the phone (typically a development laptop or desktop).
The app runs an HCI proxy between a TCP socket on the "outside" and the Bluetooth HCI HAL
on the "inside". (See [this page](https://source.android.com/docs/core/connect/bluetooth) for a high level
description of the Android Bluetooth HCI HAL).
The HCI packets received on the TCP socket are forwarded to the phone's controller, and the
packets coming from the controller are forwarded to the TCP socket.
Building
--------
You can build the app by running `./gradlew build` (use `gradlew.bat` on Windows) from the `RemoteHCI` top level directory.
You can also build with Android Studio: open the `RemoteHCI` project. You can build and/or debug from there.
If the build succeeds, you can find the app APKs (debug and release) at:
* [Release] ``app/build/outputs/apk/release/app-release-unsigned.apk``
* [Debug] ``app/build/outputs/apk/debug/app-debug.apk``
Running
-------
### Preconditions
When the proxy starts (tapping the "Start" button in the app's main activity), it will try to
bind to the Bluetooth HAL. This requires disabling SELinux temporarily, and being the only HAL client.
#### Disabling SELinux
Binding to the Bluetooth HCI HAL requires certain SELinux permissions that can't simply be changed
on a device without rebuilding its system image. To bypass these restrictions, you will need
to disable SELinux on your phone (please be aware that this is global, not just for the proxy app,
so proceed with caution).
In order to disable SELinux, you need to root the phone (it may be advisable to do this on a
development phone).
!!! tip "Disabling SELinux Temporarily"
Restart `adb` as root:
```bash
$ adb root
```
Then disable SELinux
```bash
$ adb shell setenforce 0
```
Once you're done using the proxy, you can restore SELinux, if you need to, with
```bash
$ adb shell setenforce 1
```
This state will also reset to the normal SELinux enforcement when you reboot.
#### Stopping the bluetooth process
Since the Bluetooth HAL service can only accept one client, and that in normal conditions
that client is the Android's bluetooth stack, it is required to first shut down the
Android bluetooth stack process.
!!! tip "Checking if the Bluetooth process is running"
```bash
$ adb shell "ps -A | grep com.google.android.bluetooth"
```
If the process is running, you will get a line like:
```
bluetooth 10759 876 17455796 136620 do_epoll_wait 0 S com.google.android.bluetooth
```
If you don't, it means that the process is not running and you are clear to proceed.
Simply turning Bluetooth off from the phone's settings does not ensure that the bluetooth process will exit.
If the bluetooth process is still running after toggling Bluetooth off from the settings, you may try enabling
Airplane Mode, then rebooting. The bluetooth process should, in theory, not restart after the reboot.
!!! tip "Stopping the bluetooth process with adb"
```bash
$ adb shell cmd bluetooth_manager disable
```
### Starting the app
You can start the app from the Android launcher, from Android Studio, or with `adb`
#### Launching from the launcher
Just tap the app icon on the launcher, check the TCP port that is configured, and tap
the "Start" button.
#### Launching with `adb`
Using the `am` command, you can start the activity, and pass it arguments so that you can
automatically start the proxy, and/or set the port number.
!!! tip "Launching from adb with auto-start"
```bash
$ adb shell am start -n com.github.google.bumble.remotehci/.MainActivity --ez autostart true
```
!!! tip "Launching from adb with auto-start and a port"
In this example, we auto-start the proxy upon launch, with the port set to 9995
```bash
$ adb shell am start -n com.github.google.bumble.remotehci/.MainActivity --ez autostart true --ei port 9995
```
#### Selecting a TCP port
The RemoteHCI app's main activity has a "TCP Port" setting where you can change the port on
which the proxy is accepting connections. If the default value isn't suitable, you can
change it there (you can also use the special value 0 to let the OS assign a port number for you).
### Connecting to the proxy
To connect the Bumble stack to the proxy, you need to be able to reach the phone's network
stack. This can be done over the phone's WiFi connection, or, alternatively, using an `adb`
TCP forward (which should be faster than over WiFi).
!!! tip "Forwarding TCP with `adb`"
To connect to the proxy via an `adb` TCP forward, use:
```bash
$ adb forward tcp:<outside-port> tcp:<inside-port>
```
Where ``<outside-port>`` is the port number for a listening socket on your laptop or
desktop machine, and <inside-port> is the TCP port selected in the app's user interface.
Those two ports may be the same, of course.
For example, with the default TCP port 9993:
```bash
$ adb forward tcp:9993 tcp:9993
```
Once you've ensured that you can reach the proxy's TCP port on the phone, either directly or
via an `adb` forward, you can then use it as a Bumble transport, using the transport name:
``tcp-client:<host>:<port>`` syntax.
!!! example "Connecting a Bumble client"
Connecting the `bumble-controller-info` app to the phone's controller.
Assuming you have set up an `adb` forward on port 9993:
```bash
$ bumble-controller-info tcp-client:localhost:9993
```
Or over WiFi with, in this example, the IP address of the phone being ```192.168.86.27```
```bash
$ bumble-controller-info tcp-client:192.168.86.27:9993
```

View File

@@ -0,0 +1,11 @@
EXTRAS
======
A collection of add-ons, apps and tools, to the Bumble project.
Android Remote HCI
------------------
Allows using an Android phone's built-in Bluetooth controller with a Bumble
stack running on a development machine.
See [Android Remote HCI](android_remote_hci.md) for details.

View File

@@ -3,7 +3,7 @@ HARDWARE
The Bumble Host connects to a controller over an [HCI Transport](../transports/index.md).
To use a hardware controller attached to the host on which the host application is running, the transport is typically either [HCI over UART](../transports/serial.md) or [HCI over USB](../transports/usb.md).
On Linux, the [VHCI Transport](../transports/vhci.md) can be used to communicate with any controller hardware managed by the operating system. Alternatively, a remote controller (a phyiscal controller attached to a remote host) can be used by connecting one of the networked transports (such as the [TCP Client transport](../transports/tcp_client.md), the [TCP Server transport](../transports/tcp_server.md) or the [UDP Transport](../transports/udp.md)) to an [HCI Bridge](../apps_and_tools/hci_bridge) bridging the network transport to a physical controller on a remote host.
On Linux, the [VHCI Transport](../transports/vhci.md) can be used to communicate with any controller hardware managed by the operating system. Alternatively, a remote controller (a phyiscal controller attached to a remote host) can be used by connecting one of the networked transports (such as the [TCP Client transport](../transports/tcp_client.md), the [TCP Server transport](../transports/tcp_server.md) or the [UDP Transport](../transports/udp.md)) to an [HCI Bridge](../apps_and_tools/hci_bridge.md) bridging the network transport to a physical controller on a remote host.
In theory, any controller that is compliant with the HCI over UART or HCI over USB protocols can be used.

View File

@@ -0,0 +1,59 @@
HIVE
====
Welcome to the Bumble Hive.
This is a collection of apps and virtual devices that can run entirely in a browser page.
The code for the apps and devices, as well as the Bumble runtime code, runs via [Pyodide](https://pyodide.org/).
Pyodide is a Python distribution for the browser and Node.js based on WebAssembly.
The Bumble stack uses a WebSocket to exchange HCI packets with a virtual or physical
Bluetooth controller.
The apps and devices in the hive can be accessed by following the links below. Each
page has a settings button that may be used to configure the WebSocket URL to use for
the virtual HCI connection. This will typically be the WebSocket URL for a `netsim`
daemon.
There is also a [TOML index](index.toml) that can be used by tools to know at which URL to access
each of the apps and devices, as well as their names and short descriptions.
!!! tip "Using `netsim`"
When the `netsimd` daemon is running (for example when using the Android Emulator that
is included in Android Studio), the daemon listens for connections on a TCP port.
To find out what this TCP port is, you can read the `netsim.ini` file that `netsimd`
creates, it includes a line with `web.port=<tcp-port>` (for example `web.port=7681`).
The location of the `netsim.ini` file is platform-specific.
=== "macOS"
On macOS, the directory where `netsim.ini` is stored is $TMPDIR
```bash
$ cat $TMPDIR/netsim.ini
```
=== "Linux"
On Linux, the directory where `netsim.ini` is stored is $XDG_RUNTIME_DIR
```bash
$ cat $XDG_RUNTIME_DIR/netsim.ini
```
!!! tip "Using a local radio"
You can connect the hive virtual apps and devices to a local Bluetooth radio, like,
for example, a USB dongle.
For that, you need to run a local HCI bridge to bridge a local HCI device to a WebSocket
that a web page can connect to.
Use the `bumble-hci-bridge` app, with the host transport set to a WebSocket server on an
available port (ex: `ws-server:_:7682`) and the controller transport set to the transport
name for the radio you want to use (ex: `usb:0` for the first USB dongle)
Applications
------------
* [Scanner](web/scanner/scanner.html) - Scans for BLE devices.
Virtual Devices
---------------
* [Speaker](web/speaker/speaker.html) - Virtual speaker that plays audio in a browser page.
* [Heart Rate Monitor](web/heart_rate_monitor/heart_rate_monitor.html) - Virtual heart rate monitor.

View File

@@ -0,0 +1,21 @@
version = "1.0.0"
base_url = "https://google.github.io/bumble/hive/web"
default_hci_query_param = "hci"
[[index]]
name = "speaker"
description = "Bumble Virtual Speaker"
type = "Device"
url = "speaker/speaker.html"
[[index]]
name = "scanner"
description = "Simple Scanner Application"
type = "Application"
url = "scanner/scanner.html"
[[index]]
name = "heart-rate-monitor"
description = "Virtual Heart Rate Monitor"
type = "Device"
url = "heart_rate_monitor/heart_rate_monitor.html"

View File

@@ -0,0 +1 @@
../../../../../web/bumble.js

View File

@@ -0,0 +1 @@
../../../../../../web/heart_rate_monitor/heart_rate_monitor.html

View File

@@ -0,0 +1 @@
../../../../../../web/heart_rate_monitor/heart_rate_monitor.js

View File

@@ -0,0 +1 @@
../../../../../../web/heart_rate_monitor/heart_rate_monitor.py

View File

@@ -0,0 +1 @@
../../../../../../web/scanner/scanner.css

View File

@@ -0,0 +1 @@
../../../../../../web/scanner/scanner.html

View File

@@ -0,0 +1 @@
../../../../../../web/scanner/scanner.js

View File

@@ -0,0 +1 @@
../../../../../../web/scanner/scanner.py

View File

@@ -0,0 +1 @@
../../../../../../web/speaker/logo.svg

View File

@@ -0,0 +1 @@
../../../../../../web/speaker/speaker.css

View File

@@ -0,0 +1 @@
../../../../../../web/speaker/speaker.html

View File

@@ -0,0 +1 @@
../../../../../../web/speaker/speaker.js

View File

@@ -0,0 +1 @@
../../../../../../web/speaker/speaker.py

View File

@@ -0,0 +1 @@
../../../../../web/ui.js

View File

@@ -152,11 +152,23 @@ Some platforms support features that not all platforms support
See the [Platforms page](platforms/index.md) for details.
Hive
----
The Hive is a collection of example apps and virtual devices that are implemented using the
Python Bumble API, running entirely in a web page. This is a convenient way to try out some
of the examples without any Python installation, when you have some other virtual Bluetooth
device that you can connect to or from, such as the Android Emulator.
See the [Bumble Hive](hive/index.md) for details.
Roadmap
-------
Future features to be considered include:
* More profiles
* More device examples
* Add a new type of virtual link (beyond the two existing ones) to allow for link-level simulation (timing, loss, etc)
* Bindings for languages other than Python

View File

@@ -14,7 +14,7 @@ connections.
## Moniker
The moniker syntax for an Android Emulator "netsim" transport is: `android-netsim:[<host>:<port>][<options>]`,
where `<options>` is a ','-separated list of `<name>=<value>` pairs`.
where `<options>` is a comma-separated list of `<name>=<value>` pairs.
The `mode` parameter name can specify running as a host or a controller, and `<hostname>:<port>` can specify a host name (or IP address) and TCP port number on which to reach the gRPC server for the emulator (in "host" mode), or to accept gRPC connections (in "controller" mode).
Both the `mode=<host|controller>` and `<hostname>:<port>` parameters are optional (so the moniker `android-netsim` by itself is a valid moniker, which will create a transport in `host` mode, connected to `localhost` on the default gRPC port for the Netsim background process).

View File

@@ -29,6 +29,7 @@ from bumble.device import Device
from bumble.transport import open_transport_or_link
from bumble.profiles.device_information_service import DeviceInformationService
from bumble.profiles.heart_rate_service import HeartRateService
from bumble.utils import AsyncRunner
# -----------------------------------------------------------------------------
@@ -98,6 +99,17 @@ async def main():
)
)
# Notify subscribers of the current value as soon as they subscribe
@heart_rate_service.heart_rate_measurement_characteristic.on('subscription')
def on_subscription(connection, notify_enabled, indicate_enabled):
if notify_enabled or indicate_enabled:
AsyncRunner.spawn(
device.notify_subscriber(
connection,
heart_rate_service.heart_rate_measurement_characteristic,
)
)
# Go!
await device.power_on()
await device.start_advertising(auto_restart=True)

248
examples/hid_key_map.py Normal file
View File

@@ -0,0 +1,248 @@
# shift map
# letters
shift_map = {
'a': 'A',
'b': 'B',
'c': 'C',
'd': 'D',
'e': 'E',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J',
'k': 'K',
'l': 'L',
'm': 'M',
'n': 'N',
'o': 'O',
'p': 'P',
'q': 'Q',
'r': 'R',
's': 'S',
't': 'T',
'u': 'U',
'v': 'V',
'w': 'W',
'x': 'X',
'y': 'Y',
'z': 'Z',
# numbers
'1': '!',
'2': '@',
'3': '#',
'4': '$',
'5': '%',
'6': '^',
'7': '&',
'8': '*',
'9': '(',
'0': ')',
# symbols
'-': '_',
'=': '+',
'[': '{',
']': '}',
'\\': '|',
';': ':',
'\'': '"',
',': '<',
'.': '>',
'/': '?',
'`': '~',
}
# hex map
# modifier keys
mod_keys = {
'00': '',
'01': 'left_ctrl',
'02': 'left_shift',
'04': 'left_alt',
'08': 'left_meta',
'10': 'right_ctrl',
'20': 'right_shift',
'40': 'right_alt',
'80': 'right_meta',
}
# base keys
base_keys = {
# meta
'00': '', # none
'01': 'error_ovf',
# letters
'04': 'a',
'05': 'b',
'06': 'c',
'07': 'd',
'08': 'e',
'09': 'f',
'0a': 'g',
'0b': 'h',
'0c': 'i',
'0d': 'j',
'0e': 'k',
'0f': 'l',
'10': 'm',
'11': 'n',
'12': 'o',
'13': 'p',
'14': 'q',
'15': 'r',
'16': 's',
'17': 't',
'18': 'u',
'19': 'v',
'1a': 'w',
'1b': 'x',
'1c': 'y',
'1d': 'z',
# numbers
'1e': '1',
'1f': '2',
'20': '3',
'21': '4',
'22': '5',
'23': '6',
'24': '7',
'25': '8',
'26': '9',
'27': '0',
# misc
'28': 'enter', # enter \n
'29': 'esc',
'2a': 'backspace',
'2b': 'tab',
'2c': 'spacebar', # space
'2d': '-',
'2e': '=',
'2f': '[',
'30': ']',
'31': '\\',
'32': '=',
'33': '_SEMICOLON',
'34': 'KEY_APOSTROPHE',
'35': 'KEY_GRAVE',
'36': 'KEY_COMMA',
'37': 'KEY_DOT',
'38': 'KEY_SLASH',
'39': 'KEY_CAPSLOCK',
'3a': 'KEY_F1',
'3b': 'KEY_F2',
'3c': 'KEY_F3',
'3d': 'KEY_F4',
'3e': 'KEY_F5',
'3f': 'KEY_F6',
'40': 'KEY_F7',
'41': 'KEY_F8',
'42': 'KEY_F9',
'43': 'KEY_F10',
'44': 'KEY_F11',
'45': 'KEY_F12',
'46': 'KEY_SYSRQ',
'47': 'KEY_SCROLLLOCK',
'48': 'KEY_PAUSE',
'49': 'KEY_INSERT',
'4a': 'KEY_HOME',
'4b': 'KEY_PAGEUP',
'4c': 'KEY_DELETE',
'4d': 'KEY_END',
'4e': 'KEY_PAGEDOWN',
'4f': 'KEY_RIGHT',
'50': 'KEY_LEFT',
'51': 'KEY_DOWN',
'52': 'KEY_UP',
'53': 'KEY_NUMLOCK',
'54': 'KEY_KPSLASH',
'55': 'KEY_KPASTERISK',
'56': 'KEY_KPMINUS',
'57': 'KEY_KPPLUS',
'58': 'KEY_KPENTER',
'59': 'KEY_KP1',
'5a': 'KEY_KP2',
'5b': 'KEY_KP3',
'5c': 'KEY_KP4',
'5d': 'KEY_KP5',
'5e': 'KEY_KP6',
'5f': 'KEY_KP7',
'60': 'KEY_KP8',
'61': 'KEY_KP9',
'62': 'KEY_KP0',
'63': 'KEY_KPDOT',
'64': 'KEY_102ND',
'65': 'KEY_COMPOSE',
'66': 'KEY_POWER',
'67': 'KEY_KPEQUAL',
'68': 'KEY_F13',
'69': 'KEY_F14',
'6a': 'KEY_F15',
'6b': 'KEY_F16',
'6c': 'KEY_F17',
'6d': 'KEY_F18',
'6e': 'KEY_F19',
'6f': 'KEY_F20',
'70': 'KEY_F21',
'71': 'KEY_F22',
'72': 'KEY_F23',
'73': 'KEY_F24',
'74': 'KEY_OPEN',
'75': 'KEY_HELP',
'76': 'KEY_PROPS',
'77': 'KEY_FRONT',
'78': 'KEY_STOP',
'79': 'KEY_AGAIN',
'7a': 'KEY_UNDO',
'7b': 'KEY_CUT',
'7c': 'KEY_COPY',
'7d': 'KEY_PASTE',
'7e': 'KEY_FIND',
'7f': 'KEY_MUTE',
'80': 'KEY_VOLUMEUP',
'81': 'KEY_VOLUMEDOWN',
'85': 'KEY_KPCOMMA',
'87': 'KEY_RO',
'88': 'KEY_KATAKANAHIRAGANA',
'89': 'KEY_YEN',
'8a': 'KEY_HENKAN',
'8b': 'KEY_MUHENKAN',
'8c': 'KEY_KPJPCOMMA',
'90': 'KEY_HANGEUL',
'91': 'KEY_HANJA',
'92': 'KEY_KATAKANA',
'93': 'KEY_HIRAGANA',
'94': 'KEY_ZENKAKUHANKAKU',
'b6': 'KEY_KPLEFTPAREN',
'b7': 'KEY_KPRIGHTPAREN',
'e0': 'KEY_LEFTCTRL',
'e1': 'KEY_LEFTSHIFT',
'e2': 'KEY_LEFTALT',
'e3': 'KEY_LEFTMETA',
'e4': 'KEY_RIGHTCTRL',
'e5': 'KEY_RIGHTSHIFT',
'e6': 'KEY_RIGHTALT',
'e7': 'KEY_RIGHTMETA',
'e8': 'KEY_MEDIA_PLAYPAUSE',
'e9': 'KEY_MEDIA_STOPCD',
'ea': 'KEY_MEDIA_PREVIOUSSONG',
'eb': 'KEY_MEDIA_NEXTSONG',
'ec': 'KEY_MEDIA_EJECTCD',
'ed': 'KEY_MEDIA_VOLUMEUP',
'ee': 'KEY_MEDIA_VOLUMEDOWN',
'ef': 'KEY_MEDIA_MUTE',
'f0': 'KEY_MEDIA_WWW',
'f1': 'KEY_MEDIA_BACK',
'f2': 'KEY_MEDIA_FORWARD',
'f3': 'KEY_MEDIA_STOP',
'f4': 'KEY_MEDIA_FIND',
'f5': 'KEY_MEDIA_SCROLLUP',
'f6': 'KEY_MEDIA_SCROLLDOWN',
'f7': 'KEY_MEDIA_EDIT',
'f8': 'KEY_MEDIA_SLEEP',
'f9': 'KEY_MEDIA_COFFEE',
'fa': 'KEY_MEDIA_REFRESH',
'fb': 'KEY_MEDIA_CALC',
}

View File

@@ -0,0 +1,159 @@
from bumble.colors import color
from hid_key_map import base_keys, mod_keys, shift_map
# ------------------------------------------------------------------------------
def get_key(modifier: str, key: str) -> str:
if modifier == '22':
modifier = '02'
if modifier in mod_keys:
modifier = mod_keys[modifier]
else:
return ''
if key in base_keys:
key = base_keys[key]
else:
return ''
if (modifier == 'left_shift' or modifier == 'right_shift') and key in shift_map:
key = shift_map[key]
return key
class Keyboard:
def __init__(self): # type: ignore
self.report = [
[ # Bit array for Modifier keys
0, # Right GUI - (usually the Windows key)
0, # Right ALT
0, # Right Shift
0, # Right Control
0, # Left GUI - (usually the Windows key)
0, # Left ALT
0, # Left Shift
0, # Left Control
],
0x00, # Vendor reserved
'', # Rest is space for 6 keys
'',
'',
'',
'',
'',
]
def decode_keyboard_report(self, input_report: bytes, report_length: int) -> None:
if report_length >= 8:
modifier = input_report[1]
self.report[0] = [int(x) for x in '{0:08b}'.format(modifier)]
self.report[0].reverse() # type: ignore
modifier_key = str((modifier & 0x22).to_bytes(1, "big").hex())
keycodes = []
for k in range(3, report_length):
keycodes.append(str(input_report[k].to_bytes(1, "big").hex()))
self.report[k - 1] = get_key(modifier_key, keycodes[k - 3])
else:
print(color('Warning: Not able to parse report', 'yellow'))
def print_keyboard_report(self) -> None:
print(color('\tKeyboard Input Received', 'green', None, 'bold'))
print(color(f'Keys:', 'white', None, 'bold'))
for i in range(1, 7):
print(
color(f' Key{i}{" ":>8s}= ', 'cyan', None, 'bold'), self.report[i + 1]
)
print(color(f'\nModifier Keys:', 'white', None, 'bold'))
print(
color(f' Left Ctrl : ', 'cyan'),
f'{self.report[0][0] == 1!s:<5}', # type: ignore
color(f' Left Shift : ', 'cyan'),
f'{self.report[0][1] == 1!s:<5}', # type: ignore
color(f' Left ALT : ', 'cyan'),
f'{self.report[0][2] == 1!s:<5}', # type: ignore
color(f' Left GUI : ', 'cyan'),
f'{self.report[0][3] == 1!s:<5}\n', # type: ignore
color(f' Right Ctrl : ', 'cyan'),
f'{self.report[0][4] == 1!s:<5}', # type: ignore
color(f' Right Shift : ', 'cyan'),
f'{self.report[0][5] == 1!s:<5}', # type: ignore
color(f' Right ALT : ', 'cyan'),
f'{self.report[0][6] == 1!s:<5}', # type: ignore
color(f' Right GUI : ', 'cyan'),
f'{self.report[0][7] == 1!s:<5}', # type: ignore
)
# ------------------------------------------------------------------------------
class Mouse:
def __init__(self): # type: ignore
self.report = [
[ # Bit array for Buttons
0, # Button 1 (primary/trigger
0, # Button 2 (secondary)
0, # Button 3 (tertiary)
0, # Button 4
0, # Button 5
0, # unused padding bits
0, # unused padding bits
0, # unused padding bits
],
0, # X
0, # Y
0, # Wheel
0, # AC Pan
]
def decode_mouse_report(self, input_report: bytes, report_length: int) -> None:
self.report[0] = [int(x) for x in '{0:08b}'.format(input_report[1])]
self.report[0].reverse() # type: ignore
self.report[1] = input_report[2]
self.report[2] = input_report[3]
if report_length in [5, 6]:
self.report[3] = input_report[4]
self.report[4] = input_report[5] if report_length == 6 else 0
def print_mouse_report(self) -> None:
print(color('\tMouse Input Received', 'green', None, 'bold'))
print(
color(f' Button 1 (primary/trigger) = ', 'cyan'),
self.report[0][0] == 1, # type: ignore
color(f'\n Button 2 (secondary) = ', 'cyan'),
self.report[0][1] == 1, # type: ignore
color(f'\n Button 3 (tertiary) = ', 'cyan'),
self.report[0][2] == 1, # type: ignore
color(f'\n Button4 = ', 'cyan'),
self.report[0][3] == 1, # type: ignore
color(f'\n Button5 = ', 'cyan'),
self.report[0][4] == 1, # type: ignore
color(f'\n X (X-axis displacement) = ', 'cyan'),
self.report[1],
color(f'\n Y (Y-axis displacement) = ', 'cyan'),
self.report[2],
color(f'\n Wheel = ', 'cyan'),
self.report[3],
color(f'\n AC PAN = ', 'cyan'),
self.report[4],
)
# ------------------------------------------------------------------------------
class ReportParser:
@staticmethod
def parse_input_report(input_report: bytes) -> None:
report_id = input_report[0] # pylint: disable=unsubscriptable-object
report_length = len(input_report)
# Keyboard input report (report id = 1)
if report_id == 1 and report_length >= 8:
keyboard = Keyboard() # type: ignore
keyboard.decode_keyboard_report(input_report, report_length)
keyboard.print_keyboard_report()
# Mouse input report (report id = 2)
elif report_id == 2 and report_length in [4, 5, 6]:
mouse = Mouse() # type: ignore
mouse.decode_mouse_report(input_report, report_length)
mouse.print_mouse_report()
else:
print(color(f'Warning: Parse Error Report ID {report_id}', 'yellow'))

View File

@@ -131,7 +131,7 @@ async def main():
await device.power_on()
# Create a listener to wait for AVDTP connections
listener = Listener(Listener.create_registrar(device))
listener = Listener.for_device(device)
listener.on('connection', on_avdtp_connection)
if len(sys.argv) >= 5:

View File

@@ -179,7 +179,7 @@ async def main():
await stream_packets(read, protocol)
else:
# Create a listener to wait for AVDTP connections
listener = Listener(Listener.create_registrar(device), version=(1, 2))
listener = Listener.for_device(device=device, version=(1, 2))
listener.on(
'connection', lambda protocol: on_avdtp_connection(read, protocol)
)

View File

@@ -21,6 +21,7 @@ import sys
import os
import logging
from bumble import l2cap
from bumble.core import AdvertisingData
from bumble.device import Device
from bumble.transport import open_transport_or_link
@@ -95,8 +96,10 @@ async def main():
channel.sink = on_data
psm = device.register_l2cap_channel_server(0, on_coc, 8)
print(f'### LE_PSM_OUT = {psm}')
server = device.create_l2cap_server(
spec=l2cap.LeCreditBasedChannelSpec(max_credits=8), handler=on_coc
)
print(f'### LE_PSM_OUT = {server.psm}')
# Add the ASHA service to the GATT server
read_only_properties_characteristic = Characteristic(
@@ -147,7 +150,7 @@ async def main():
ASHA_LE_PSM_OUT_CHARACTERISTIC,
Characteristic.Properties.READ,
Characteristic.READABLE,
struct.pack('<H', psm),
struct.pack('<H', server.psm),
)
device.add_service(
Service(

540
examples/run_hid_host.py Normal file
View File

@@ -0,0 +1,540 @@
# Copyright 2021-2022 Google LLC
#
# Licensed under the Apache License, Version 2.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.
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
import asyncio
import sys
import os
import logging
from bumble.colors import color
import bumble.core
from bumble.device import Device
from bumble.transport import open_transport_or_link
from bumble.core import (
BT_L2CAP_PROTOCOL_ID,
BT_HIDP_PROTOCOL_ID,
BT_HUMAN_INTERFACE_DEVICE_SERVICE,
BT_BR_EDR_TRANSPORT,
)
from bumble.hci import Address
from bumble.hid import Host, Message
from bumble.sdp import (
Client as SDP_Client,
DataElement,
ServiceAttribute,
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
SDP_ALL_ATTRIBUTES_RANGE,
SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID,
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
)
from hid_report_parser import ReportParser
# -----------------------------------------------------------------------------
# SDP attributes for Bluetooth HID devices
SDP_HID_SERVICE_NAME_ATTRIBUTE_ID = 0x0100
SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID = 0x0101
SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID = 0x0102
SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID = 0x0200 # [DEPRECATED]
SDP_HID_PARSER_VERSION_ATTRIBUTE_ID = 0x0201
SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID = 0x0202
SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID = 0x0203
SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID = 0x0204
SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID = 0x0205
SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID = 0x0206
SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID = 0x0207
SDP_HID_SDP_DISABLE_ATTRIBUTE_ID = 0x0208 # [DEPRECATED]
SDP_HID_BATTERY_POWER_ATTRIBUTE_ID = 0x0209
SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID = 0x020A
SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID = 0x020B # DEPRECATED]
SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID = 0x020C
SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID = 0x020D
SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID = 0x020E
SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID = 0x020F
SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID = 0x0210
# -----------------------------------------------------------------------------
async def get_hid_device_sdp_record(device, connection):
# Connect to the SDP Server
sdp_client = SDP_Client(device)
await sdp_client.connect(connection)
if sdp_client:
print(color('Connected to SDP Server', 'blue'))
else:
print(color('Failed to connect to SDP Server', 'red'))
# List BT HID Device service in the root browse group
service_record_handles = await sdp_client.search_services(
[BT_HUMAN_INTERFACE_DEVICE_SERVICE]
)
if len(service_record_handles) < 1:
await sdp_client.disconnect()
raise Exception(
color(f'BT HID Device service not found on peer device!!!!', 'red')
)
# For BT_HUMAN_INTERFACE_DEVICE_SERVICE service, get all its attributes
for service_record_handle in service_record_handles:
attributes = await sdp_client.get_attributes(
service_record_handle, [SDP_ALL_ATTRIBUTES_RANGE]
)
print(color(f'SERVICE {service_record_handle:04X} attributes:', 'yellow'))
print(color(f'SDP attributes for HID device', 'magenta'))
for attribute in attributes:
if attribute.id == SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID:
print(
color(' Service Record Handle : ', 'cyan'),
hex(attribute.value.value),
)
elif attribute.id == SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID:
print(
color(' Service Class : ', 'cyan'), attribute.value.value[0].value
)
elif attribute.id == SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID:
print(
color(' SDP Browse Group List : ', 'cyan'),
attribute.value.value[0].value,
)
elif attribute.id == SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
print(
color(' BT_L2CAP_PROTOCOL_ID : ', 'cyan'),
attribute.value.value[0].value[0].value,
)
print(
color(' PSM for Bluetooth HID Control channel : ', 'cyan'),
hex(attribute.value.value[0].value[1].value),
)
print(
color(' BT_HIDP_PROTOCOL_ID : ', 'cyan'),
attribute.value.value[1].value[0].value,
)
elif attribute.id == SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID:
print(
color(' Lanugage : ', 'cyan'), hex(attribute.value.value[0].value)
)
print(
color(' Encoding : ', 'cyan'), hex(attribute.value.value[1].value)
)
print(
color(' PrimaryLanguageBaseID : ', 'cyan'),
hex(attribute.value.value[2].value),
)
elif attribute.id == SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID:
print(
color(' BT_HUMAN_INTERFACE_DEVICE_SERVICE ', 'cyan'),
attribute.value.value[0].value[0].value,
)
print(
color(' HID Profileversion number : ', 'cyan'),
hex(attribute.value.value[0].value[1].value),
)
elif attribute.id == SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
print(
color(' BT_L2CAP_PROTOCOL_ID : ', 'cyan'),
attribute.value.value[0].value[0].value[0].value,
)
print(
color(' PSM for Bluetooth HID Interrupt channel : ', 'cyan'),
hex(attribute.value.value[0].value[0].value[1].value),
)
print(
color(' BT_HIDP_PROTOCOL_ID : ', 'cyan'),
attribute.value.value[0].value[1].value[0].value,
)
elif attribute.id == SDP_HID_SERVICE_NAME_ATTRIBUTE_ID:
print(color(' Service Name: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID:
print(color(' Service Description: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID:
print(color(' Provider Name: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID:
print(color(' Release Number: ', 'cyan'), hex(attribute.value.value))
elif attribute.id == SDP_HID_PARSER_VERSION_ATTRIBUTE_ID:
print(
color(' HID Parser Version: ', 'cyan'), hex(attribute.value.value)
)
elif attribute.id == SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID:
print(
color(' HIDDeviceSubclass: ', 'cyan'), hex(attribute.value.value)
)
elif attribute.id == SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID:
print(color(' HIDCountryCode: ', 'cyan'), hex(attribute.value.value))
elif attribute.id == SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID:
print(color(' HIDVirtualCable: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID:
print(color(' HIDReconnectInitiate: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID:
print(
color(' HID Report Descriptor type: ', 'cyan'),
hex(attribute.value.value[0].value[0].value),
)
print(
color(' HID Report DescriptorList: ', 'cyan'),
attribute.value.value[0].value[1].value,
)
elif attribute.id == SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID:
print(
color(' HID LANGID Base Language: ', 'cyan'),
hex(attribute.value.value[0].value[0].value),
)
print(
color(' HID LANGID Base Bluetooth String Offset: ', 'cyan'),
hex(attribute.value.value[0].value[1].value),
)
elif attribute.id == SDP_HID_BATTERY_POWER_ATTRIBUTE_ID:
print(color(' HIDBatteryPower: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID:
print(color(' HIDRemoteWake: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID:
print(
color(' HIDProfileVersion : ', 'cyan'), hex(attribute.value.value)
)
elif attribute.id == SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID:
print(
color(' HIDSupervisionTimeout: ', 'cyan'),
hex(attribute.value.value),
)
elif attribute.id == SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID:
print(
color(' HIDNormallyConnectable: ', 'cyan'), attribute.value.value
)
elif attribute.id == SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID:
print(color(' HIDBootDevice: ', 'cyan'), attribute.value.value)
elif attribute.id == SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID:
print(
color(' HIDSSRHostMaxLatency: ', 'cyan'),
hex(attribute.value.value),
)
elif attribute.id == SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID:
print(
color(' HIDSSRHostMinTimeout: ', 'cyan'),
hex(attribute.value.value),
)
else:
print(
color(
f' Warning: Attribute ID: {attribute.id} match not found.\n Attribute Info: {attribute}',
'yellow',
)
)
await sdp_client.disconnect()
# -----------------------------------------------------------------------------
async def get_stream_reader(pipe) -> asyncio.StreamReader:
loop = asyncio.get_event_loop()
reader = asyncio.StreamReader(loop=loop)
protocol = asyncio.StreamReaderProtocol(reader)
await loop.connect_read_pipe(lambda: protocol, pipe)
return reader
# -----------------------------------------------------------------------------
async def main():
if len(sys.argv) < 4:
print(
'Usage: run_hid_host.py <device-config> <transport-spec> '
'<bluetooth-address> [test-mode]'
)
print('example: run_hid_host.py classic1.json usb:0 E1:CA:72:48:C4:E8/P')
return
def on_hid_data_cb(pdu):
report_type = pdu[0] & 0x0F
if len(pdu) == 1:
print(color(f'Warning: No report received', 'yellow'))
return
report_length = len(pdu[1:])
report_id = pdu[1]
if report_type != Message.ReportType.OTHER_REPORT:
print(
color(
f' Report type = {report_type}, Report length = {report_length}, Report id = {report_id}',
'blue',
None,
'bold',
)
)
if (report_length <= 1) or (report_id == 0):
return
if report_type == Message.ReportType.INPUT_REPORT:
ReportParser.parse_input_report(pdu[1:]) # type: ignore
async def handle_virtual_cable_unplug():
await hid_host.disconnect_interrupt_channel()
await hid_host.disconnect_control_channel()
await device.keystore.delete(target_address) # type: ignore
await connection.disconnect()
def on_hid_virtual_cable_unplug_cb():
asyncio.create_task(handle_virtual_cable_unplug())
print('<<< connecting to HCI...')
async with await open_transport_or_link(sys.argv[2]) as (hci_source, hci_sink):
print('<<< CONNECTED')
# Create a device
device = Device.from_config_file_with_hci(sys.argv[1], hci_source, hci_sink)
device.classic_enabled = True
await device.power_on()
# Connect to a peer
target_address = sys.argv[3]
print(f'=== Connecting to {target_address}...')
connection = await device.connect(target_address, transport=BT_BR_EDR_TRANSPORT)
print(f'=== Connected to {connection.peer_address}!')
# Request authentication
print('*** Authenticating...')
await connection.authenticate()
print('*** Authenticated...')
# Enable encryption
print('*** Enabling encryption...')
await connection.encrypt()
print('*** Encryption on')
await get_hid_device_sdp_record(device, connection)
# Create HID host and start it
print('@@@ Starting HID Host...')
hid_host = Host(device, connection)
# Register for HID data call back
hid_host.on('data', on_hid_data_cb)
# Register for virtual cable unplug call back
hid_host.on('virtual_cable_unplug', on_hid_virtual_cable_unplug_cb)
async def menu():
reader = await get_stream_reader(sys.stdin)
while True:
print(
"\n************************ HID Host Menu *****************************\n"
)
print(" 1. Connect Control Channel")
print(" 2. Connect Interrupt Channel")
print(" 3. Disconnect Control Channel")
print(" 4. Disconnect Interrupt Channel")
print(" 5. Get Report")
print(" 6. Set Report")
print(" 7. Set Protocol Mode")
print(" 8. Get Protocol Mode")
print(" 9. Send Report")
print("10. Suspend")
print("11. Exit Suspend")
print("12. Virtual Cable Unplug")
print("13. Disconnect device")
print("14. Delete Bonding")
print("15. Re-connect to device")
print("\nEnter your choice : \n")
choice = await reader.readline()
choice = choice.decode('utf-8').strip()
if choice == '1':
await hid_host.connect_control_channel()
elif choice == '2':
await hid_host.connect_interrupt_channel()
elif choice == '3':
await hid_host.disconnect_control_channel()
elif choice == '4':
await hid_host.disconnect_interrupt_channel()
elif choice == '5':
print(" 1. Report ID 0x02")
print(" 2. Report ID 0x03")
print(" 3. Report ID 0x05")
choice1 = await reader.readline()
choice1 = choice1.decode('utf-8').strip()
if choice1 == '1':
hid_host.get_report(1, 2, 3)
elif choice1 == '2':
hid_host.get_report(2, 3, 2)
elif choice1 == '3':
hid_host.get_report(3, 5, 3)
else:
print('Incorrect option selected')
elif choice == '6':
print(" 1. Report type 1 and Report id 0x01")
print(" 2. Report type 2 and Report id 0x03")
print(" 3. Report type 3 and Report id 0x05")
choice1 = await reader.readline()
choice1 = choice1.decode('utf-8').strip()
if choice1 == '1':
# data includes first octet as report id
data = bytearray(
[0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]
)
hid_host.set_report(1, data)
elif choice1 == '2':
data = bytearray([0x03, 0x01, 0x01])
hid_host.set_report(2, data)
elif choice1 == '3':
data = bytearray([0x05, 0x01, 0x01, 0x01])
hid_host.set_report(3, data)
else:
print('Incorrect option selected')
elif choice == '7':
print(" 0. Boot")
print(" 1. Report")
choice1 = await reader.readline()
choice1 = choice1.decode('utf-8').strip()
if choice1 == '0':
hid_host.set_protocol(Message.ProtocolMode.BOOT_PROTOCOL)
elif choice1 == '1':
hid_host.set_protocol(Message.ProtocolMode.REPORT_PROTOCOL)
else:
print('Incorrect option selected')
elif choice == '8':
hid_host.get_protocol()
elif choice == '9':
print(" 1. Report ID 0x01")
print(" 2. Report ID 0x03")
choice1 = await reader.readline()
choice1 = choice1.decode('utf-8').strip()
if choice1 == '1':
data = bytearray(
[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
)
hid_host.send_data(data)
elif choice1 == '2':
data = bytearray([0x03, 0x00, 0x0D, 0xFD, 0x00, 0x00])
hid_host.send_data(data)
else:
print('Incorrect option selected')
elif choice == '10':
hid_host.suspend()
elif choice == '11':
hid_host.exit_suspend()
elif choice == '12':
hid_host.virtual_cable_unplug()
try:
await device.keystore.delete(target_address)
except KeyError:
print('Device not found or Device already unpaired.')
elif choice == '13':
peer_address = Address.from_string_for_transport(
target_address, transport=BT_BR_EDR_TRANSPORT
)
connection = device.find_connection_by_bd_addr(
peer_address, transport=BT_BR_EDR_TRANSPORT
)
if connection is not None:
await connection.disconnect()
else:
print("Already disconnected from device")
elif choice == '14':
try:
await device.keystore.delete(target_address)
print("Unpair successful")
except KeyError:
print('Device not found or Device already unpaired.')
elif choice == '15':
connection = await device.connect(
target_address, transport=BT_BR_EDR_TRANSPORT
)
await connection.authenticate()
await connection.encrypt()
else:
print("Invalid option selected.")
if (len(sys.argv) > 4) and (sys.argv[4] == 'test-mode'):
# Enabling menu for testing
await menu()
else:
# HID Connection
# Control channel
await hid_host.connect_control_channel()
# Interrupt Channel
await hid_host.connect_interrupt_channel()
await hci_source.wait_for_termination()
# -----------------------------------------------------------------------------
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
asyncio.run(main())

10
extras/android/RemoteHCI/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,73 @@
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
}
android {
namespace = "com.github.google.bumble.remotehci"
compileSdk = 33
defaultConfig {
applicationId = "com.github.google.bumble.remotehci"
minSdk = 26
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
aidl = false
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(kotlin("reflect"))
implementation(libs.core.ktx)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.activity.compose)
implementation(platform(libs.compose.bom))
implementation(libs.ui)
implementation(libs.ui.graphics)
implementation(libs.ui.tooling.preview)
implementation(libs.material3)
compileOnly(project(":lib"))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
androidTestImplementation(platform(libs.compose.bom))
androidTestImplementation(libs.ui.test.junit4)
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
//compileOnly(files("${project.rootDir.absolutePath}/sdk/framework.jar"))
}

View File

@@ -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

View File

@@ -0,0 +1,29 @@
<?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.INTERNET" />
<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.RemoteHCI"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.RemoteHCI">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.bluetooth;
@VintfStability
interface IBluetoothHci {
void close();
void initialize(in android.hardware.bluetooth.IBluetoothHciCallbacks callback);
void sendAclData(in byte[] data);
void sendHciCommand(in byte[] command);
void sendIsoData(in byte[] data);
void sendScoData(in byte[] data);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.bluetooth;
@VintfStability
interface IBluetoothHciCallbacks {
void aclDataReceived(in byte[] data);
void hciEventReceived(in byte[] event);
void initializationComplete(in android.hardware.bluetooth.Status status);
void isoDataReceived(in byte[] data);
void scoDataReceived(in byte[] data);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.bluetooth;
@Backing(type="int")
@VintfStability
enum Status {
SUCCESS = 0,
ALREADY_INITIALIZED = 1,
UNABLE_TO_OPEN_INTERFACE = 2,
HARDWARE_INITIALIZATION_ERROR = 3,
UNKNOWN = 4,
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 android.hardware.bluetooth@1.1;
import @1.0::HciPacket;
import @1.0::IBluetoothHci;
import IBluetoothHciCallbacks;
/**
* The Host Controller Interface (HCI) is the layer defined by the Bluetooth
* specification between the software that runs on the host and the Bluetooth
* controller chip. This boundary is the natural choice for a Hardware
* Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
* the stack and abstracts away power management, initialization, and other
* implementation-specific details related to the hardware.
*/
interface IBluetoothHci extends @1.0::IBluetoothHci {
/**
* Same as @1.0, but uses 1.1 Callbacks version
*/
initialize_1_1(@1.1::IBluetoothHciCallbacks callback);
/**
* Send an ISO data packet (as specified in the Bluetooth Core
* Specification v5.2) to the Bluetooth controller.
* Packets must be processed in order.
* @param data HCI data packet to be sent
*/
sendIsoData(HciPacket data);
};

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 android.hardware.bluetooth@1.1;
import @1.0::HciPacket;
import @1.0::IBluetoothHciCallbacks;
/**
* The interface from the Bluetooth Controller to the stack.
*/
interface IBluetoothHciCallbacks extends @1.0::IBluetoothHciCallbacks {
/**
* Send a ISO data packet form the controller to the host.
* @param data the ISO HCI packet to be passed to the host stack
*/
isoDataReceived(HciPacket data);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,259 @@
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package android.hardware.bluetooth;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public interface IBluetoothHci extends android.os.IInterface
{
/** Default implementation for IBluetoothHci. */
public static class Default implements android.hardware.bluetooth.IBluetoothHci
{
@Override public void close() throws android.os.RemoteException
{
}
@Override public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException
{
}
@Override public void sendAclData(byte[] data) throws android.os.RemoteException
{
}
@Override public void sendHciCommand(byte[] command) throws android.os.RemoteException
{
}
@Override public void sendIsoData(byte[] data) throws android.os.RemoteException
{
}
@Override public void sendScoData(byte[] data) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.hardware.bluetooth.IBluetoothHci
{
/** Construct the stub at attach it to the interface. */
public Stub()
{
//this.markVintfStability();
try {
Method method = this.getClass().getMethod("markVintfStability", (Class<?>[])null);
method.invoke(this);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHci interface,
* generating a proxy if needed.
*/
public static android.hardware.bluetooth.IBluetoothHci asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHci))) {
return ((android.hardware.bluetooth.IBluetoothHci)iin);
}
return new android.hardware.bluetooth.IBluetoothHci.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
data.enforceInterface(descriptor);
}
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
}
switch (code)
{
case TRANSACTION_close:
{
this.close();
reply.writeNoException();
break;
}
case TRANSACTION_initialize:
{
android.hardware.bluetooth.IBluetoothHciCallbacks _arg0;
_arg0 = android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.asInterface(data.readStrongBinder());
this.initialize(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_sendAclData:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.sendAclData(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_sendHciCommand:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.sendHciCommand(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_sendIsoData:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.sendIsoData(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_sendScoData:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.sendScoData(_arg0);
reply.writeNoException();
break;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
return true;
}
private static class Proxy implements android.hardware.bluetooth.IBluetoothHci
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void close() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_close, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongInterface(callback);
boolean _status = mRemote.transact(Stub.TRANSACTION_initialize, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void sendAclData(byte[] data) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(data);
boolean _status = mRemote.transact(Stub.TRANSACTION_sendAclData, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void sendHciCommand(byte[] command) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(command);
boolean _status = mRemote.transact(Stub.TRANSACTION_sendHciCommand, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void sendIsoData(byte[] data) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(data);
boolean _status = mRemote.transact(Stub.TRANSACTION_sendIsoData, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void sendScoData(byte[] data) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(data);
boolean _status = mRemote.transact(Stub.TRANSACTION_sendScoData, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_close = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_initialize = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_sendAclData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_sendHciCommand = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
static final int TRANSACTION_sendIsoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
static final int TRANSACTION_sendScoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
}
public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHci".replace('$', '.');
public void close() throws android.os.RemoteException;
public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException;
public void sendAclData(byte[] data) throws android.os.RemoteException;
public void sendHciCommand(byte[] command) throws android.os.RemoteException;
public void sendIsoData(byte[] data) throws android.os.RemoteException;
public void sendScoData(byte[] data) throws android.os.RemoteException;
}

View File

@@ -0,0 +1,234 @@
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package android.hardware.bluetooth;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public interface IBluetoothHciCallbacks extends android.os.IInterface
{
/** Default implementation for IBluetoothHciCallbacks. */
public static class Default implements android.hardware.bluetooth.IBluetoothHciCallbacks
{
@Override public void aclDataReceived(byte[] data) throws android.os.RemoteException
{
}
@Override public void hciEventReceived(byte[] event) throws android.os.RemoteException
{
}
@Override public void initializationComplete(int status) throws android.os.RemoteException
{
}
@Override public void isoDataReceived(byte[] data) throws android.os.RemoteException
{
}
@Override public void scoDataReceived(byte[] data) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.hardware.bluetooth.IBluetoothHciCallbacks
{
/** Construct the stub at attach it to the interface. */
public Stub()
{
//this.markVintfStability();
try {
Method method = this.getClass().getMethod("markVintfStability", (Class<?>[])null);
method.invoke(this);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHciCallbacks interface,
* generating a proxy if needed.
*/
public static android.hardware.bluetooth.IBluetoothHciCallbacks asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHciCallbacks))) {
return ((android.hardware.bluetooth.IBluetoothHciCallbacks)iin);
}
return new android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
data.enforceInterface(descriptor);
}
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
}
switch (code)
{
case TRANSACTION_aclDataReceived:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.aclDataReceived(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_hciEventReceived:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.hciEventReceived(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_initializationComplete:
{
int _arg0;
_arg0 = data.readInt();
this.initializationComplete(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_isoDataReceived:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.isoDataReceived(_arg0);
reply.writeNoException();
break;
}
case TRANSACTION_scoDataReceived:
{
byte[] _arg0;
_arg0 = data.createByteArray();
this.scoDataReceived(_arg0);
reply.writeNoException();
break;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
return true;
}
private static class Proxy implements android.hardware.bluetooth.IBluetoothHciCallbacks
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void aclDataReceived(byte[] data) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(data);
boolean _status = mRemote.transact(Stub.TRANSACTION_aclDataReceived, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void hciEventReceived(byte[] event) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(event);
boolean _status = mRemote.transact(Stub.TRANSACTION_hciEventReceived, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void initializationComplete(int status) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(status);
boolean _status = mRemote.transact(Stub.TRANSACTION_initializationComplete, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void isoDataReceived(byte[] data) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(data);
boolean _status = mRemote.transact(Stub.TRANSACTION_isoDataReceived, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void scoDataReceived(byte[] data) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeByteArray(data);
boolean _status = mRemote.transact(Stub.TRANSACTION_scoDataReceived, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_aclDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_hciEventReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_initializationComplete = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_isoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
static final int TRANSACTION_scoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
}
public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHciCallbacks".replace('$', '.');
public void aclDataReceived(byte[] data) throws android.os.RemoteException;
public void hciEventReceived(byte[] event) throws android.os.RemoteException;
public void initializationComplete(int status) throws android.os.RemoteException;
public void isoDataReceived(byte[] data) throws android.os.RemoteException;
public void scoDataReceived(byte[] data) throws android.os.RemoteException;
}

View File

@@ -0,0 +1,11 @@
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package android.hardware.bluetooth;
public @interface Status {
public static final int SUCCESS = 0;
public static final int ALREADY_INITIALIZED = 1;
public static final int UNABLE_TO_OPEN_INTERFACE = 2;
public static final int HARDWARE_INITIALIZATION_ERROR = 3;
public static final int UNKNOWN = 4;
}

View File

@@ -0,0 +1,816 @@
package android.hardware.bluetooth.V1_0;
import android.os.HidlSupport;
import android.os.HwBinder;
import android.os.IHwBinder;
import android.os.HwBlob;
import android.os.HwParcel;
import android.os.IHwInterface;
import android.os.NativeHandle;
/**
* The Host Controller Interface (HCI) is the layer defined by the Bluetooth
* specification between the software that runs on the host and the Bluetooth
* controller chip. This boundary is the natural choice for a Hardware
* Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
* the stack and abstracts away power management, initialization, and other
* implementation-specific details related to the hardware.
*/
public interface IBluetoothHci extends android.internal.hidl.base.V1_0.IBase {
/**
* Fully-qualified interface name for this interface.
*/
public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHci";
/**
* Does a checked conversion from a binder to this class.
*/
/* package private */ static IBluetoothHci asInterface(IHwBinder binder) {
if (binder == null) {
return null;
}
IHwInterface iface =
binder.queryLocalInterface(kInterfaceName);
if ((iface != null) && (iface instanceof IBluetoothHci)) {
return (IBluetoothHci)iface;
}
IBluetoothHci proxy = new IBluetoothHci.Proxy(binder);
try {
for (String descriptor : proxy.interfaceChain()) {
if (descriptor.equals(kInterfaceName)) {
return proxy;
}
}
} catch (android.os.RemoteException e) {
}
return null;
}
/**
* Does a checked conversion from any interface to this class.
*/
public static IBluetoothHci castFrom(IHwInterface iface) {
return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder());
}
@Override
public IHwBinder asBinder();
/**
* This will invoke the equivalent of the C++ getService(std::string) if retry is
* true or tryGetService(std::string) if retry is false. If the service is
* available on the device and retry is true, this will wait for the service to
* start.
*
*/
public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException {
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName, retry));
}
/**
* Calls getService("default",retry).
*/
public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException {
return getService("default", retry);
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(String,boolean) instead.
*/
@Deprecated
public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException {
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName));
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(boolean) instead.
*/
@Deprecated
public static IBluetoothHci getService() throws android.os.RemoteException {
return getService("default");
}
/**
* Initialize the underlying HCI interface.
*
* This method should be used to initialize any hardware interfaces
* required to communicate with the Bluetooth hardware in the
* device.
*
* The |oninitializationComplete| callback must be invoked in response
* to this function to indicate success before any other function
* (sendHciCommand, sendAclData, * sendScoData) is invoked on this
* interface.
*
* @param callback implements IBluetoothHciCallbacks which will
* receive callbacks when incoming HCI packets are received
* from the controller to be sent to the host.
*/
void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
throws android.os.RemoteException;
/**
* Send an HCI command (as specified in the Bluetooth Specification
* V4.2, Vol 2, Part 5, Section 5.4.1) to the Bluetooth controller.
* Commands must be executed in order.
*
* @param command is the HCI command to be sent
*/
void sendHciCommand(java.util.ArrayList<Byte> command)
throws android.os.RemoteException;
/**
* Send an HCI ACL data packet (as specified in the Bluetooth Specification
* V4.2, Vol 2, Part 5, Section 5.4.2) to the Bluetooth controller.
* Packets must be processed in order.
* @param data HCI data packet to be sent
*/
void sendAclData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException;
/**
* Send an SCO data packet (as specified in the Bluetooth Specification
* V4.2, Vol 2, Part 5, Section 5.4.3) to the Bluetooth controller.
* Packets must be processed in order.
* @param data HCI data packet to be sent
*/
void sendScoData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException;
/**
* Close the HCI interface
*/
void close()
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* ["android.hardware.foo@1.0::IChild",
* "android.hardware.foo@1.0::IParent"
* "android.internal.hidl.base@1.0::IBase"]
*
* @return descriptors a vector of descriptors of the run-time type of the
* object.
*/
java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException;
/*
* Emit diagnostic information to the given file.
*
* Optionally overriden.
*
* @param fd File descriptor to dump data to.
* Must only be used for the duration of this call.
* @param options Arguments for debugging.
* Must support empty for default debug information.
*/
void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceDescriptor on an IChild object must yield
* "android.hardware.foo@1.0::IChild"
*
* @return descriptor a descriptor of the run-time type of the
* object (the first element of the vector returned by
* interfaceChain())
*/
String interfaceDescriptor()
throws android.os.RemoteException;
/*
* Returns hashes of the source HAL files that define the interfaces of the
* runtime type information on the object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* [(hash of IChild.hal),
* (hash of IParent.hal)
* (hash of IBase.hal)].
*
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
* according to SHA-256 standard.
*
* @return hashchain a vector of SHA-1 digests
*/
java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException;
/*
* This method trigger the interface to enable/disable instrumentation based
* on system property hal.instrumentation.enable.
*/
void setHALInstrumentation()
throws android.os.RemoteException;
/*
* Registers a death recipient, to be called when the process hosting this
* interface dies.
*
* @param recipient a hidl_death_recipient callback object
* @param cookie a cookie that must be returned with the callback
* @return success whether the death recipient was registered successfully.
*/
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException;
/*
* Provides way to determine if interface is running without requesting
* any functionality.
*/
void ping()
throws android.os.RemoteException;
/*
* Get debug information on references on this interface.
* @return info debugging information. See comments of DebugInfo.
*/
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException;
/*
* This method notifies the interface that one or more system properties
* have changed. The default implementation calls
* (C++) report_sysprop_change() in libcutils or
* (Java) android.os.SystemProperties.reportSyspropChanged,
* which in turn calls a set of registered callbacks (eg to update trace
* tags).
*/
void notifySyspropsChanged()
throws android.os.RemoteException;
/*
* Unregisters the registered death recipient. If this service was registered
* multiple times with the same exact death recipient, this unlinks the most
* recently registered one.
*
* @param recipient a previously registered hidl_death_recipient callback
* @return success whether the death recipient was unregistered successfully.
*/
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException;
public static final class Proxy implements IBluetoothHci {
private IHwBinder mRemote;
public Proxy(IHwBinder remote) {
mRemote = java.util.Objects.requireNonNull(remote);
}
@Override
public IHwBinder asBinder() {
return mRemote;
}
@Override
public String toString() {
try {
return this.interfaceDescriptor() + "@Proxy";
} catch (android.os.RemoteException ex) {
/* ignored; handled below. */
}
return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy";
}
@Override
public final boolean equals(java.lang.Object other) {
return HidlSupport.interfacesEqual(this, other);
}
@Override
public final int hashCode() {
return this.asBinder().hashCode();
}
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow.
@Override
public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void sendHciCommand(java.util.ArrayList<Byte> command)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeInt8Vector(command);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void sendAclData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void sendScoData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void close()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
@Override
public java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
return _hidl_out_descriptors;
} finally {
_hidl_reply.release();
}
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
_hidl_request.writeNativeHandle(fd);
_hidl_request.writeStringVector(options);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public String interfaceDescriptor()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
String _hidl_out_descriptor = _hidl_reply.readString();
return _hidl_out_descriptor;
} finally {
_hidl_reply.release();
}
}
@Override
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
{
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
{
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
_hidl_vec_size * 32,_hidl_blob.handle(),
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
byte[/* 32 */] _hidl_vec_element = new byte[32];
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
_hidl_array_offset_1 += 32 * 1;
}
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
}
}
}
return _hidl_out_hashchain;
} finally {
_hidl_reply.release();
}
}
@Override
public void setHALInstrumentation()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException {
return mRemote.linkToDeath(recipient, cookie);
}
@Override
public void ping()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
return _hidl_out_info;
} finally {
_hidl_reply.release();
}
}
@Override
public void notifySyspropsChanged()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException {
return mRemote.unlinkToDeath(recipient);
}
}
public static abstract class Stub extends HwBinder implements IBluetoothHci {
@Override
public IHwBinder asBinder() {
return this;
}
@Override
public final java.util.ArrayList<String> interfaceChain() {
return new java.util.ArrayList<String>(java.util.Arrays.asList(
android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName,
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
return;
}
@Override
public final String interfaceDescriptor() {
return android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName;
}
@Override
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */,
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
}
@Override
public final void setHALInstrumentation() {
}
@Override
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
return true;
}
@Override
public final void ping() {
return;
}
@Override
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
info.pid = HidlSupport.getPidIfSharable();
info.ptr = 0;
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
return info;
}
@Override
public final void notifySyspropsChanged() {
HwBinder.enableInstrumentation();
}
@Override
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
return true;
}
@Override
public IHwInterface queryLocalInterface(String descriptor) {
if (kInterfaceName.equals(descriptor)) {
return this;
}
return null;
}
public void registerAsService(String serviceName) throws android.os.RemoteException {
registerService(serviceName);
}
@Override
public String toString() {
return this.interfaceDescriptor() + "@Stub";
}
//@Override
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
throws android.os.RemoteException {
switch (_hidl_code) {
case 1 /* initialize */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
initialize(callback);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 2 /* sendHciCommand */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
java.util.ArrayList<Byte> command = _hidl_request.readInt8Vector();
sendHciCommand(command);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 3 /* sendAclData */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
sendAclData(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 4 /* sendScoData */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
sendScoData(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 5 /* close */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
close();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256067662 /* interfaceChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeStringVector(_hidl_out_descriptors);
_hidl_reply.send();
break;
}
case 256131655 /* debug */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
NativeHandle fd = _hidl_request.readNativeHandle();
java.util.ArrayList<String> options = _hidl_request.readStringVector();
debug(fd, options);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256136003 /* interfaceDescriptor */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
String _hidl_out_descriptor = interfaceDescriptor();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeString(_hidl_out_descriptor);
_hidl_reply.send();
break;
}
case 256398152 /* getHashChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
{
HwBlob _hidl_blob = new HwBlob(16 /* size */);
{
int _hidl_vec_size = _hidl_out_hashchain.size();
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
throw new IllegalArgumentException("Array element is not of the expected length");
}
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
_hidl_array_offset_1 += 32 * 1;
}
}
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
}
_hidl_reply.writeBuffer(_hidl_blob);
}
_hidl_reply.send();
break;
}
case 256462420 /* setHALInstrumentation */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
setHALInstrumentation();
break;
}
case 256660548 /* linkToDeath */:
{
break;
}
case 256921159 /* ping */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
ping();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 257049926 /* getDebugInfo */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
_hidl_reply.send();
break;
}
case 257120595 /* notifySyspropsChanged */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
notifySyspropsChanged();
break;
}
case 257250372 /* unlinkToDeath */:
{
break;
}
}
}
}
}

View File

@@ -0,0 +1,762 @@
package android.hardware.bluetooth.V1_0;
import android.os.HidlSupport;
import android.os.HwBinder;
import android.os.IHwBinder;
import android.os.HwBlob;
import android.os.HwParcel;
import android.os.IHwInterface;
import android.os.NativeHandle;
/**
* The interface from the Bluetooth Controller to the stack.
*/
public interface IBluetoothHciCallbacks extends android.internal.hidl.base.V1_0.IBase {
/**
* Fully-qualified interface name for this interface.
*/
public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHciCallbacks";
/**
* Does a checked conversion from a binder to this class.
*/
/* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) {
if (binder == null) {
return null;
}
IHwInterface iface =
binder.queryLocalInterface(kInterfaceName);
if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) {
return (IBluetoothHciCallbacks)iface;
}
IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder);
try {
for (String descriptor : proxy.interfaceChain()) {
if (descriptor.equals(kInterfaceName)) {
return proxy;
}
}
} catch (android.os.RemoteException e) {
}
return null;
}
/**
* Does a checked conversion from any interface to this class.
*/
public static IBluetoothHciCallbacks castFrom(IHwInterface iface) {
return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder());
}
@Override
public IHwBinder asBinder();
/**
* This will invoke the equivalent of the C++ getService(std::string) if retry is
* true or tryGetService(std::string) if retry is false. If the service is
* available on the device and retry is true, this will wait for the service to
* start.
*
*/
public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException {
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName, retry));
}
/**
* Calls getService("default",retry).
*/
public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException {
return getService("default", retry);
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(String,boolean) instead.
*/
@Deprecated
public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException {
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName));
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(boolean) instead.
*/
@Deprecated
public static IBluetoothHciCallbacks getService() throws android.os.RemoteException {
return getService("default");
}
/**
* Invoked when the Bluetooth controller initialization has been
* completed.
*/
void initializationComplete(int status)
throws android.os.RemoteException;
/**
* This function is invoked when an HCI event is received from the
* Bluetooth controller to be forwarded to the Bluetooth stack.
* @param event is the HCI event to be sent to the Bluetooth stack.
*/
void hciEventReceived(java.util.ArrayList<Byte> event)
throws android.os.RemoteException;
/**
* Send an ACL data packet form the controller to the host.
* @param data the ACL HCI packet to be passed to the host stack
*/
void aclDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException;
/**
* Send a SCO data packet form the controller to the host.
* @param data the SCO HCI packet to be passed to the host stack
*/
void scoDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* ["android.hardware.foo@1.0::IChild",
* "android.hardware.foo@1.0::IParent"
* "android.internal.hidl.base@1.0::IBase"]
*
* @return descriptors a vector of descriptors of the run-time type of the
* object.
*/
java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException;
/*
* Emit diagnostic information to the given file.
*
* Optionally overriden.
*
* @param fd File descriptor to dump data to.
* Must only be used for the duration of this call.
* @param options Arguments for debugging.
* Must support empty for default debug information.
*/
void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceDescriptor on an IChild object must yield
* "android.hardware.foo@1.0::IChild"
*
* @return descriptor a descriptor of the run-time type of the
* object (the first element of the vector returned by
* interfaceChain())
*/
String interfaceDescriptor()
throws android.os.RemoteException;
/*
* Returns hashes of the source HAL files that define the interfaces of the
* runtime type information on the object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* [(hash of IChild.hal),
* (hash of IParent.hal)
* (hash of IBase.hal)].
*
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
* according to SHA-256 standard.
*
* @return hashchain a vector of SHA-1 digests
*/
java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException;
/*
* This method trigger the interface to enable/disable instrumentation based
* on system property hal.instrumentation.enable.
*/
void setHALInstrumentation()
throws android.os.RemoteException;
/*
* Registers a death recipient, to be called when the process hosting this
* interface dies.
*
* @param recipient a hidl_death_recipient callback object
* @param cookie a cookie that must be returned with the callback
* @return success whether the death recipient was registered successfully.
*/
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException;
/*
* Provides way to determine if interface is running without requesting
* any functionality.
*/
void ping()
throws android.os.RemoteException;
/*
* Get debug information on references on this interface.
* @return info debugging information. See comments of DebugInfo.
*/
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException;
/*
* This method notifies the interface that one or more system properties
* have changed. The default implementation calls
* (C++) report_sysprop_change() in libcutils or
* (Java) android.os.SystemProperties.reportSyspropChanged,
* which in turn calls a set of registered callbacks (eg to update trace
* tags).
*/
void notifySyspropsChanged()
throws android.os.RemoteException;
/*
* Unregisters the registered death recipient. If this service was registered
* multiple times with the same exact death recipient, this unlinks the most
* recently registered one.
*
* @param recipient a previously registered hidl_death_recipient callback
* @return success whether the death recipient was unregistered successfully.
*/
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException;
public static final class Proxy implements IBluetoothHciCallbacks {
private IHwBinder mRemote;
public Proxy(IHwBinder remote) {
mRemote = java.util.Objects.requireNonNull(remote);
}
@Override
public IHwBinder asBinder() {
return mRemote;
}
@Override
public String toString() {
try {
return this.interfaceDescriptor() + "@Proxy";
} catch (android.os.RemoteException ex) {
/* ignored; handled below. */
}
return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy";
}
@Override
public final boolean equals(java.lang.Object other) {
return HidlSupport.interfacesEqual(this, other);
}
@Override
public final int hashCode() {
return this.asBinder().hashCode();
}
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow.
@Override
public void initializationComplete(int status)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt32(status);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void hciEventReceived(java.util.ArrayList<Byte> event)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt8Vector(event);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void aclDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void scoDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
@Override
public java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
return _hidl_out_descriptors;
} finally {
_hidl_reply.release();
}
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
_hidl_request.writeNativeHandle(fd);
_hidl_request.writeStringVector(options);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public String interfaceDescriptor()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
String _hidl_out_descriptor = _hidl_reply.readString();
return _hidl_out_descriptor;
} finally {
_hidl_reply.release();
}
}
@Override
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
{
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
{
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
_hidl_vec_size * 32,_hidl_blob.handle(),
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
byte[/* 32 */] _hidl_vec_element = new byte[32];
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
_hidl_array_offset_1 += 32 * 1;
}
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
}
}
}
return _hidl_out_hashchain;
} finally {
_hidl_reply.release();
}
}
@Override
public void setHALInstrumentation()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException {
return mRemote.linkToDeath(recipient, cookie);
}
@Override
public void ping()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
return _hidl_out_info;
} finally {
_hidl_reply.release();
}
}
@Override
public void notifySyspropsChanged()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException {
return mRemote.unlinkToDeath(recipient);
}
}
public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks {
@Override
public IHwBinder asBinder() {
return this;
}
@Override
public final java.util.ArrayList<String> interfaceChain() {
return new java.util.ArrayList<String>(java.util.Arrays.asList(
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName,
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
return;
}
@Override
public final String interfaceDescriptor() {
return android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName;
}
@Override
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */,
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
}
@Override
public final void setHALInstrumentation() {
}
@Override
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
return true;
}
@Override
public final void ping() {
return;
}
@Override
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
info.pid = HidlSupport.getPidIfSharable();
info.ptr = 0;
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
return info;
}
@Override
public final void notifySyspropsChanged() {
HwBinder.enableInstrumentation();
}
@Override
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
return true;
}
@Override
public IHwInterface queryLocalInterface(String descriptor) {
if (kInterfaceName.equals(descriptor)) {
return this;
}
return null;
}
public void registerAsService(String serviceName) throws android.os.RemoteException {
registerService(serviceName);
}
@Override
public String toString() {
return this.interfaceDescriptor() + "@Stub";
}
//@Override
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
throws android.os.RemoteException {
switch (_hidl_code) {
case 1 /* initializationComplete */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
int status = _hidl_request.readInt32();
initializationComplete(status);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 2 /* hciEventReceived */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
java.util.ArrayList<Byte> event = _hidl_request.readInt8Vector();
hciEventReceived(event);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 3 /* aclDataReceived */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
aclDataReceived(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 4 /* scoDataReceived */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
scoDataReceived(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256067662 /* interfaceChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeStringVector(_hidl_out_descriptors);
_hidl_reply.send();
break;
}
case 256131655 /* debug */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
NativeHandle fd = _hidl_request.readNativeHandle();
java.util.ArrayList<String> options = _hidl_request.readStringVector();
debug(fd, options);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256136003 /* interfaceDescriptor */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
String _hidl_out_descriptor = interfaceDescriptor();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeString(_hidl_out_descriptor);
_hidl_reply.send();
break;
}
case 256398152 /* getHashChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
{
HwBlob _hidl_blob = new HwBlob(16 /* size */);
{
int _hidl_vec_size = _hidl_out_hashchain.size();
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
throw new IllegalArgumentException("Array element is not of the expected length");
}
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
_hidl_array_offset_1 += 32 * 1;
}
}
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
}
_hidl_reply.writeBuffer(_hidl_blob);
}
_hidl_reply.send();
break;
}
case 256462420 /* setHALInstrumentation */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
setHALInstrumentation();
break;
}
case 256660548 /* linkToDeath */:
{
break;
}
case 256921159 /* ping */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
ping();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 257049926 /* getDebugInfo */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
_hidl_reply.send();
break;
}
case 257120595 /* notifySyspropsChanged */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
notifySyspropsChanged();
break;
}
case 257250372 /* unlinkToDeath */:
{
break;
}
}
}
}
}

View File

@@ -0,0 +1,48 @@
package android.hardware.bluetooth.V1_0;
public final class Status {
public static final int SUCCESS = 0;
public static final int TRANSPORT_ERROR = 1 /* ::android::hardware::bluetooth::V1_0::Status.SUCCESS implicitly + 1 */;
public static final int INITIALIZATION_ERROR = 2 /* ::android::hardware::bluetooth::V1_0::Status.TRANSPORT_ERROR implicitly + 1 */;
public static final int UNKNOWN = 3 /* ::android::hardware::bluetooth::V1_0::Status.INITIALIZATION_ERROR implicitly + 1 */;
public static final String toString(int o) {
if (o == SUCCESS) {
return "SUCCESS";
}
if (o == TRANSPORT_ERROR) {
return "TRANSPORT_ERROR";
}
if (o == INITIALIZATION_ERROR) {
return "INITIALIZATION_ERROR";
}
if (o == UNKNOWN) {
return "UNKNOWN";
}
return "0x" + Integer.toHexString(o);
}
public static final String dumpBitfield(int o) {
java.util.ArrayList<String> list = new java.util.ArrayList<>();
int flipped = 0;
list.add("SUCCESS"); // SUCCESS == 0
if ((o & TRANSPORT_ERROR) == TRANSPORT_ERROR) {
list.add("TRANSPORT_ERROR");
flipped |= TRANSPORT_ERROR;
}
if ((o & INITIALIZATION_ERROR) == INITIALIZATION_ERROR) {
list.add("INITIALIZATION_ERROR");
flipped |= INITIALIZATION_ERROR;
}
if ((o & UNKNOWN) == UNKNOWN) {
list.add("UNKNOWN");
flipped |= UNKNOWN;
}
if (o != flipped) {
list.add("0x" + Integer.toHexString(o & (~flipped)));
}
return String.join(" | ", list);
}
};

View File

@@ -0,0 +1,840 @@
package android.hardware.bluetooth.V1_1;
import android.os.HidlSupport;
import android.os.HwBinder;
import android.os.IHwBinder;
import android.os.HwBlob;
import android.os.HwParcel;
import android.os.IHwInterface;
import android.os.NativeHandle;
/**
* The Host Controller Interface (HCI) is the layer defined by the Bluetooth
* specification between the software that runs on the host and the Bluetooth
* controller chip. This boundary is the natural choice for a Hardware
* Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
* the stack and abstracts away power management, initialization, and other
* implementation-specific details related to the hardware.
*/
public interface IBluetoothHci extends android.hardware.bluetooth.V1_0.IBluetoothHci {
/**
* Fully-qualified interface name for this interface.
*/
public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHci";
/**
* Does a checked conversion from a binder to this class.
*/
/* package private */ static IBluetoothHci asInterface(IHwBinder binder) {
if (binder == null) {
return null;
}
IHwInterface iface =
binder.queryLocalInterface(kInterfaceName);
if ((iface != null) && (iface instanceof IBluetoothHci)) {
return (IBluetoothHci)iface;
}
IBluetoothHci proxy = new IBluetoothHci.Proxy(binder);
try {
for (String descriptor : proxy.interfaceChain()) {
if (descriptor.equals(kInterfaceName)) {
return proxy;
}
}
} catch (android.os.RemoteException e) {
}
return null;
}
/**
* Does a checked conversion from any interface to this class.
*/
public static IBluetoothHci castFrom(IHwInterface iface) {
return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder());
}
@Override
public IHwBinder asBinder();
/**
* This will invoke the equivalent of the C++ getService(std::string) if retry is
* true or tryGetService(std::string) if retry is false. If the service is
* available on the device and retry is true, this will wait for the service to
* start.
*
*/
public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException {
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName, retry));
}
/**
* Calls getService("default",retry).
*/
public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException {
return getService("default", retry);
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(String,boolean) instead.
*/
@Deprecated
public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException {
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName));
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(boolean) instead.
*/
@Deprecated
public static IBluetoothHci getService() throws android.os.RemoteException {
return getService("default");
}
/**
* Same as @1.0, but uses 1.1 Callbacks version
*/
void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback)
throws android.os.RemoteException;
/**
* Send an ISO data packet (as specified in the Bluetooth Core
* Specification v5.2) to the Bluetooth controller.
* Packets must be processed in order.
* @param data HCI data packet to be sent
*/
void sendIsoData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* ["android.hardware.foo@1.0::IChild",
* "android.hardware.foo@1.0::IParent"
* "android.internal.hidl.base@1.0::IBase"]
*
* @return descriptors a vector of descriptors of the run-time type of the
* object.
*/
java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException;
/*
* Emit diagnostic information to the given file.
*
* Optionally overriden.
*
* @param fd File descriptor to dump data to.
* Must only be used for the duration of this call.
* @param options Arguments for debugging.
* Must support empty for default debug information.
*/
void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceDescriptor on an IChild object must yield
* "android.hardware.foo@1.0::IChild"
*
* @return descriptor a descriptor of the run-time type of the
* object (the first element of the vector returned by
* interfaceChain())
*/
String interfaceDescriptor()
throws android.os.RemoteException;
/*
* Returns hashes of the source HAL files that define the interfaces of the
* runtime type information on the object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* [(hash of IChild.hal),
* (hash of IParent.hal)
* (hash of IBase.hal)].
*
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
* according to SHA-256 standard.
*
* @return hashchain a vector of SHA-1 digests
*/
java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException;
/*
* This method trigger the interface to enable/disable instrumentation based
* on system property hal.instrumentation.enable.
*/
void setHALInstrumentation()
throws android.os.RemoteException;
/*
* Registers a death recipient, to be called when the process hosting this
* interface dies.
*
* @param recipient a hidl_death_recipient callback object
* @param cookie a cookie that must be returned with the callback
* @return success whether the death recipient was registered successfully.
*/
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException;
/*
* Provides way to determine if interface is running without requesting
* any functionality.
*/
void ping()
throws android.os.RemoteException;
/*
* Get debug information on references on this interface.
* @return info debugging information. See comments of DebugInfo.
*/
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException;
/*
* This method notifies the interface that one or more system properties
* have changed. The default implementation calls
* (C++) report_sysprop_change() in libcutils or
* (Java) android.os.SystemProperties.reportSyspropChanged,
* which in turn calls a set of registered callbacks (eg to update trace
* tags).
*/
void notifySyspropsChanged()
throws android.os.RemoteException;
/*
* Unregisters the registered death recipient. If this service was registered
* multiple times with the same exact death recipient, this unlinks the most
* recently registered one.
*
* @param recipient a previously registered hidl_death_recipient callback
* @return success whether the death recipient was unregistered successfully.
*/
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException;
public static final class Proxy implements IBluetoothHci {
private IHwBinder mRemote;
public Proxy(IHwBinder remote) {
mRemote = java.util.Objects.requireNonNull(remote);
}
@Override
public IHwBinder asBinder() {
return mRemote;
}
@Override
public String toString() {
try {
return this.interfaceDescriptor() + "@Proxy";
} catch (android.os.RemoteException ex) {
/* ignored; handled below. */
}
return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy";
}
@Override
public final boolean equals(java.lang.Object other) {
return HidlSupport.interfacesEqual(this, other);
}
@Override
public final int hashCode() {
return this.asBinder().hashCode();
}
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow.
@Override
public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void sendHciCommand(java.util.ArrayList<Byte> command)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeInt8Vector(command);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void sendAclData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void sendScoData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void close()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
// Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHci follow.
@Override
public void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
_hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(6 /* initialize_1_1 */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void sendIsoData(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(7 /* sendIsoData */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
@Override
public java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
return _hidl_out_descriptors;
} finally {
_hidl_reply.release();
}
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
_hidl_request.writeNativeHandle(fd);
_hidl_request.writeStringVector(options);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public String interfaceDescriptor()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
String _hidl_out_descriptor = _hidl_reply.readString();
return _hidl_out_descriptor;
} finally {
_hidl_reply.release();
}
}
@Override
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
{
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
{
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
_hidl_vec_size * 32,_hidl_blob.handle(),
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
byte[/* 32 */] _hidl_vec_element = new byte[32];
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
_hidl_array_offset_1 += 32 * 1;
}
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
}
}
}
return _hidl_out_hashchain;
} finally {
_hidl_reply.release();
}
}
@Override
public void setHALInstrumentation()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException {
return mRemote.linkToDeath(recipient, cookie);
}
@Override
public void ping()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
return _hidl_out_info;
} finally {
_hidl_reply.release();
}
}
@Override
public void notifySyspropsChanged()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException {
return mRemote.unlinkToDeath(recipient);
}
}
public static abstract class Stub extends HwBinder implements IBluetoothHci {
@Override
public IHwBinder asBinder() {
return this;
}
@Override
public final java.util.ArrayList<String> interfaceChain() {
return new java.util.ArrayList<String>(java.util.Arrays.asList(
android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName,
android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName,
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
return;
}
@Override
public final String interfaceDescriptor() {
return android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName;
}
@Override
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
new byte[/* 32 */]{54,47,-47,-62,22,65,-62,34,79,59,-128,-61,13,-105,-105,-71,-120,-6,63,52,66,67,-43,49,-70,115,-59,83,119,-102,87,99} /* 362fd1c21641c2224f3b80c30d9797b988fa3f344243d531ba73c553779a5763 */,
new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */,
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
}
@Override
public final void setHALInstrumentation() {
}
@Override
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
return true;
}
@Override
public final void ping() {
return;
}
@Override
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
info.pid = HidlSupport.getPidIfSharable();
info.ptr = 0;
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
return info;
}
@Override
public final void notifySyspropsChanged() {
HwBinder.enableInstrumentation();
}
@Override
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
return true;
}
@Override
public IHwInterface queryLocalInterface(String descriptor) {
if (kInterfaceName.equals(descriptor)) {
return this;
}
return null;
}
public void registerAsService(String serviceName) throws android.os.RemoteException {
registerService(serviceName);
}
@Override
public String toString() {
return this.interfaceDescriptor() + "@Stub";
}
//@Override
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
throws android.os.RemoteException {
switch (_hidl_code) {
case 1 /* initialize */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
initialize(callback);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 2 /* sendHciCommand */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
java.util.ArrayList<Byte> command = _hidl_request.readInt8Vector();
sendHciCommand(command);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 3 /* sendAclData */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
sendAclData(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 4 /* sendScoData */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
sendScoData(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 5 /* close */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
close();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 6 /* initialize_1_1 */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
initialize_1_1(callback);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 7 /* sendIsoData */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
sendIsoData(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256067662 /* interfaceChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeStringVector(_hidl_out_descriptors);
_hidl_reply.send();
break;
}
case 256131655 /* debug */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
NativeHandle fd = _hidl_request.readNativeHandle();
java.util.ArrayList<String> options = _hidl_request.readStringVector();
debug(fd, options);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256136003 /* interfaceDescriptor */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
String _hidl_out_descriptor = interfaceDescriptor();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeString(_hidl_out_descriptor);
_hidl_reply.send();
break;
}
case 256398152 /* getHashChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
{
HwBlob _hidl_blob = new HwBlob(16 /* size */);
{
int _hidl_vec_size = _hidl_out_hashchain.size();
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
throw new IllegalArgumentException("Array element is not of the expected length");
}
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
_hidl_array_offset_1 += 32 * 1;
}
}
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
}
_hidl_reply.writeBuffer(_hidl_blob);
}
_hidl_reply.send();
break;
}
case 256462420 /* setHALInstrumentation */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
setHALInstrumentation();
break;
}
case 256660548 /* linkToDeath */:
{
break;
}
case 256921159 /* ping */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
ping();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 257049926 /* getDebugInfo */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
_hidl_reply.send();
break;
}
case 257120595 /* notifySyspropsChanged */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
notifySyspropsChanged();
break;
}
case 257250372 /* unlinkToDeath */:
{
break;
}
}
}
}
}

View File

@@ -0,0 +1,774 @@
package android.hardware.bluetooth.V1_1;
import android.os.HidlSupport;
import android.os.HwBinder;
import android.os.IHwBinder;
import android.os.HwBlob;
import android.os.HwParcel;
import android.os.IHwInterface;
import android.os.NativeHandle;
/**
* The interface from the Bluetooth Controller to the stack.
*/
public interface IBluetoothHciCallbacks extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks {
/**
* Fully-qualified interface name for this interface.
*/
public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHciCallbacks";
/**
* Does a checked conversion from a binder to this class.
*/
/* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) {
if (binder == null) {
return null;
}
IHwInterface iface =
binder.queryLocalInterface(kInterfaceName);
if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) {
return (IBluetoothHciCallbacks)iface;
}
IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder);
try {
for (String descriptor : proxy.interfaceChain()) {
if (descriptor.equals(kInterfaceName)) {
return proxy;
}
}
} catch (android.os.RemoteException e) {
}
return null;
}
/**
* Does a checked conversion from any interface to this class.
*/
public static IBluetoothHciCallbacks castFrom(IHwInterface iface) {
return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder());
}
@Override
public IHwBinder asBinder();
/**
* This will invoke the equivalent of the C++ getService(std::string) if retry is
* true or tryGetService(std::string) if retry is false. If the service is
* available on the device and retry is true, this will wait for the service to
* start.
*
*/
public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException {
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName, retry));
}
/**
* Calls getService("default",retry).
*/
public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException {
return getService("default", retry);
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(String,boolean) instead.
*/
@Deprecated
public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException {
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName));
}
/**
* @deprecated this will not wait for the interface to come up if it hasn't yet
* started. See getService(boolean) instead.
*/
@Deprecated
public static IBluetoothHciCallbacks getService() throws android.os.RemoteException {
return getService("default");
}
/**
* Send a ISO data packet form the controller to the host.
* @param data the ISO HCI packet to be passed to the host stack
*/
void isoDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* ["android.hardware.foo@1.0::IChild",
* "android.hardware.foo@1.0::IParent"
* "android.internal.hidl.base@1.0::IBase"]
*
* @return descriptors a vector of descriptors of the run-time type of the
* object.
*/
java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException;
/*
* Emit diagnostic information to the given file.
*
* Optionally overriden.
*
* @param fd File descriptor to dump data to.
* Must only be used for the duration of this call.
* @param options Arguments for debugging.
* Must support empty for default debug information.
*/
void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException;
/*
* Provides run-time type information for this object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceDescriptor on an IChild object must yield
* "android.hardware.foo@1.0::IChild"
*
* @return descriptor a descriptor of the run-time type of the
* object (the first element of the vector returned by
* interfaceChain())
*/
String interfaceDescriptor()
throws android.os.RemoteException;
/*
* Returns hashes of the source HAL files that define the interfaces of the
* runtime type information on the object.
* For example, for the following interface definition:
* package android.hardware.foo@1.0;
* interface IParent {};
* interface IChild extends IParent {};
* Calling interfaceChain on an IChild object must yield the following:
* [(hash of IChild.hal),
* (hash of IParent.hal)
* (hash of IBase.hal)].
*
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
* according to SHA-256 standard.
*
* @return hashchain a vector of SHA-1 digests
*/
java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException;
/*
* This method trigger the interface to enable/disable instrumentation based
* on system property hal.instrumentation.enable.
*/
void setHALInstrumentation()
throws android.os.RemoteException;
/*
* Registers a death recipient, to be called when the process hosting this
* interface dies.
*
* @param recipient a hidl_death_recipient callback object
* @param cookie a cookie that must be returned with the callback
* @return success whether the death recipient was registered successfully.
*/
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException;
/*
* Provides way to determine if interface is running without requesting
* any functionality.
*/
void ping()
throws android.os.RemoteException;
/*
* Get debug information on references on this interface.
* @return info debugging information. See comments of DebugInfo.
*/
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException;
/*
* This method notifies the interface that one or more system properties
* have changed. The default implementation calls
* (C++) report_sysprop_change() in libcutils or
* (Java) android.os.SystemProperties.reportSyspropChanged,
* which in turn calls a set of registered callbacks (eg to update trace
* tags).
*/
void notifySyspropsChanged()
throws android.os.RemoteException;
/*
* Unregisters the registered death recipient. If this service was registered
* multiple times with the same exact death recipient, this unlinks the most
* recently registered one.
*
* @param recipient a previously registered hidl_death_recipient callback
* @return success whether the death recipient was unregistered successfully.
*/
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException;
public static final class Proxy implements IBluetoothHciCallbacks {
private IHwBinder mRemote;
public Proxy(IHwBinder remote) {
mRemote = java.util.Objects.requireNonNull(remote);
}
@Override
public IHwBinder asBinder() {
return mRemote;
}
@Override
public String toString() {
try {
return this.interfaceDescriptor() + "@Proxy";
} catch (android.os.RemoteException ex) {
/* ignored; handled below. */
}
return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy";
}
@Override
public final boolean equals(java.lang.Object other) {
return HidlSupport.interfacesEqual(this, other);
}
@Override
public final int hashCode() {
return this.asBinder().hashCode();
}
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow.
@Override
public void initializationComplete(int status)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt32(status);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void hciEventReceived(java.util.ArrayList<Byte> event)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt8Vector(event);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void aclDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public void scoDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
// Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHciCallbacks follow.
@Override
public void isoDataReceived(java.util.ArrayList<Byte> data)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName);
_hidl_request.writeInt8Vector(data);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(5 /* isoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
@Override
public java.util.ArrayList<String> interfaceChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
return _hidl_out_descriptors;
} finally {
_hidl_reply.release();
}
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
_hidl_request.writeNativeHandle(fd);
_hidl_request.writeStringVector(options);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public String interfaceDescriptor()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
String _hidl_out_descriptor = _hidl_reply.readString();
return _hidl_out_descriptor;
} finally {
_hidl_reply.release();
}
}
@Override
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
{
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
{
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
_hidl_vec_size * 32,_hidl_blob.handle(),
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
byte[/* 32 */] _hidl_vec_element = new byte[32];
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
_hidl_array_offset_1 += 32 * 1;
}
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
}
}
}
return _hidl_out_hashchain;
} finally {
_hidl_reply.release();
}
}
@Override
public void setHALInstrumentation()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
throws android.os.RemoteException {
return mRemote.linkToDeath(recipient, cookie);
}
@Override
public void ping()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
_hidl_reply.verifySuccess();
_hidl_request.releaseTemporaryStorage();
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
return _hidl_out_info;
} finally {
_hidl_reply.release();
}
}
@Override
public void notifySyspropsChanged()
throws android.os.RemoteException {
HwParcel _hidl_request = new HwParcel();
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
HwParcel _hidl_reply = new HwParcel();
try {
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
_hidl_request.releaseTemporaryStorage();
} finally {
_hidl_reply.release();
}
}
@Override
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
throws android.os.RemoteException {
return mRemote.unlinkToDeath(recipient);
}
}
public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks {
@Override
public IHwBinder asBinder() {
return this;
}
@Override
public final java.util.ArrayList<String> interfaceChain() {
return new java.util.ArrayList<String>(java.util.Arrays.asList(
android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName,
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName,
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
}
@Override
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
return;
}
@Override
public final String interfaceDescriptor() {
return android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName;
}
@Override
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
new byte[/* 32 */]{64,-85,44,104,102,-63,-115,50,-70,-10,-28,-98,48,83,-108,-98,121,96,31,86,-106,58,121,30,-109,-26,-117,-98,-31,-113,113,-115} /* 40ab2c6866c18d32baf6e49e3053949e79601f56963a791e93e68b9ee18f718d */,
new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */,
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
}
@Override
public final void setHALInstrumentation() {
}
@Override
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
return true;
}
@Override
public final void ping() {
return;
}
@Override
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
info.pid = HidlSupport.getPidIfSharable();
info.ptr = 0;
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
return info;
}
@Override
public final void notifySyspropsChanged() {
HwBinder.enableInstrumentation();
}
@Override
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
return true;
}
@Override
public IHwInterface queryLocalInterface(String descriptor) {
if (kInterfaceName.equals(descriptor)) {
return this;
}
return null;
}
public void registerAsService(String serviceName) throws android.os.RemoteException {
registerService(serviceName);
}
@Override
public String toString() {
return this.interfaceDescriptor() + "@Stub";
}
//@Override
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
throws android.os.RemoteException {
switch (_hidl_code) {
case 1 /* initializationComplete */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
int status = _hidl_request.readInt32();
initializationComplete(status);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 2 /* hciEventReceived */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
java.util.ArrayList<Byte> event = _hidl_request.readInt8Vector();
hciEventReceived(event);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 3 /* aclDataReceived */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
aclDataReceived(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 4 /* scoDataReceived */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
scoDataReceived(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 5 /* isoDataReceived */:
{
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName);
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
isoDataReceived(data);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256067662 /* interfaceChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeStringVector(_hidl_out_descriptors);
_hidl_reply.send();
break;
}
case 256131655 /* debug */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
NativeHandle fd = _hidl_request.readNativeHandle();
java.util.ArrayList<String> options = _hidl_request.readStringVector();
debug(fd, options);
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 256136003 /* interfaceDescriptor */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
String _hidl_out_descriptor = interfaceDescriptor();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.writeString(_hidl_out_descriptor);
_hidl_reply.send();
break;
}
case 256398152 /* getHashChain */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
{
HwBlob _hidl_blob = new HwBlob(16 /* size */);
{
int _hidl_vec_size = _hidl_out_hashchain.size();
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
{
long _hidl_array_offset_1 = _hidl_index_0 * 32;
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
throw new IllegalArgumentException("Array element is not of the expected length");
}
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
_hidl_array_offset_1 += 32 * 1;
}
}
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
}
_hidl_reply.writeBuffer(_hidl_blob);
}
_hidl_reply.send();
break;
}
case 256462420 /* setHALInstrumentation */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
setHALInstrumentation();
break;
}
case 256660548 /* linkToDeath */:
{
break;
}
case 256921159 /* ping */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
ping();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
_hidl_reply.send();
break;
}
case 257049926 /* getDebugInfo */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
_hidl_reply.send();
break;
}
case 257120595 /* notifySyspropsChanged */:
{
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
notifySyspropsChanged();
break;
}
case 257250372 /* unlinkToDeath */:
{
break;
}
}
}
}
}

View File

@@ -0,0 +1,260 @@
package com.github.google.bumble.remotehci;
import android.hardware.bluetooth.V1_0.Status;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.util.ArrayList;
import java.util.NoSuchElementException;
public interface HciHal {
public enum Status {
SUCCESS("SUCCESS"),
ALREADY_INITIALIZED("ALREADY_INITIALIZED"),
UNABLE_TO_OPEN_INTERFACE("UNABLE_TO_OPEN_INTERFACE"),
INITIALIZATION_ERROR("INITIALIZATION_ERROR"),
TRANSPORT_ERROR("TRANSPORT_ERROR"),
UNKNOWN("UNKNOWN");
public final String label;
private Status(String label) {
this.label = label;
}
}
static final String TAG = "HciHal";
public static HciHal create(HciHalCallback hciCallbacks) {
// First try HIDL
HciHal hciHal = HciHidlHal.create(hciCallbacks);
if (hciHal != null) {
Log.d(TAG, "Found HIDL HAL");
return hciHal;
}
// Then try AIDL
hciHal = HciAidlHal.create(hciCallbacks);
if (hciHal != null) {
Log.d(TAG, "Found AIDL HAL");
return hciHal;
}
Log.d(TAG, "No HAL found");
return null;
}
public Status initialize() throws RemoteException, InterruptedException;
public void sendPacket(HciPacket.Type type, byte[] packet);
}
class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.Stub implements HciHal {
private static final String TAG = "HciHidlHal";
private final android.hardware.bluetooth.V1_0.IBluetoothHci mHciService;
private final HciHalCallback mHciCallbacks;
private int mInitializationStatus = -1;
public static HciHidlHal create(HciHalCallback hciCallbacks) {
// Get the HAL service.
android.hardware.bluetooth.V1_0.IBluetoothHci hciService;
try {
hciService = android.hardware.bluetooth.V1_0.IBluetoothHci.getService(true);
} catch (NoSuchElementException e) {
Log.d(TAG, "HIDL HAL V1.0 not found");
return null;
} catch (android.os.RemoteException e) {
Log.w(TAG, "Exception from getService: " + e);
return null;
}
Log.d(TAG, "Found HIDL HAL V1.0");
return new HciHidlHal(hciService, hciCallbacks);
}
private HciHidlHal(android.hardware.bluetooth.V1_0.IBluetoothHci hciService, HciHalCallback hciCallbacks) {
mHciService = hciService;
mHciCallbacks = hciCallbacks;
}
public Status initialize() throws RemoteException, InterruptedException {
// Trigger the initialization.
mHciService.initialize(this);
// Wait for the initialization to complete.
Log.d(TAG, "Waiting for initialization status...");
synchronized (this) {
while (mInitializationStatus == -1) {
wait();
}
}
// Map the status code.
switch (mInitializationStatus) {
case android.hardware.bluetooth.V1_0.Status.SUCCESS:
return Status.SUCCESS;
case android.hardware.bluetooth.V1_0.Status.TRANSPORT_ERROR:
return Status.TRANSPORT_ERROR;
case android.hardware.bluetooth.V1_0.Status.INITIALIZATION_ERROR:
return Status.INITIALIZATION_ERROR;
default:
return Status.UNKNOWN;
}
}
@Override
public void sendPacket(HciPacket.Type type, byte[] packet) {
ArrayList<Byte> data = HciPacket.byteArrayToList(packet);
try {
switch (type) {
case COMMAND:
mHciService.sendHciCommand(data);
break;
case ACL_DATA:
mHciService.sendAclData(data);
break;
case SCO_DATA:
mHciService.sendScoData(data);
break;
}
} catch (RemoteException error) {
Log.w(TAG, "failed to forward packet: " + error);
}
}
@Override
public synchronized void initializationComplete(int status) throws RemoteException {
mInitializationStatus = status;
notifyAll();
}
@Override
public void hciEventReceived(ArrayList<Byte> event) throws RemoteException {
byte[] packet = HciPacket.listToByteArray(event);
mHciCallbacks.onPacket(HciPacket.Type.EVENT, packet);
}
@Override
public void aclDataReceived(ArrayList<Byte> data) throws RemoteException {
byte[] packet = HciPacket.listToByteArray(data);
mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, packet);
}
@Override
public void scoDataReceived(ArrayList<Byte> data) throws RemoteException {
byte[] packet = HciPacket.listToByteArray(data);
mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, packet);
}
}
class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub implements HciHal {
private static final String TAG = "HciAidlHal";
private final android.hardware.bluetooth.IBluetoothHci mHciService;
private final HciHalCallback mHciCallbacks;
private int mInitializationStatus = android.hardware.bluetooth.Status.SUCCESS;
public static HciAidlHal create(HciHalCallback hciCallbacks) {
IBinder binder = ServiceManager.getService("android.hardware.bluetooth.IBluetoothHci/default");
if (binder == null) {
Log.d(TAG, "AIDL HAL not found");
return null;
}
android.hardware.bluetooth.IBluetoothHci hciService = android.hardware.bluetooth.IBluetoothHci.Stub.asInterface(binder);
return new HciAidlHal(hciService, hciCallbacks);
}
private HciAidlHal(android.hardware.bluetooth.IBluetoothHci hciService, HciHalCallback hciCallbacks) {
super();
mHciService = hciService;
mHciCallbacks = hciCallbacks;
}
public Status initialize() throws RemoteException, InterruptedException {
// Trigger the initialization.
mHciService.initialize(this);
// Wait for the initialization to complete.
Log.d(TAG, "Waiting for initialization status...");
synchronized (this) {
while (mInitializationStatus == -1) {
wait();
}
}
// Map the status code.
switch (mInitializationStatus) {
case android.hardware.bluetooth.Status.SUCCESS:
return Status.SUCCESS;
case android.hardware.bluetooth.Status.ALREADY_INITIALIZED:
return Status.ALREADY_INITIALIZED;
case android.hardware.bluetooth.Status.UNABLE_TO_OPEN_INTERFACE:
return Status.UNABLE_TO_OPEN_INTERFACE;
case android.hardware.bluetooth.Status.HARDWARE_INITIALIZATION_ERROR:
return Status.INITIALIZATION_ERROR;
default:
return Status.UNKNOWN;
}
}
// HciHal methods.
@Override
public void sendPacket(HciPacket.Type type, byte[] packet) {
try {
switch (type) {
case COMMAND:
mHciService.sendHciCommand(packet);
break;
case ACL_DATA:
mHciService.sendAclData(packet);
break;
case SCO_DATA:
mHciService.sendScoData(packet);
break;
case ISO_DATA:
mHciService.sendIsoData(packet);
break;
}
} catch (RemoteException error) {
Log.w(TAG, "failed to forward packet: " + error);
}
}
// IBluetoothHciCallbacks methods.
@Override
public synchronized void initializationComplete(int status) throws RemoteException {
mInitializationStatus = status;
notifyAll();
}
@Override
public void hciEventReceived(byte[] event) throws RemoteException {
mHciCallbacks.onPacket(HciPacket.Type.EVENT, event);
}
@Override
public void aclDataReceived(byte[] data) throws RemoteException {
mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, data);
}
@Override
public void scoDataReceived(byte[] data) throws RemoteException {
mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, data);
}
@Override
public void isoDataReceived(byte[] data) throws RemoteException {
mHciCallbacks.onPacket(HciPacket.Type.ISO_DATA, data);
}
}

View File

@@ -0,0 +1,5 @@
package com.github.google.bumble.remotehci;
public interface HciHalCallback {
public void onPacket(HciPacket.Type type, byte[] packet);
}

View File

@@ -0,0 +1,69 @@
package com.github.google.bumble.remotehci;
import java.util.ArrayList;
public class HciPacket {
public enum Type {
COMMAND((byte) 1),
ACL_DATA((byte) 2),
SCO_DATA((byte) 3),
EVENT((byte) 4),
ISO_DATA((byte)5);
final byte value;
final int lengthSize;
final int lengthOffset;
Type(byte value) throws IllegalArgumentException {
switch (value) {
case 1:
case 3:
lengthSize = 1;
lengthOffset = 2;
break;
case 2:
case 5:
lengthSize = 2;
lengthOffset = 2;
break;
case 4:
lengthSize = 1;
lengthOffset = 1;
break;
default:
throw new IllegalArgumentException();
}
this.value = value;
}
static Type fromValue(byte value) {
for (Type type : values()) {
if (type.value == value) {
return type;
}
}
return null;
}
}
public static ArrayList<Byte> byteArrayToList(byte[] byteArray) {
ArrayList<Byte> list = new ArrayList<>();
list.ensureCapacity(byteArray.length);
for (byte x : byteArray) {
list.add(x);
}
return list;
}
public static byte[] listToByteArray(ArrayList<Byte> byteList) {
byte[] byteArray = new byte[byteList.size()];
for (int i = 0; i < byteList.size(); i++) {
byteArray[i] = byteList.get(i);
}
return byteArray;
}
}

View File

@@ -0,0 +1,83 @@
package com.github.google.bumble.remotehci;
import static java.lang.Integer.min;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
class HciParser {
Sink sink;
State state;
int bytesNeeded;
ByteArrayOutputStream packet = new ByteArrayOutputStream();
HciPacket.Type packetType;
HciParser(Sink sink) {
this.sink = sink;
reset();
}
void feedData(byte[] data, int dataSize) {
int dataOffset = 0;
int dataLeft = dataSize;
while (dataLeft > 0 && bytesNeeded > 0) {
int consumed = min(dataLeft, bytesNeeded);
if (state != State.NEED_TYPE) {
packet.write(data, dataOffset, consumed);
}
bytesNeeded -= consumed;
if (bytesNeeded == 0) {
if (state == State.NEED_TYPE) {
packetType = HciPacket.Type.fromValue(data[dataOffset]);
if (packetType == null) {
throw new InvalidFormatException();
}
bytesNeeded = packetType.lengthOffset + packetType.lengthSize;
state = State.NEED_LENGTH;
} else if (state == State.NEED_LENGTH) {
ByteBuffer lengthBuffer =
ByteBuffer.wrap(packet.toByteArray())
.order(ByteOrder.LITTLE_ENDIAN);
bytesNeeded = packetType.lengthSize == 1 ?
lengthBuffer.get(packetType.lengthOffset) & 0xFF :
lengthBuffer.getShort(packetType.lengthOffset) & 0xFFFF;
state = State.NEED_BODY;
}
// Emit a packet if one is complete.
if (state == State.NEED_BODY && bytesNeeded == 0) {
if (sink != null) {
sink.onPacket(packetType, packet.toByteArray());
}
reset();
}
}
dataOffset += consumed;
dataLeft -= consumed;
}
}
void reset() {
state = State.NEED_TYPE;
bytesNeeded = 1;
packet.reset();
packetType = null;
}
enum State {
NEED_TYPE, NEED_LENGTH, NEED_BODY
}
interface Sink {
void onPacket(HciPacket.Type type, byte[] packet);
}
static class InvalidFormatException extends RuntimeException {
}
}

View File

@@ -0,0 +1,143 @@
package com.github.google.bumble.remotehci;
import android.os.RemoteException;
import android.util.Log;
import java.io.IOException;
public class HciProxy {
private static final String TAG = "HciProxy";
private final HciServer mServer;
private final Listener mListener;
private int mCommandPacketsReceived;
private int mAclPacketsReceived;
private int mScoPacketsReceived;
private int mEventPacketsSent;
private int mAclPacketsSent;
private int mScoPacketsSent;
HciProxy(int port, Listener listener) throws HalException {
this.mListener = listener;
// Instantiate a HAL to communicate with the hardware.
HciHal hciHal = HciHal.create(new HciHalCallback() {
@Override
public void onPacket(HciPacket.Type type, byte[] packet) {
mServer.sendPacket(type, packet);
switch (type) {
case EVENT:
mEventPacketsSent += 1;
break;
case ACL_DATA:
mAclPacketsSent += 1;
break;
case SCO_DATA:
mScoPacketsSent += 1;
break;
}
updateHciPacketCount();
}
});
if (hciHal == null) {
String message = "Could not instantiate a HAL instance";
Log.w(TAG, message);
throw new HalException(message);
}
// Initialize the HAL.
HciHal.Status status = null;
try {
status = hciHal.initialize();
} catch (RemoteException | InterruptedException e) {
throw new HalException("Exception while initializing");
}
if (status != HciHal.Status.SUCCESS) {
String message = "HAL initialization failed: " + status.label;
Log.w(TAG, message);
throw new HalException(message);
}
// Create a server to accept clients.
mServer = new HciServer(port, new HciServer.Listener() {
@Override
public void onHostConnectionState(boolean connected) {
mListener.onHostConnectionState(connected);
if (connected) {
mCommandPacketsReceived = 0;
mAclPacketsReceived = 0;
mScoPacketsReceived = 0;
mEventPacketsSent = 0;
mAclPacketsSent = 0;
mScoPacketsSent = 0;
updateHciPacketCount();
}
}
@Override
public void onMessage(String message) {
listener.onMessage(message);
}
@Override
public void onPacket(HciPacket.Type type, byte[] packet) {
Log.d(TAG, String.format("onPacket: type=%s, size=%d", type, packet.length));
hciHal.sendPacket(type, packet);
switch (type) {
case COMMAND:
mCommandPacketsReceived += 1;
break;
case ACL_DATA:
mAclPacketsReceived += 1;
break;
case SCO_DATA:
mScoPacketsReceived += 1;
break;
}
updateHciPacketCount();
}
});
}
public void run() throws IOException {
mServer.run();
}
private void updateHciPacketCount() {
mListener.onHciPacketCountChange(
mCommandPacketsReceived,
mAclPacketsReceived,
mScoPacketsReceived,
mEventPacketsSent,
mAclPacketsSent,
mScoPacketsSent
);
}
public interface Listener {
void onHostConnectionState(boolean connected);
void onHciPacketCountChange(
int commandPacketsReceived,
int aclPacketsReceived,
int scoPacketsReceived,
int eventPacketsSent,
int aclPacketsSent,
int scoPacketsSent
);
void onMessage(String message);
}
public static class HalException extends RuntimeException {
public final String message;
public HalException(String message) {
this.message = message;
}
}
}

View File

@@ -0,0 +1,92 @@
package com.github.google.bumble.remotehci;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class HciServer {
private static final String TAG = "HciServer";
private static final int BUFFER_SIZE = 1024;
private final int mPort;
private final Listener mListener;
private OutputStream mOutputStream;
public interface Listener extends HciParser.Sink {
void onHostConnectionState(boolean connected);
void onMessage(String message);
}
HciServer(int port, Listener listener) {
this.mPort = port;
this.mListener = listener;
}
public void run() throws IOException {
for (;;) {
try {
loop();
} catch (IOException error) {
mListener.onMessage("Cannot listen on port " + mPort);
return;
}
}
}
private void loop() throws IOException {
mListener.onHostConnectionState(false);
try (ServerSocket serverSocket = new ServerSocket(mPort)) {
mListener.onMessage("Waiting for connection on port " + serverSocket.getLocalPort());
try (Socket clientSocket = serverSocket.accept()) {
mListener.onHostConnectionState(true);
mListener.onMessage("Connected");
HciParser parser = new HciParser(mListener);
InputStream inputStream = clientSocket.getInputStream();
synchronized (this) {
mOutputStream = clientSocket.getOutputStream();
}
byte[] buffer = new byte[BUFFER_SIZE];
try {
for (; ; ) {
int bytesRead = inputStream.read(buffer);
if (bytesRead < 0) {
Log.d(TAG, "end of stream");
break;
}
parser.feedData(buffer, bytesRead);
}
} catch (IOException error) {
Log.d(TAG, "exception in read loop: " + error);
}
}
} finally {
synchronized (this) {
mOutputStream = null;
}
}
}
public void sendPacket(HciPacket.Type type, byte[] packet) {
// Create a combined data buffer so we can write it out in a single call.
byte[] data = new byte[packet.length + 1];
data[0] = type.value;
System.arraycopy(packet, 0, data, 1, packet.length);
synchronized (this) {
if (mOutputStream != null) {
try {
mOutputStream.write(data);
} catch (IOException error) {
Log.w(TAG, "failed to write packet: " + error);
}
} else {
Log.d(TAG, "no client, dropping packet");
}
}
}
}

View File

@@ -0,0 +1,229 @@
package com.github.google.bumble.remotehci
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import com.github.google.bumble.remotehci.HciProxy.HalException
import com.github.google.bumble.remotehci.ui.theme.RemoteHCITheme
import java.io.IOException
import java.util.logging.Logger
import kotlin.concurrent.thread
const val DEFAULT_TCP_PORT = 9993
const val TCP_PORT_PREF_KEY = "tcp_port"
class AppViewModel : ViewModel(), HciProxy.Listener {
private var preferences: SharedPreferences? = null
var tcpPort by mutableStateOf(DEFAULT_TCP_PORT)
var canStart by mutableStateOf(true)
var message by mutableStateOf("")
var hostConnected by mutableStateOf(false)
var hciCommandPacketsReceived by mutableStateOf(0)
var hciAclPacketsReceived by mutableStateOf(0)
var hciScoPacketsReceived by mutableStateOf(0)
var hciEventPacketsSent by mutableStateOf(0)
var hciAclPacketsSent by mutableStateOf(0)
var hciScoPacketsSent by mutableStateOf(0)
fun loadPreferences(preferences: SharedPreferences) {
this.preferences = preferences
val savedTcpPortString = preferences.getString(TCP_PORT_PREF_KEY, null)
if (savedTcpPortString != null) {
val savedTcpPortInt = savedTcpPortString.toIntOrNull()
if (savedTcpPortInt != null) {
tcpPort = savedTcpPortInt
}
}
}
fun updateTcpPort(tcpPort: Int) {
this.tcpPort = tcpPort
// Save the port to the preferences
with (preferences!!.edit()) {
putString(TCP_PORT_PREF_KEY, tcpPort.toString())
apply()
}
}
override fun onHostConnectionState(connected: Boolean) {
hostConnected = connected
}
override fun onHciPacketCountChange(
commandPacketsReceived: Int,
aclPacketsReceived: Int,
scoPacketsReceived: Int,
eventPacketsSent: Int,
aclPacketsSent: Int,
scoPacketsSent: Int
) {
hciCommandPacketsReceived = commandPacketsReceived
hciAclPacketsReceived = aclPacketsReceived
hciScoPacketsReceived = scoPacketsReceived
hciEventPacketsSent = eventPacketsSent
hciAclPacketsSent = aclPacketsSent
hciScoPacketsSent = scoPacketsSent
}
override fun onMessage(message: String) {
this.message = message
}
}
class MainActivity : ComponentActivity() {
private val log = Logger.getLogger(MainActivity::class.java.name)
private val appViewModel = AppViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appViewModel.loadPreferences(getPreferences(Context.MODE_PRIVATE))
val tcpPort = intent.getIntExtra("port", -1)
if (tcpPort >= 0) {
appViewModel.tcpPort = tcpPort
}
setContent {
MainView(appViewModel, ::startProxy)
}
if (intent.getBooleanExtra("autostart", false)) {
startProxy()
}
}
private fun startProxy() {
// Run the proxy in a thread.
appViewModel.message = ""
thread {
log.info("HCI Proxy thread starting")
appViewModel.canStart = false
try {
val hciProxy = HciProxy(appViewModel.tcpPort, appViewModel)
hciProxy.run()
} catch (error: IOException) {
log.warning("Exception while running HCI Server: $error")
} catch (error: HalException) {
log.warning("HAL exception: ${error.message}")
appViewModel.message = "Cannot bind to HAL (${error.message}). You may need to use the command 'setenforce 0' in a root adb shell."
}
log.info("HCI Proxy thread ended")
appViewModel.canStart = true
}
}
}
@Composable
fun ActionButton(text: String, onClick: () -> Unit, enabled: Boolean) {
Button(onClick = onClick, enabled = enabled) {
Text(text = text)
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun MainView(appViewModel: AppViewModel, startProxy: () -> Unit) {
RemoteHCITheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
) {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
Text(
text = "Bumble Remote HCI",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
Divider()
Text(
text = appViewModel.message
)
Divider()
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
label = {
Text(text = "TCP Port")
},
value = appViewModel.tcpPort.toString(),
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
onValueChange = {
if (it.isNotEmpty()) {
val tcpPort = it.toIntOrNull()
if (tcpPort != null) {
appViewModel.updateTcpPort(tcpPort)
}
}
},
keyboardActions = KeyboardActions(
onDone = {keyboardController?.hide()}
)
)
Divider()
val connectState = if (appViewModel.hostConnected) "CONNECTED" else "DISCONNECTED"
Text(
text = "HOST: $connectState",
modifier = Modifier.background(color = if (appViewModel.hostConnected) Color.Green else Color.Red),
color = Color.Black
)
Divider()
Text(
text = "Command Packets Received: ${appViewModel.hciCommandPacketsReceived}"
)
Text(
text = "ACL Packets Received: ${appViewModel.hciAclPacketsReceived}"
)
Text(
text = "SCO Packets Received: ${appViewModel.hciScoPacketsReceived}"
)
Text(
text = "Event Packets Sent: ${appViewModel.hciEventPacketsSent}"
)
Text(
text = "ACL Packets Sent: ${appViewModel.hciAclPacketsSent}"
)
Text(
text = "SCO Packets Sent: ${appViewModel.hciScoPacketsSent}"
)
Divider()
ActionButton(
text = "Start", onClick = startProxy, enabled = appViewModel.canStart
)
}
}
}
}

View File

@@ -0,0 +1,11 @@
package com.github.google.bumble.remotehci.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@@ -0,0 +1,70 @@
package com.github.google.bumble.remotehci.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun RemoteHCITheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -0,0 +1,34 @@
package com.github.google.bumble.remotehci.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@@ -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>

View File

@@ -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>

Some files were not shown because too many files have changed in this diff Show More