1#!/usr/bin/env python3
2#
3# Copyright 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import logging
18import time
19from acts import utils
20
21from acts.test_utils.bt.bt_constants import bt_default_timeout
22from acts.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms
23from acts.test_utils.bt.bt_constants import default_le_connection_interval_ms
24from acts.test_utils.bt.bt_constants import default_le_data_length
25from acts.test_utils.bt.bt_constants import gatt_phy
26from acts.test_utils.bt.bt_constants import gatt_transport
27from acts.test_utils.bt.bt_constants import l2cap_coc_header_size
28from acts.test_utils.bt.bt_constants import le_connection_event_time_step_ms
29from acts.test_utils.bt.bt_constants import le_connection_interval_time_step_ms
30from acts.test_utils.bt.bt_constants import le_default_supervision_timeout
31from acts.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
32from acts.test_utils.bt.bt_gatt_utils import setup_gatt_connection
33from acts.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
34
35log = logging
36
37
38class BtCoCTestUtilsError(Exception):
39    pass
40
41
42def do_multi_connection_throughput(client_ad, list_server_ad,
43                                   list_client_conn_id, num_iterations,
44                                   number_buffers, buffer_size):
45    """Throughput measurements from one client to one-or-many servers.
46
47    Args:
48        client_ad: the Android device to perform the write.
49        list_server_ad: the list of Android server devices connected to this client.
50        list_client_conn_id: list of client connection IDs
51        num_iterations: the number of test repetitions.
52        number_buffers: the total number of data buffers to transmit per test.
53        buffer_size: the number of bytes per L2CAP data buffer.
54
55    Returns:
56        Throughput in terms of bytes per second, 0 if test failed.
57    """
58
59    total_num_bytes = 0
60    start_write_time = time.perf_counter()
61    client_ad.log.info(
62        "do_multi_connection_throughput: Before write. Start Time={:f}, "
63        "num_iterations={}, number_buffers={}, buffer_size={}, "
64        "number_buffers*buffer_size={}, num_servers={}".format(
65            start_write_time, num_iterations, number_buffers, buffer_size,
66            number_buffers * buffer_size, len(list_server_ad)))
67
68    if (len(list_server_ad) != len(list_client_conn_id)):
69        client_ad.log.error("do_multi_connection_throughput: invalid "
70                            "parameters. Num of list_server_ad({}) != "
71                            "list_client_conn({})".format(
72                                len(list_server_ad), len(list_client_conn_id)))
73        return 0
74
75    try:
76        for _, client_conn_id in enumerate(list_client_conn_id):
77            client_ad.log.info("do_multi_connection_throughput: "
78                               "client_conn_id={}".format(client_conn_id))
79            # Plumb the tx data queue with the first set of data buffers.
80            client_ad.droid.bluetoothConnectionThroughputSend(
81                number_buffers, buffer_size, client_conn_id)
82    except Exception as err:
83        client_ad.log.error("Failed to write data: {}".format(err))
84        return 0
85
86    # Each Loop iteration will write and read one set of buffers.
87    for _ in range(0, (num_iterations - 1)):
88        try:
89            for _, client_conn_id in enumerate(list_client_conn_id):
90                client_ad.droid.bluetoothConnectionThroughputSend(
91                    number_buffers, buffer_size, client_conn_id)
92        except Exception as err:
93            client_ad.log.error("Failed to write data: {}".format(err))
94            return 0
95
96        for _, server_ad in enumerate(list_server_ad):
97            try:
98                server_ad.droid.bluetoothConnectionThroughputRead(
99                    number_buffers, buffer_size)
100                total_num_bytes += number_buffers * buffer_size
101            except Exception as err:
102                server_ad.log.error("Failed to read data: {}".format(err))
103                return 0
104
105    for _, server_ad in enumerate(list_server_ad):
106        try:
107            server_ad.droid.bluetoothConnectionThroughputRead(
108                number_buffers, buffer_size)
109            total_num_bytes += number_buffers * buffer_size
110        except Exception as err:
111            server_ad.log.error("Failed to read data: {}".format(err))
112            return 0
113
114    end_read_time = time.perf_counter()
115
116    test_time = (end_read_time - start_write_time)
117    if (test_time == 0):
118        client_ad.log.error("Buffer transmits cannot take zero time")
119        return 0
120    data_rate = (1.000 * total_num_bytes) / test_time
121    log.info(
122        "Calculated using total write and read times: total_num_bytes={}, "
123        "test_time={}, data rate={:08.0f} bytes/sec, {:08.0f} bits/sec".format(
124            total_num_bytes, test_time, data_rate, (data_rate * 8)))
125    return data_rate
126
127
128def orchestrate_coc_connection(
129        client_ad,
130        server_ad,
131        is_ble,
132        secured_conn=False,
133        le_connection_interval=0,
134        le_tx_data_length=default_le_data_length,
135        accept_timeout_ms=default_bluetooth_socket_timeout_ms,
136        le_min_ce_len=0,
137        le_max_ce_len=0,
138        gatt_disconnection=True):
139    """Sets up the CoC connection between two Android devices.
140
141    Args:
142        client_ad: the Android device performing the connection.
143        server_ad: the Android device accepting the connection.
144        is_ble: using LE transport.
145        secured_conn: using secured connection
146        le_connection_interval: LE Connection interval. 0 means use default.
147        le_tx_data_length: LE Data Length used by BT Controller to transmit.
148        accept_timeout_ms: timeout while waiting for incoming connection.
149        gatt_disconnection: LE GATT disconnection, default is True, False will return
150        bluetooth_gatt and gatt_callback
151    Returns:
152        True if connection was successful or false if unsuccessful,
153        client connection ID,
154        and server connection ID
155    """
156    server_ad.droid.bluetoothStartPairingHelper()
157    client_ad.droid.bluetoothStartPairingHelper()
158
159    adv_callback = None
160    mac_address = None
161    if is_ble:
162        try:
163            # This will start advertising and scanning. Will fail if it could
164            # not find the advertisements from server_ad
165            client_ad.log.info(
166                "Orchestrate_coc_connection: Start BLE advertisement and"
167                "scanning. Secured Connection={}".format(secured_conn))
168            mac_address, adv_callback, scan_callback = (
169                get_mac_address_of_generic_advertisement(client_ad, server_ad))
170        except BtTestUtilsError as err:
171            raise BtCoCTestUtilsError(
172                "Orchestrate_coc_connection: Error in getting mac address: {}".
173                format(err))
174    else:
175        mac_address = server_ad.droid.bluetoothGetLocalAddress()
176        adv_callback = None
177
178    # Adjust the Connection Interval (if necessary)
179    bluetooth_gatt_1 = -1
180    gatt_callback_1 = -1
181    gatt_connected = False
182    if is_ble and (le_connection_interval != 0 or le_min_ce_len != 0 or le_max_ce_len != 0):
183        client_ad.log.info(
184            "Adjusting connection interval={}, le_min_ce_len={}, le_max_ce_len={}"
185            .format(le_connection_interval, le_min_ce_len, le_max_ce_len))
186        try:
187            bluetooth_gatt_1, gatt_callback_1 = setup_gatt_connection(
188                client_ad,
189                mac_address,
190                False,
191                transport=gatt_transport['le'],
192                opportunistic=False)
193            client_ad.droid.bleStopBleScan(scan_callback)
194        except GattTestUtilsError as err:
195            client_ad.log.error(err)
196            if (adv_callback != None):
197                server_ad.droid.bleStopBleAdvertising(adv_callback)
198            return False, None, None
199        client_ad.log.info("setup_gatt_connection returns success")
200        if (le_connection_interval != 0):
201            minInterval = le_connection_interval / le_connection_interval_time_step_ms
202            maxInterval = le_connection_interval / le_connection_interval_time_step_ms
203        else:
204            minInterval = default_le_connection_interval_ms / le_connection_interval_time_step_ms
205            maxInterval = default_le_connection_interval_ms / le_connection_interval_time_step_ms
206        if (le_min_ce_len != 0):
207            le_min_ce_len = le_min_ce_len / le_connection_event_time_step_ms
208        if (le_max_ce_len != 0):
209            le_max_ce_len = le_max_ce_len / le_connection_event_time_step_ms
210
211        return_status = client_ad.droid.gattClientRequestLeConnectionParameters(
212            bluetooth_gatt_1, minInterval, maxInterval, 0,
213            le_default_supervision_timeout, le_min_ce_len, le_max_ce_len)
214        if not return_status:
215            client_ad.log.error(
216                "gattClientRequestLeConnectionParameters returns failure")
217            if (adv_callback != None):
218                server_ad.droid.bleStopBleAdvertising(adv_callback)
219            return False, None, None
220        client_ad.log.info(
221            "gattClientRequestLeConnectionParameters returns success. Interval={}"
222            .format(minInterval))
223        gatt_connected = True
224        # For now, we will only test with 1 Mbit Phy.
225        # TODO: Add explicit tests with 2 MBit Phy.
226        client_ad.droid.gattClientSetPreferredPhy(
227            bluetooth_gatt_1, gatt_phy['1m'], gatt_phy['1m'], 0)
228
229    server_ad.droid.bluetoothSocketConnBeginAcceptThreadPsm(
230        accept_timeout_ms, is_ble, secured_conn)
231
232    psm_value = server_ad.droid.bluetoothSocketConnGetPsm()
233    client_ad.log.info("Assigned PSM value={}".format(psm_value))
234
235    client_ad.droid.bluetoothSocketConnBeginConnectThreadPsm(
236        mac_address, is_ble, psm_value, secured_conn)
237
238    if (le_tx_data_length != default_le_data_length) and is_ble:
239        client_ad.log.info("orchestrate_coc_connection: call "
240                           "bluetoothSocketRequestMaximumTxDataLength")
241        client_ad.droid.bluetoothSocketRequestMaximumTxDataLength()
242
243    end_time = time.time() + bt_default_timeout
244    test_result = False
245    while time.time() < end_time:
246        if len(server_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
247            server_ad.log.info("CoC Server Connection Active")
248            if len(client_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
249                client_ad.log.info("CoC Client Connection Active")
250                test_result = True
251                break
252        time.sleep(1)
253
254    if (adv_callback != None):
255        server_ad.droid.bleStopBleAdvertising(adv_callback)
256
257    if not test_result:
258        client_ad.log.error("Failed to establish an CoC connection")
259        return False, None, None
260
261    if len(client_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
262        server_ad.log.info(
263            "CoC client_ad Connection Active, num=%d",
264            len(client_ad.droid.bluetoothSocketConnActiveConnections()))
265    else:
266        server_ad.log.info("Error CoC client_ad Connection Inactive")
267        client_ad.log.info("Error CoC client_ad Connection Inactive")
268
269    # Wait for the client to be ready
270    client_conn_id = None
271    while (client_conn_id == None):
272        client_conn_id = client_ad.droid.bluetoothGetLastConnId()
273        if (client_conn_id != None):
274            break
275        time.sleep(1)
276
277    # Wait for the server to be ready
278    server_conn_id = None
279    while (server_conn_id == None):
280        server_conn_id = server_ad.droid.bluetoothGetLastConnId()
281        if (server_conn_id != None):
282            break
283        time.sleep(1)
284
285    client_ad.log.info(
286        "orchestrate_coc_connection: client conn id={}, server conn id={}".
287        format(client_conn_id, server_conn_id))
288
289    if gatt_disconnection:
290
291        if gatt_connected:
292            disconnect_gatt_connection(client_ad, bluetooth_gatt_1,
293                                       gatt_callback_1)
294            client_ad.droid.gattClientClose(bluetooth_gatt_1)
295
296        return True, client_conn_id, server_conn_id
297
298    else:
299        return True, client_conn_id, server_conn_id, bluetooth_gatt_1, gatt_callback_1
300