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

#!/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))