You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
11 KiB
267 lines
11 KiB
4 months ago
|
#!/usr/bin/env python3
|
||
|
#
|
||
|
# Copyright 2020 - 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.
|
||
|
|
||
|
from google.protobuf import empty_pb2 as empty_proto
|
||
|
|
||
|
from l2cap.classic import facade_pb2 as l2cap_facade_pb2
|
||
|
from l2cap.classic.facade_pb2 import LinkSecurityInterfaceCallbackEventType
|
||
|
from l2cap.le import facade_pb2 as l2cap_le_facade_pb2
|
||
|
from l2cap.le.facade_pb2 import SecurityLevel
|
||
|
from bluetooth_packets_python3 import hci_packets
|
||
|
from bluetooth_packets_python3 import l2cap_packets
|
||
|
from cert.event_stream import FilteringEventStream
|
||
|
from cert.event_stream import EventStream, IEventStream
|
||
|
from cert.closable import Closable, safeClose
|
||
|
from cert.py_hci import PyHci
|
||
|
from cert.matchers import HciMatchers
|
||
|
from cert.matchers import L2capMatchers
|
||
|
from cert.truth import assertThat
|
||
|
from facade import common_pb2 as common
|
||
|
|
||
|
|
||
|
class PyL2capChannel(IEventStream):
|
||
|
|
||
|
def __init__(self, device, psm, l2cap_stream):
|
||
|
self._device = device
|
||
|
self._psm = psm
|
||
|
self._le_l2cap_stream = l2cap_stream
|
||
|
self._our_le_l2cap_view = FilteringEventStream(self._le_l2cap_stream,
|
||
|
L2capMatchers.PacketPayloadWithMatchingPsm(self._psm))
|
||
|
|
||
|
def get_event_queue(self):
|
||
|
return self._our_le_l2cap_view.get_event_queue()
|
||
|
|
||
|
def send(self, payload):
|
||
|
self._device.l2cap.SendDynamicChannelPacket(
|
||
|
l2cap_facade_pb2.DynamicChannelPacket(psm=self._psm, payload=payload))
|
||
|
|
||
|
def close_channel(self):
|
||
|
self._device.l2cap.CloseChannel(l2cap_facade_pb2.CloseChannelRequest(psm=self._psm))
|
||
|
|
||
|
def set_traffic_paused(self, paused):
|
||
|
self._device.l2cap.SetTrafficPaused(l2cap_facade_pb2.SetTrafficPausedRequest(psm=self._psm, paused=paused))
|
||
|
|
||
|
|
||
|
class _ClassicConnectionResponseFutureWrapper(object):
|
||
|
"""
|
||
|
The future object returned when we send a connection request from DUT. Can be used to get connection status and
|
||
|
create the corresponding PyL2capDynamicChannel object later
|
||
|
"""
|
||
|
|
||
|
def __init__(self, grpc_response_future, device, psm, l2cap_stream):
|
||
|
self._grpc_response_future = grpc_response_future
|
||
|
self._device = device
|
||
|
self._psm = psm
|
||
|
self._l2cap_stream = l2cap_stream
|
||
|
|
||
|
def get_channel(self):
|
||
|
return PyL2capChannel(self._device, self._psm, self._l2cap_stream)
|
||
|
|
||
|
|
||
|
class PyL2cap(Closable):
|
||
|
|
||
|
def __init__(self, device, cert_address, has_security=False):
|
||
|
self._device = device
|
||
|
self._cert_address = cert_address
|
||
|
self._hci = PyHci(device)
|
||
|
self._l2cap_stream = EventStream(self._device.l2cap.FetchL2capData(empty_proto.Empty()))
|
||
|
self._security_connection_event_stream = EventStream(
|
||
|
self._device.l2cap.FetchSecurityConnectionEvents(empty_proto.Empty()))
|
||
|
if has_security == False:
|
||
|
self._hci.register_for_events(hci_packets.EventCode.LINK_KEY_REQUEST)
|
||
|
|
||
|
def close(self):
|
||
|
safeClose(self._l2cap_stream)
|
||
|
safeClose(self._security_connection_event_stream)
|
||
|
safeClose(self._hci)
|
||
|
|
||
|
def register_dynamic_channel(self, psm=0x33, mode=l2cap_facade_pb2.RetransmissionFlowControlMode.BASIC):
|
||
|
self._device.l2cap.SetDynamicChannel(
|
||
|
l2cap_facade_pb2.SetEnableDynamicChannelRequest(psm=psm, retransmission_mode=mode))
|
||
|
return PyL2capChannel(self._device, psm, self._l2cap_stream)
|
||
|
|
||
|
def connect_dynamic_channel_to_cert(self, psm=0x33, mode=l2cap_facade_pb2.RetransmissionFlowControlMode.BASIC):
|
||
|
"""
|
||
|
Send open Dynamic channel request to CERT.
|
||
|
Get a future for connection result, to be used after CERT accepts request
|
||
|
"""
|
||
|
self.register_dynamic_channel(psm, mode)
|
||
|
response_future = self._device.l2cap.OpenChannel.future(
|
||
|
l2cap_facade_pb2.OpenChannelRequest(psm=psm, remote=self._cert_address, mode=mode))
|
||
|
|
||
|
return _ClassicConnectionResponseFutureWrapper(response_future, self._device, psm, self._l2cap_stream)
|
||
|
|
||
|
def get_channel_queue_buffer_size(self):
|
||
|
return self._device.l2cap.GetChannelQueueDepth(empty_proto.Empty()).size
|
||
|
|
||
|
def initiate_connection_for_security(self):
|
||
|
"""
|
||
|
Establish an ACL for the specific purpose of pairing devices
|
||
|
"""
|
||
|
self._device.l2cap.InitiateConnectionForSecurity(self._cert_address)
|
||
|
|
||
|
def get_security_connection_event_stream(self):
|
||
|
"""
|
||
|
Stream of Link related events. Events are returned with an address.
|
||
|
Events map to the LinkSecurityInterfaceListener callbacks
|
||
|
"""
|
||
|
return self._security_connection_event_stream
|
||
|
|
||
|
def security_link_hold(self):
|
||
|
"""
|
||
|
Holds open the ACL indefinitely allowing for the security handshake
|
||
|
to take place
|
||
|
"""
|
||
|
self._device.l2cap.SecurityLinkHold(self._cert_address)
|
||
|
|
||
|
def security_link_ensure_authenticated(self):
|
||
|
"""
|
||
|
Triggers authentication process by sending HCI event AUTHENTICATION_REQUESTED
|
||
|
"""
|
||
|
self._device.l2cap.SecurityLinkEnsureAuthenticated(self._cert_address)
|
||
|
|
||
|
def security_link_release(self):
|
||
|
"""
|
||
|
Releases a Held open ACL allowing for the ACL to time out after the default time
|
||
|
"""
|
||
|
self._device.l2cap.SecurityLinkRelease(self._cert_address)
|
||
|
|
||
|
def security_link_disconnect(self):
|
||
|
"""
|
||
|
Immediately release and disconnect ACL
|
||
|
"""
|
||
|
self._device.l2cap.SecurityLinkDisconnect(self._cert_address)
|
||
|
|
||
|
def verify_security_connection(self):
|
||
|
"""
|
||
|
Verify that we get a connection and a link key request
|
||
|
"""
|
||
|
assertThat(self.get_security_connection_event_stream()).emits(
|
||
|
lambda event: event.event_type == LinkSecurityInterfaceCallbackEventType.ON_CONNECTED)
|
||
|
assertThat(self._hci.get_event_stream()).emits(HciMatchers.LinkKeyRequest())
|
||
|
|
||
|
|
||
|
class PyLeL2capFixedChannel(IEventStream):
|
||
|
|
||
|
def __init__(self, device, cid, l2cap_stream):
|
||
|
self._device = device
|
||
|
self._cid = cid
|
||
|
self._le_l2cap_stream = l2cap_stream
|
||
|
self._our_le_l2cap_view = FilteringEventStream(self._le_l2cap_stream,
|
||
|
L2capMatchers.PacketPayloadWithMatchingCid(self._cid))
|
||
|
|
||
|
def get_event_queue(self):
|
||
|
return self._our_le_l2cap_view.get_event_queue()
|
||
|
|
||
|
def send(self, payload):
|
||
|
self._device.l2cap_le.SendFixedChannelPacket(
|
||
|
l2cap_le_facade_pb2.FixedChannelPacket(cid=self._cid, payload=payload))
|
||
|
|
||
|
def close_channel(self):
|
||
|
self._device.l2cap_le.SetFixedChannel(
|
||
|
l2cap_le_facade_pb2.SetEnableFixedChannelRequest(cid=self._cid, enable=False))
|
||
|
|
||
|
|
||
|
class PyLeL2capDynamicChannel(IEventStream):
|
||
|
|
||
|
def __init__(self, device, cert_address, psm, l2cap_stream):
|
||
|
self._device = device
|
||
|
self._cert_address = cert_address
|
||
|
self._psm = psm
|
||
|
self._le_l2cap_stream = l2cap_stream
|
||
|
self._our_le_l2cap_view = FilteringEventStream(self._le_l2cap_stream,
|
||
|
L2capMatchers.PacketPayloadWithMatchingPsm(self._psm))
|
||
|
|
||
|
def get_event_queue(self):
|
||
|
return self._our_le_l2cap_view.get_event_queue()
|
||
|
|
||
|
def send(self, payload):
|
||
|
self._device.l2cap_le.SendDynamicChannelPacket(
|
||
|
l2cap_le_facade_pb2.DynamicChannelPacket(psm=self._psm, payload=payload))
|
||
|
|
||
|
def close_channel(self):
|
||
|
self._device.l2cap_le.CloseDynamicChannel(
|
||
|
l2cap_le_facade_pb2.CloseDynamicChannelRequest(remote=self._cert_address, psm=self._psm))
|
||
|
|
||
|
|
||
|
class _CreditBasedConnectionResponseFutureWrapper(object):
|
||
|
"""
|
||
|
The future object returned when we send a connection request from DUT. Can be used to get connection status and
|
||
|
create the corresponding PyLeL2capDynamicChannel object later
|
||
|
"""
|
||
|
|
||
|
def __init__(self, grpc_response_future, device, cert_address, psm, le_l2cap_stream):
|
||
|
self._grpc_response_future = grpc_response_future
|
||
|
self._device = device
|
||
|
self._cert_address = cert_address
|
||
|
self._psm = psm
|
||
|
self._le_l2cap_stream = le_l2cap_stream
|
||
|
|
||
|
def get_status(self):
|
||
|
return l2cap_packets.LeCreditBasedConnectionResponseResult(self._grpc_response_future.result().status)
|
||
|
|
||
|
def get_channel(self):
|
||
|
assertThat(self.get_status()).isEqualTo(l2cap_packets.LeCreditBasedConnectionResponseResult.SUCCESS)
|
||
|
return PyLeL2capDynamicChannel(self._device, self._cert_address, self._psm, self._le_l2cap_stream)
|
||
|
|
||
|
|
||
|
class PyLeL2cap(Closable):
|
||
|
|
||
|
def __init__(self, device):
|
||
|
self._device = device
|
||
|
self._le_l2cap_stream = EventStream(self._device.l2cap_le.FetchL2capData(empty_proto.Empty()))
|
||
|
|
||
|
def close(self):
|
||
|
safeClose(self._le_l2cap_stream)
|
||
|
|
||
|
def enable_fixed_channel(self, cid=4):
|
||
|
self._device.l2cap_le.SetFixedChannel(l2cap_le_facade_pb2.SetEnableFixedChannelRequest(cid=cid, enable=True))
|
||
|
|
||
|
def get_fixed_channel(self, cid=4):
|
||
|
return PyLeL2capFixedChannel(self._device, cid, self._le_l2cap_stream)
|
||
|
|
||
|
def register_coc(self, cert_address, psm=0x33, security_level=SecurityLevel.NO_SECURITY):
|
||
|
self._device.l2cap_le.SetDynamicChannel(
|
||
|
l2cap_le_facade_pb2.SetEnableDynamicChannelRequest(psm=psm, enable=True, security_level=security_level))
|
||
|
return PyLeL2capDynamicChannel(self._device, cert_address, psm, self._le_l2cap_stream)
|
||
|
|
||
|
def connect_coc_to_cert(self, cert_address, psm=0x33):
|
||
|
"""
|
||
|
Send open LE COC request to CERT. Get a future for connection result, to be used after CERT accepts request
|
||
|
"""
|
||
|
self.register_coc(cert_address, psm)
|
||
|
response_future = self._device.l2cap_le.OpenDynamicChannel.future(
|
||
|
l2cap_le_facade_pb2.OpenDynamicChannelRequest(psm=psm, remote=cert_address))
|
||
|
|
||
|
return _CreditBasedConnectionResponseFutureWrapper(response_future, self._device, cert_address, psm,
|
||
|
self._le_l2cap_stream)
|
||
|
|
||
|
def update_connection_parameter(self,
|
||
|
conn_interval_min=0x10,
|
||
|
conn_interval_max=0x10,
|
||
|
conn_latency=0x0a,
|
||
|
supervision_timeout=0x64,
|
||
|
min_ce_length=12,
|
||
|
max_ce_length=12):
|
||
|
self._device.l2cap_le.SendConnectionParameterUpdate(
|
||
|
l2cap_le_facade_pb2.ConnectionParameter(
|
||
|
conn_interval_min=conn_interval_min,
|
||
|
conn_interval_max=conn_interval_max,
|
||
|
conn_latency=conn_latency,
|
||
|
supervision_timeout=supervision_timeout,
|
||
|
min_ce_length=min_ce_length,
|
||
|
max_ce_length=max_ce_length))
|