1from __future__ import annotations
2import asyncio
3import grpc
4import grpc.aio
5import logging
6import struct
7import sys
8
9from bumble.device import Device
10from google.protobuf import empty_pb2  # pytype: disable=pyi-error
11
12from pandora_experimental.hid_grpc_aio import HIDServicer
13
14from bumble.pandora import utils
15from pandora_experimental.hid_pb2 import (
16    ProtocolModeEvent,
17    ReportEvent,
18    PROTOCOL_REPORT_MODE,
19    PROTOCOL_BOOT_MODE,
20    PROTOCOL_UNSUPPORTED_MODE,
21)
22
23from bumble.core import (
24    BT_BR_EDR_TRANSPORT,
25    BT_L2CAP_PROTOCOL_ID,
26    BT_HUMAN_INTERFACE_DEVICE_SERVICE,
27    BT_HIDP_PROTOCOL_ID,
28    UUID,
29    ProtocolError,
30)
31
32from bumble.hci import (
33    HCI_StatusError,
34    HCI_CONNECTION_ALREADY_EXISTS_ERROR,
35    HCI_PAGE_TIMEOUT_ERROR,
36)
37from bumble.hid import (
38    Device as HID_Device,
39    HID_CONTROL_PSM,
40    HID_INTERRUPT_PSM,
41    Message,
42)
43
44from bumble.sdp import (
45    Client as SDP_Client,
46    DataElement,
47    ServiceAttribute,
48    SDP_PUBLIC_BROWSE_ROOT,
49    SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
50    SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
51    SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
52    SDP_ALL_ATTRIBUTES_RANGE,
53    SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID,
54    SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
55    SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
56    SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
57)
58from bumble.utils import AsyncRunner
59
60# -----------------------------------------------------------------------------
61# SDP attributes for Bluetooth HID devices
62SDP_HID_SERVICE_NAME_ATTRIBUTE_ID = 0x0100
63SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID = 0x0101
64SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID = 0x0102
65SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID = 0x0200  # [DEPRECATED]
66SDP_HID_PARSER_VERSION_ATTRIBUTE_ID = 0x0201
67SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID = 0x0202
68SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID = 0x0203
69SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID = 0x0204
70SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID = 0x0205
71SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID = 0x0206
72SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID = 0x0207
73SDP_HID_SDP_DISABLE_ATTRIBUTE_ID = 0x0208  # [DEPRECATED]
74SDP_HID_BATTERY_POWER_ATTRIBUTE_ID = 0x0209
75SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID = 0x020A
76SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID = 0x020B  # DEPRECATED]
77SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID = 0x020C
78SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID = 0x020D
79SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID = 0x020E
80SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID = 0x020F
81SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID = 0x0210
82
83# Refer to HID profile specification v1.1.1, "5.3 Service Discovery Protocol (SDP)" for details
84# HID SDP attribute values
85LANGUAGE = 0x656E  # 0x656E uint16 “en” (English)
86ENCODING = 0x6A  # 0x006A uint16 UTF-8 encoding
87PRIMARY_LANGUAGE_BASE_ID = 0x100  # 0x0100 uint16 PrimaryLanguageBaseID
88VERSION_NUMBER = 0x0101  # 0x0101 uint16 version number (v1.1)
89SERVICE_NAME = b'Bumble HID'
90SERVICE_DESCRIPTION = b'Bumble'
91PROVIDER_NAME = b'Bumble'
92HID_PARSER_VERSION = 0x0111  # uint16 0x0111 (v1.1.1)
93HID_DEVICE_SUBCLASS = 0xC0  # Combo keyboard/pointing device
94HID_COUNTRY_CODE = 0x21  # 0x21 Uint8, USA
95HID_VIRTUAL_CABLE = True  # Virtual cable enabled
96HID_RECONNECT_INITIATE = True  #  Reconnect initiate enabled
97REPORT_DESCRIPTOR_TYPE = 0x22  # 0x22 Type = Report Descriptor
98HID_LANGID_BASE_LANGUAGE = 0x0409  # 0x0409 Language = English (United States)
99HID_LANGID_BASE_BLUETOOTH_STRING_OFFSET = 0x100  # 0x0100 Default
100HID_BATTERY_POWER = True  #  Battery power enabled
101HID_REMOTE_WAKE = True  #  Remote wake enabled
102HID_SUPERVISION_TIMEOUT = 0xC80  # uint16 0xC80 (2s)
103HID_NORMALLY_CONNECTABLE = True  #  Normally connectable enabled
104HID_BOOT_DEVICE = True  #  Boot device support enabled
105HID_SSR_HOST_MAX_LATENCY = 0x640  # uint16 0x640 (1s)
106HID_SSR_HOST_MIN_TIMEOUT = 0xC80  # uint16 0xC80 (2s)
107HID_REPORT_MAP = bytes(  # Text String, 50 Octet Report Descriptor
108    # pylint: disable=line-too-long
109    [
110        0x05,
111        0x01,  # Usage Page (Generic Desktop Ctrls)
112        0x09,
113        0x06,  # Usage (Keyboard)
114        0xA1,
115        0x01,  # Collection (Application)
116        0x85,
117        0x01,  # . Report ID (1)
118        0x05,
119        0x07,  # . Usage Page (Kbrd/Keypad)
120        0x19,
121        0xE0,  # . Usage Minimum (0xE0)
122        0x29,
123        0xE7,  # . Usage Maximum (0xE7)
124        0x15,
125        0x00,  # . Logical Minimum (0)
126        0x25,
127        0x01,  # . Logical Maximum (1)
128        0x75,
129        0x01,  # . Report Size (1)
130        0x95,
131        0x08,  # . Report Count (8)
132        0x81,
133        0x02,  # . Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
134        0x95,
135        0x01,  # . Report Count (1)
136        0x75,
137        0x08,  # . Report Size (8)
138        0x81,
139        0x03,  # . Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
140        0x95,
141        0x05,  # . Report Count (5)
142        0x75,
143        0x01,  # . Report Size (1)
144        0x05,
145        0x08,  # . Usage Page (LEDs)
146        0x19,
147        0x01,  # . Usage Minimum (Num Lock)
148        0x29,
149        0x05,  # . Usage Maximum (Kana)
150        0x91,
151        0x02,  # . Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
152        0x95,
153        0x01,  # . Report Count (1)
154        0x75,
155        0x03,  # . Report Size (3)
156        0x91,
157        0x03,  # . Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
158        0x95,
159        0x06,  # . Report Count (6)
160        0x75,
161        0x08,  # . Report Size (8)
162        0x15,
163        0x00,  # . Logical Minimum (0)
164        0x25,
165        0x65,  # . Logical Maximum (101)
166        0x05,
167        0x07,  # . Usage Page (Kbrd/Keypad)
168        0x19,
169        0x00,  # . Usage Minimum (0x00)
170        0x29,
171        0x65,  # . Usage Maximum (0x65)
172        0x81,
173        0x00,  # . Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
174        0xC0,  # End Collection
175        0x05,
176        0x01,  # Usage Page (Generic Desktop Ctrls)
177        0x09,
178        0x02,  # Usage (Mouse)
179        0xA1,
180        0x01,  # Collection (Application)
181        0x85,
182        0x02,  # . Report ID (2)
183        0x09,
184        0x01,  # . Usage (Pointer)
185        0xA1,
186        0x00,  # . Collection (Physical)
187        0x05,
188        0x09,  # .   Usage Page (Button)
189        0x19,
190        0x01,  # .   Usage Minimum (0x01)
191        0x29,
192        0x03,  # .   Usage Maximum (0x03)
193        0x15,
194        0x00,  # .   Logical Minimum (0)
195        0x25,
196        0x01,  # .   Logical Maximum (1)
197        0x95,
198        0x03,  # .   Report Count (3)
199        0x75,
200        0x01,  # .   Report Size (1)
201        0x81,
202        0x02,  # .   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
203        0x95,
204        0x01,  # .   Report Count (1)
205        0x75,
206        0x05,  # .   Report Size (5)
207        0x81,
208        0x03,  # .   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
209        0x05,
210        0x01,  # .   Usage Page (Generic Desktop Ctrls)
211        0x09,
212        0x30,  # .   Usage (X)
213        0x09,
214        0x31,  # .   Usage (Y)
215        0x15,
216        0x81,  # .   Logical Minimum (-127)
217        0x25,
218        0x7F,  # .   Logical Maximum (127)
219        0x75,
220        0x08,  # .   Report Size (8)
221        0x95,
222        0x02,  # .   Report Count (2)
223        0x81,
224        0x06,  # .   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
225        0xC0,  # . End Collection (Physical)
226        0xC0,  # End Collection (Application)
227    ])
228
229# Default protocol mode set to report protocol
230protocol_mode = Message.ProtocolMode.REPORT_PROTOCOL
231
232from bumble.core import AdvertisingData
233from bumble.device import Device, Connection, Peer
234from bumble.gatt import (
235    Descriptor,
236    Service,
237    Characteristic,
238    CharacteristicValue,
239    GATT_DEVICE_INFORMATION_SERVICE,
240    GATT_HUMAN_INTERFACE_DEVICE_SERVICE,
241    GATT_BATTERY_SERVICE,
242    GATT_BATTERY_LEVEL_CHARACTERISTIC,
243    GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC,
244    GATT_REPORT_CHARACTERISTIC,
245    GATT_REPORT_MAP_CHARACTERISTIC,
246    GATT_PROTOCOL_MODE_CHARACTERISTIC,
247    GATT_HID_INFORMATION_CHARACTERISTIC,
248    GATT_HID_CONTROL_POINT_CHARACTERISTIC,
249    GATT_REPORT_REFERENCE_DESCRIPTOR,
250)
251
252# -----------------------------------------------------------------------------
253
254# Protocol Modes (HID Specification V1.1.1 Section 2.1.2)
255HID_BOOT_PROTOCOL = 0x00
256HID_REPORT_PROTOCOL = 0x01
257
258# Report Types (HID Specification V1.1.1 Section 2.1.1)
259HID_INPUT_REPORT = 0x01
260HID_OUTPUT_REPORT = 0x02
261HID_FEATURE_REPORT = 0x03
262
263# Report Map
264HID_KEYBOARD_REPORT_MAP = bytes(
265    # pylint: disable=line-too-long
266    [
267        0x05,
268        0x01,  # Usage Page (Generic Desktop Controls)
269        0x09,
270        0x06,  # Usage (Keyboard)
271        0xA1,
272        0x01,  # Collection (Application)
273        0x85,
274        0x01,  # . Report ID (1)
275        0x05,
276        0x07,  # . Usage Page (Keyboard/Keypad)
277        0x19,
278        0xE0,  # . Usage Minimum (0xE0)
279        0x29,
280        0xE7,  # . Usage Maximum (0xE7)
281        0x15,
282        0x00,  # . Logical Minimum (0)
283        0x25,
284        0x01,  # . Logical Maximum (1)
285        0x75,
286        0x01,  # . Report Size (1)
287        0x95,
288        0x08,  # . Report Count (8)
289        0x81,
290        0x02,  # . Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
291        0x95,
292        0x01,  # . Report Count (1)
293        0x75,
294        0x08,  # . Report Size (8)
295        0x81,
296        0x01,  # . Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
297        0x95,
298        0x06,  # . Report Count (6)
299        0x75,
300        0x08,  # . Report Size (8)
301        0x15,
302        0x00,  # . Logical Minimum (0x00)
303        0x25,
304        0x94,  # . Logical Maximum (0x94)
305        0x05,
306        0x07,  # . Usage Page (Keyboard/Keypad)
307        0x19,
308        0x00,  # . Usage Minimum (0x00)
309        0x29,
310        0x94,  # . Usage Maximum (0x94)
311        0x81,
312        0x00,  # . Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
313        0x95,
314        0x05,  # . Report Count (5)
315        0x75,
316        0x01,  # . Report Size (1)
317        0x05,
318        0x08,  # . Usage Page (LEDs)
319        0x19,
320        0x01,  # . Usage Minimum (Num Lock)
321        0x29,
322        0x05,  # . Usage Maximum (Kana)
323        0x91,
324        0x02,  # . Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
325        0x95,
326        0x01,  # . Report Count (1)
327        0x75,
328        0x03,  # . Report Size (3)
329        0x91,
330        0x01,  # . Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
331        0xC0,  # End Collection
332    ])
333
334
335# -----------------------------------------------------------------------------
336# pylint: disable=invalid-overridden-method
337class ServerListener(Device.Listener, Connection.Listener):
338
339    def __init__(self, device):
340        self.device = device
341
342    @AsyncRunner.run_in_task()
343    async def on_connection(self, connection):
344        logging.info(f'=== Connected to {connection}')
345        connection.listener = self
346
347    @AsyncRunner.run_in_task()
348    async def on_disconnection(self, reason):
349        logging.info(f'### Disconnected, reason={reason}')
350
351
352# -----------------------------------------------------------------------------
353def on_hid_control_point_write(_connection, value):
354    logging.info(f'Control Point Write: {value}')
355
356
357# -----------------------------------------------------------------------------
358def sdp_records():
359    service_record_handle = 0x00010006
360    return {
361        service_record_handle: [
362            ServiceAttribute(
363                SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
364                DataElement.unsigned_integer_32(service_record_handle),
365            ),
366            ServiceAttribute(
367                SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
368                DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]),
369            ),
370            ServiceAttribute(
371                SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
372                DataElement.sequence([DataElement.uuid(BT_HUMAN_INTERFACE_DEVICE_SERVICE)]),
373            ),
374            ServiceAttribute(
375                SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
376                DataElement.sequence([
377                    DataElement.sequence([
378                        DataElement.uuid(BT_L2CAP_PROTOCOL_ID),
379                        DataElement.unsigned_integer_16(HID_CONTROL_PSM),
380                    ]),
381                    DataElement.sequence([
382                        DataElement.uuid(BT_HIDP_PROTOCOL_ID),
383                    ]),
384                ]),
385            ),
386            ServiceAttribute(
387                SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID,
388                DataElement.sequence([
389                    DataElement.unsigned_integer_16(LANGUAGE),
390                    DataElement.unsigned_integer_16(ENCODING),
391                    DataElement.unsigned_integer_16(PRIMARY_LANGUAGE_BASE_ID),
392                ]),
393            ),
394            ServiceAttribute(
395                SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
396                DataElement.sequence([
397                    DataElement.sequence([
398                        DataElement.uuid(BT_HUMAN_INTERFACE_DEVICE_SERVICE),
399                        DataElement.unsigned_integer_16(VERSION_NUMBER),
400                    ]),
401                ]),
402            ),
403            ServiceAttribute(
404                SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
405                DataElement.sequence([
406                    DataElement.sequence([
407                        DataElement.sequence([
408                            DataElement.uuid(BT_L2CAP_PROTOCOL_ID),
409                            DataElement.unsigned_integer_16(HID_INTERRUPT_PSM),
410                        ]),
411                        DataElement.sequence([
412                            DataElement.uuid(BT_HIDP_PROTOCOL_ID),
413                        ]),
414                    ]),
415                ]),
416            ),
417            ServiceAttribute(
418                SDP_HID_SERVICE_NAME_ATTRIBUTE_ID,
419                DataElement(DataElement.TEXT_STRING, SERVICE_NAME),
420            ),
421            ServiceAttribute(
422                SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID,
423                DataElement(DataElement.TEXT_STRING, SERVICE_DESCRIPTION),
424            ),
425            ServiceAttribute(
426                SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID,
427                DataElement(DataElement.TEXT_STRING, PROVIDER_NAME),
428            ),
429            ServiceAttribute(
430                SDP_HID_PARSER_VERSION_ATTRIBUTE_ID,
431                DataElement.unsigned_integer_32(HID_PARSER_VERSION),
432            ),
433            ServiceAttribute(
434                SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID,
435                DataElement.unsigned_integer_32(HID_DEVICE_SUBCLASS),
436            ),
437            ServiceAttribute(
438                SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID,
439                DataElement.unsigned_integer_32(HID_COUNTRY_CODE),
440            ),
441            ServiceAttribute(
442                SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID,
443                DataElement.boolean(HID_VIRTUAL_CABLE),
444            ),
445            ServiceAttribute(
446                SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID,
447                DataElement.boolean(HID_RECONNECT_INITIATE),
448            ),
449            ServiceAttribute(
450                SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID,
451                DataElement.sequence([
452                    DataElement.sequence([
453                        DataElement.unsigned_integer_16(REPORT_DESCRIPTOR_TYPE),
454                        DataElement(DataElement.TEXT_STRING, HID_REPORT_MAP),
455                    ]),
456                ]),
457            ),
458            ServiceAttribute(
459                SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID,
460                DataElement.sequence([
461                    DataElement.sequence([
462                        DataElement.unsigned_integer_16(HID_LANGID_BASE_LANGUAGE),
463                        DataElement.unsigned_integer_16(HID_LANGID_BASE_BLUETOOTH_STRING_OFFSET),
464                    ]),
465                ]),
466            ),
467            ServiceAttribute(
468                SDP_HID_BATTERY_POWER_ATTRIBUTE_ID,
469                DataElement.boolean(HID_BATTERY_POWER),
470            ),
471            ServiceAttribute(
472                SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID,
473                DataElement.boolean(HID_REMOTE_WAKE),
474            ),
475            ServiceAttribute(
476                SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID,
477                DataElement.unsigned_integer_16(HID_SUPERVISION_TIMEOUT),
478            ),
479            ServiceAttribute(
480                SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID,
481                DataElement.boolean(HID_NORMALLY_CONNECTABLE),
482            ),
483            ServiceAttribute(
484                SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID,
485                DataElement.boolean(HID_BOOT_DEVICE),
486            ),
487            ServiceAttribute(
488                SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID,
489                DataElement.unsigned_integer_16(HID_SSR_HOST_MAX_LATENCY),
490            ),
491            ServiceAttribute(
492                SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID,
493                DataElement.unsigned_integer_16(HID_SSR_HOST_MIN_TIMEOUT),
494            ),
495        ]
496    }
497
498
499# -----------------------------------------------------------------------------
500def hogp_device(device):
501    # Create an 'input report' characteristic to send keyboard reports to the host
502    input_report_kb_characteristic = Characteristic(
503        GATT_REPORT_CHARACTERISTIC,
504        Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY,
505        Characteristic.READABLE | Characteristic.WRITEABLE,
506        bytes([0, 0, 0, 0, 0, 0, 0, 0, 0]),
507        [Descriptor(
508            GATT_REPORT_REFERENCE_DESCRIPTOR,
509            Descriptor.READABLE,
510            bytes([0x01, HID_INPUT_REPORT]),
511        )],
512    )
513    # Create an 'input report' characteristic to send mouse reports to the host
514    input_report_mouse_characteristic = Characteristic(
515        GATT_REPORT_CHARACTERISTIC,
516        Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY,
517        Characteristic.READABLE | Characteristic.WRITEABLE,
518        bytes([0, 0, 0, 0]),
519        [Descriptor(
520            GATT_REPORT_REFERENCE_DESCRIPTOR,
521            Descriptor.READABLE,
522            bytes([0x02, HID_INPUT_REPORT]),
523        )],
524    )
525    # Create an 'output report' characteristic to receive keyboard reports from the host
526    output_report_characteristic = Characteristic(
527        GATT_REPORT_CHARACTERISTIC,
528        Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.WRITE_WITHOUT_RESPONSE,
529        Characteristic.READABLE | Characteristic.WRITEABLE,
530        bytes([0]),
531        [Descriptor(
532            GATT_REPORT_REFERENCE_DESCRIPTOR,
533            Descriptor.READABLE,
534            bytes([0x01, HID_OUTPUT_REPORT]),
535        )],
536    )
537
538    # Add the services to the GATT sever
539    device.add_services([
540        Service(
541            GATT_DEVICE_INFORMATION_SERVICE,
542            [
543                Characteristic(
544                    GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC,
545                    Characteristic.Properties.READ,
546                    Characteristic.READABLE,
547                    'Bumble',
548                )
549            ],
550        ),
551        Service(
552            GATT_HUMAN_INTERFACE_DEVICE_SERVICE,
553            [
554                Characteristic(
555                    GATT_PROTOCOL_MODE_CHARACTERISTIC,
556                    Characteristic.Properties.READ,
557                    Characteristic.READABLE,
558                    bytes([HID_REPORT_PROTOCOL]),
559                ),
560                Characteristic(
561                    GATT_HID_INFORMATION_CHARACTERISTIC,
562                    Characteristic.Properties.READ,
563                    Characteristic.READABLE,
564                    # bcdHID=1.1, bCountryCode=0x00,
565                    # Flags=RemoteWake|NormallyConnectable
566                    bytes([0x11, 0x01, 0x00, 0x03]),
567                ),
568                Characteristic(
569                    GATT_HID_CONTROL_POINT_CHARACTERISTIC,
570                    Characteristic.WRITE_WITHOUT_RESPONSE,
571                    Characteristic.WRITEABLE,
572                    CharacteristicValue(write=on_hid_control_point_write),
573                ),
574                Characteristic(
575                    GATT_REPORT_MAP_CHARACTERISTIC,
576                    Characteristic.Properties.READ,
577                    Characteristic.READABLE,
578                    HID_KEYBOARD_REPORT_MAP,
579                ),
580                input_report_kb_characteristic,
581                input_report_mouse_characteristic,
582                output_report_characteristic,
583            ],
584        ),
585        Service(
586            GATT_BATTERY_SERVICE,
587            [
588                Characteristic(
589                    GATT_BATTERY_LEVEL_CHARACTERISTIC,
590                    Characteristic.Properties.READ,
591                    Characteristic.READABLE,
592                    bytes([100]),
593                )
594            ],
595        ),
596    ])
597
598    # Debug print
599    for attribute in device.gatt_server.attributes:
600        logging.info(attribute)
601
602    # Set the advertising data
603    device.advertising_data = bytes(
604        AdvertisingData([
605            (
606                AdvertisingData.COMPLETE_LOCAL_NAME,
607                bytes('Bumble Keyboard', 'utf-8'),
608            ),
609            (
610                AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
611                bytes(GATT_HUMAN_INTERFACE_DEVICE_SERVICE),
612            ),
613            (AdvertisingData.APPEARANCE, struct.pack('<H', 0x03C1)),
614            (AdvertisingData.FLAGS, bytes([0x05])),
615        ]))
616
617    # Attach a listener
618    device.listener = ServerListener(device)
619
620
621async def handle_virtual_cable_unplug():
622    hid_host_bd_addr = str(hid_device.remote_device_bd_address)
623    await hid_device.disconnect_interrupt_channel()
624    await hid_device.disconnect_control_channel()
625    await hid_device.device.keystore.delete(hid_host_bd_addr)  # type: ignore
626    connection = hid_device.connection
627    if connection is not None:
628        await connection.disconnect()
629
630
631def on_get_report_cb(report_id: int, report_type: int, buffer_size: int):
632    retValue = hid_device.GetSetStatus()
633    logging.info("GET_REPORT report_id: " + str(report_id) + "report_type: " + str(report_type) + "buffer_size:" +
634                 str(buffer_size))
635    if report_type == Message.ReportType.INPUT_REPORT:
636        if report_id == 1:
637            retValue.data = bytearray([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
638            retValue.status = hid_device.GetSetReturn.SUCCESS
639        elif report_id == 2:
640            retValue.data = bytearray([0x02, 0x00, 0x00, 0x00])
641            retValue.status = hid_device.GetSetReturn.SUCCESS
642        else:
643            retValue.status = hid_device.GetSetReturn.REPORT_ID_NOT_FOUND
644
645    return retValue
646
647
648def on_set_report_cb(report_id: int, report_type: int, report_size: int, data: bytes):
649    retValue = hid_device.GetSetStatus()
650    logging.info("SET_REPORT report_id: " + str(report_id) + "report_type: " + str(report_type) + "report_size " +
651                 str(report_size) + "data:" + str(data))
652
653    report = ReportEvent()
654    report.report_type = report_type
655    report.report_id = report_id
656    report.report_data = str(data.hex())
657
658    if hid_report_queue:
659        hid_report_queue.put_nowait(report)
660
661    if report_type == Message.ReportType.FEATURE_REPORT:
662        retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER
663    elif report_type == Message.ReportType.INPUT_REPORT:
664        if report_id == 1 and report_size != 9:
665            retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER
666        elif report_id == 2 and report_size != 4:
667            retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER
668        elif report_id == 3:
669            retValue.status = hid_device.GetSetReturn.REPORT_ID_NOT_FOUND
670        else:
671            retValue.status = hid_device.GetSetReturn.SUCCESS
672    else:
673        retValue.status = hid_device.GetSetReturn.SUCCESS
674
675    return retValue
676
677
678def on_get_protocol_cb():
679    retValue = hid_device.GetSetStatus()
680    retValue.data = protocol_mode.to_bytes(length=1, byteorder=sys.byteorder)
681    retValue.status = hid_device.GetSetReturn.SUCCESS
682    return retValue
683
684
685def on_set_protocol_cb(protocol: int):
686    retValue = hid_device.GetSetStatus()
687    # We do not support SET_PROTOCOL.
688    logging.info(f"SET_PROTOCOL mode: {protocol}")
689    mode = ProtocolModeEvent()
690    if protocol == PROTOCOL_REPORT_MODE:
691        mode.protocol_mode = PROTOCOL_REPORT_MODE
692    elif protocol == PROTOCOL_BOOT_MODE:
693        mode.protocol_mode = PROTOCOL_BOOT_MODE
694    else:
695        mode.protocol_mode = PROTOCOL_UNSUPPORTED_MODE
696    hid_protoMode_queue.put_nowait(mode)
697    retValue.status = hid_device.GetSetReturn.ERR_UNSUPPORTED_REQUEST
698    return retValue
699
700
701def on_virtual_cable_unplug_cb():
702    logging.info('Received Virtual Cable Unplug')
703    asyncio.create_task(handle_virtual_cable_unplug())
704
705
706hid_protoMode_queue = None
707hid_report_queue = None
708
709# This class implements the Hid Pandora interface.
710class HIDService(HIDServicer):
711
712    hid_device = None
713
714    def __init__(self, device: Device) -> None:
715        super().__init__()
716        self.device = device
717        self.device.sdp_service_records.update(sdp_records())
718        self.event_queue: Optional[asyncio.Queue[ProtocolModeEvent]] = None
719        hogp_device(self.device)
720        logging.info(f'Hid device register: ')
721        global hid_device
722        hid_device = HID_Device(self.device)
723        # Register for  call backs
724        hid_device.register_get_report_cb(on_get_report_cb)
725        hid_device.register_set_report_cb(on_set_report_cb)
726        hid_device.register_get_protocol_cb(on_get_protocol_cb)
727        hid_device.register_set_protocol_cb(on_set_protocol_cb)
728        # Register for virtual cable unplug call back
729        hid_device.on('virtual_cable_unplug', on_virtual_cable_unplug_cb)
730
731    @utils.rpc
732    async def ConnectHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty:
733
734        logging.info(f'ConnectHost')
735        try:
736            hid_host_bd_addr = str(hid_device.remote_device_bd_address)
737            connection = await self.device.connect(hid_host_bd_addr, transport=BT_BR_EDR_TRANSPORT)
738            await connection.authenticate()
739            await connection.encrypt()
740            await hid_device.connect_control_channel()
741            await hid_device.connect_interrupt_channel()
742        except AttributeError as e:
743            logging.error(f'Device does not exist')
744            raise e
745        except (HCI_StatusError, ProtocolError) as e:
746            logging.error(f"Connection failure error: {e}")
747            raise e
748
749        return empty_pb2.Empty()
750
751    @utils.rpc
752    async def DisconnectHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty:
753
754        logging.info(f'DisconnectHost')
755        try:
756            await hid_device.disconnect_interrupt_channel()
757            await hid_device.disconnect_control_channel()
758            connection = hid_device.connection
759            if connection is not None:
760                await connection.disconnect()
761            else:
762                logging.info(f'Already disconnected from Hid Host')
763        except AttributeError as e:
764            logging.error(f'Device does not exist')
765            raise e
766
767        return empty_pb2.Empty()
768
769    @utils.rpc
770    async def VirtualCableUnplugHost(self, request: empty_pb2.Empty, context: grpc.ServicerContext) -> empty_pb2.Empty:
771
772        logging.info(f'VirtualCableUnplugHost')
773        try:
774            hid_device.virtual_cable_unplug()
775            try:
776                hid_host_bd_addr = str(hid_device.remote_device_bd_address)
777                await hid_device.device.keystore.delete(hid_host_bd_addr)
778            except KeyError:
779                logging.error(f'Device not found or Device already unpaired.')
780                raise
781        except AttributeError as e:
782            logging.exception(f'Device does not exist')
783            raise e
784        return empty_pb2.Empty()
785
786    @utils.rpc
787    async def OnSetProtocolMode(self, request: empty_pb2.Empty,
788                                context: grpc.ServicerContext) -> AsyncGenerator[ProtocolModeEvent, None]:
789        logging.info(f'OnSetProtocolMode')
790
791        if self.event_queue is not None:
792            raise RuntimeError('already streaming OnSetProtocolMode events')
793
794        self.event_queue = asyncio.Queue()
795        global hid_protoMode_queue
796        hid_protoMode_queue = self.event_queue
797
798        try:
799            while event := await hid_protoMode_queue.get():
800                yield event
801
802        finally:
803            self.event_queue = None
804            hid_protoMode_queue = None
805
806    @utils.rpc
807    async def OnSetReport(self, request: empty_pb2.Empty,
808                          context: grpc.ServicerContext) -> AsyncGenerator[ReportEvent, None]:
809        logging.info(f'OnSetReport')
810
811        if self.event_queue is not None:
812            raise RuntimeError('already streaming OnSetReport events')
813
814        self.event_queue = asyncio.Queue()
815        global hid_report_queue
816        hid_report_queue = self.event_queue
817
818        try:
819            while event := await hid_report_queue.get():
820                yield event
821
822        finally:
823            self.event_queue = None
824            hid_report_queue = None
825