# -*- coding: utf-8 -*-
# Copyright 2019 Oticon A/S
# SPDX-License-Identifier: Apache-2.0

import math
from numpy import random;
import copy
import statistics;
import os;
import numpy;
import csv;
import tests.test_utils;
from collections import defaultdict, namedtuple
from enum import IntEnum;
from components.utils import *;
from components.basic_commands import *;
from components.address import *;
from components.events import *;
from components.resolvable import *;
from components.advertiser import *;
from components.scanner import *;
from components.initiator import *;
from components.addata import *;
from components.preambles import *;
from components.test_spec import TestSpec;
from components.dump import PacketType, channel_num_to_index;
from tests.test_utils import *

global lowerIRK, upperIRK, ENC_KEYS

class FragmentPreference(IntEnum):
    FRAGMENT_ALL_DATA = 0          # The Controller may fragment all Host advertising data
    FRAGMENT_MIN_DATA = 1          # The Controller should not fragment or should minimize fragmentation of Host advertising data

class PreferredPhysicalChannel(IntEnum):
    LE_1M    = 0                   # 0 ~ The Host prefers to use the LE 1M transmitter PHY (possibly among others)
    LE_2M    = 1                   # 1 ~ The Host prefers to use the LE 2M transmitter PHY (possibly among others)
    LE_CODED = 2                   # 2 ~ The Host prefers to use the LE Coded transmitter PHY (possibly among others)

"""
    LL/DDI/ADV/BV-01-C [Non-Connectable Advertising Packets on one channel]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_01_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 100, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);

    success = advertiser.enable();
    if success:
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = success and scanner.qualifyReports( 100, None, advertiser.advertiseData );

        success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-02-C [Undirected Advertising Packets on one channel]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_02_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 100, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);

    success = advertiser.enable();
    if success:
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = success and scanner.qualifyReports( 100, None, advertiser.advertiseData );

        success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-03-C [Non-Connectable Advertising Packets on all channels]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_03_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 50, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    success = True;
    adData = ADData();
    adData.encode( ADType.COMPLETE_LOCAL_NAME, 'THIS IS JUST A RANDOM NAME...' );

    for dataLength in [ 1, 0, 31, 0 ]:
        trace.trace(7, '-'*80);

        advertiser.advertiseData = [ ] if dataLength == 0 else [ 0x01 ] if dataLength == 1 else adData.asBytes();

        advertising = advertiser.enable();
        success = success and advertising;
        if advertising:
            success = scanner.enable() and success;
            scanner.monitor();
            success = scanner.disable() and success;
            success = scanner.qualifyReports( 50, None, list(advertiser.advertiseData) ) and success;
            success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-04-C [Undirected Advertising with Data on all channels ]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_04_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 50, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    success = True;
    adData = ADData();

    for dataLength in [ 1, 0, 31, 0 ]:
        trace.trace(7, '-'*80);

        advertiser.advertiseData = [ ] if dataLength == 0 else [ 0x01 ] if dataLength == 1 else \
                                   adData.encode( ADType.COMPLETE_LOCAL_NAME, 'THIS IS JUST A RANDOM NAME...' );

        advertising = advertiser.enable();
        success = success and advertising;
        if advertising:
            success = scanner.enable() and success;
            scanner.monitor();
            success = scanner.disable() and success;
            success = success and scanner.qualifyReports( 50, None, advertiser.advertiseData );

            success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-05-C [Undirected Connectable Advertising with Scan Request/Response ]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_05_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 30, 1);

    success = True;
    adData = ADData();

    for address in [ 0x456789ABCDEF, address_scramble_OUI( 0x456789ABCDEF ), address_exchange_OUI_LAP( 0x456789ABCDEF ) ]:
        for nameLength in [ 2, 31 ]:
            trace.trace(7, '-'*80);

            advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, '' ) if nameLength < 31 else \
                                      adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT IUT IUT IUT IUT IUT IUT IUT' );

            advertising = advertiser.enable();
            success = success and advertising;

            trace.trace(6, "\nUsing scanner address: %s SCAN_RSP data length: %d\n" % (formatAddress( toArray(address, 6), SimpleAddressType.PUBLIC), nameLength) );
            success = success and preamble_set_public_address( transport, lowerTester, address, trace );

            if advertising:
                success = scanner.enable() and success;
                scanner.monitor();
                success = scanner.disable() and success;
                success = success and scanner.qualifyReports( 1 );
                success = success and scanner.qualifyResponses( 1, advertiser.responseData );

                success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-06-C [Stop Advertising on Connection Request]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_06_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 0);

    success = True;
    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT' );

    initiatorAddress = Address( ExtendedAddressType.PUBLIC );
    initiator = Initiator(transport, lowerTester, upperTester, trace, initiatorAddress, publicIdentityAddress(upperTester));

    for address in [ 0x456789ABCDEF, address_scramble_OUI( 0x456789ABCDEF ), address_scramble_LAP( 0x456789ABCDEF ), address_exchange_OUI_LAP( 0x456789ABCDEF ) ]:
        trace.trace(7, '-'*80);

        trace.trace(6, "\nUsing initiator address: %s\n" % formatAddress( toArray(address, 6), SimpleAddressType.PUBLIC));
        success = success and preamble_set_public_address( transport, lowerTester, address, trace );

        success = advertiser.enable() and success;
        connected = initiator.connect();
        success = success and connected;

        if connected:
            """
                If a connection was established Advertising should have seized...
            """
            scanner.expectedResponses = None;
            success = scanner.enable() and success;
            scanner.monitor();
            success = scanner.disable() and success;
            success = success and not scanner.qualifyReports( 1 );

            success = initiator.disconnect(0x13) and success;
        else:
            success = advertiser.disable() and success;

        if not success:
            break;

    return success;

"""
    LL/DDI/ADV/BV-07-C [Scan Request/Response followed by Connection Request]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_07_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 1, 1);

    success = True;
    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT' );

    success = advertiser.enable();
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = success and scanner.qualifyReports( 1 );
    success = success and scanner.qualifyResponses( 1, advertiser.responseData );

    initiatorAddress = Address( ExtendedAddressType.PUBLIC );
    initiator = Initiator(transport, lowerTester, upperTester, trace, initiatorAddress, publicIdentityAddress(upperTester));
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            If a connection was established Advertising should have seized...
        """
        scanner.expectedResponses = None;
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = success and not scanner.qualifyReports( 1 );

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-08-C [Advertiser Filtering Scan requests]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_08_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Place Public and static Random addresses of lowerTester in the Filter Accept List for the Advertiser
    """
    ownAddress = Address( ExtendedAddressType.PUBLIC );
    peerAddresses = [ Address( IdentityAddressType.PUBLIC, 0x456789ABCDEF ), Address( IdentityAddressType.RANDOM, 0x456789ABCDEF | 0xC00000000000 ) ];
    success = addAddressesToFilterAcceptList(transport, upperTester, peerAddresses, trace);

    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT' );

    for filterPolicy in [ AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS, AdvertisingFilterPolicy.FILTER_SCAN_REQUESTS ]:
        trace.trace(7, "\nTesting Advertising Filter Policy: %s" % filterPolicy.name);
        advertiser.filterPolicy = filterPolicy;

        for addressType, peerAddress in zip([ ExtendedAddressType.PUBLIC, ExtendedAddressType.RANDOM ], peerAddresses):

            advertiser.peerAddress = peerAddress;
            success = advertiser.enable() and success;

            for i in range(3):
                useAddressType = addressType;
                trace.trace(7, '-'*80);
                if   i == 0:
                    """
                        Correct Address Type - scrambled Address
                    """
                    if useAddressType == ExtendedAddressType.PUBLIC:
                        trace.trace(7, "-- (%s,%d) Using scrambled PUBLIC address..." % (addressType.name,i));
                        success = success and preamble_set_public_address( transport, lowerTester, address_scramble_LAP( 0x456789ABCDEF ), trace );
                    else:
                        trace.trace(7, "-- (%s,%d) Using scrambled RANDOM static address..." % (addressType.name,i));
                        success = success and preamble_set_random_address( transport, lowerTester, address_scramble_LAP( 0x456789ABCDEF ) | 0xC00000000000, trace );
                elif i == 1:
                    """
                        Incorrect Address Type - correct Address
                    """
                    useAddressType = ExtendedAddressType.RANDOM if addressType == ExtendedAddressType.PUBLIC else ExtendedAddressType.PUBLIC;
                    if useAddressType == ExtendedAddressType.PUBLIC:
                        trace.trace(7, "-- (%s,%d) Using incorrect PUBLIC address..." % (addressType.name,i));
                        success = success and preamble_set_public_address( transport, lowerTester, toNumber(peerAddresses[1].address), trace );
                    else:
                        trace.trace(7, "-- (%s,%d) Using incorrect RANDOM static address..." % (addressType.name,i));
                        success = success and preamble_set_random_address( transport, lowerTester, toNumber(peerAddresses[0].address), trace );
                else:
                    """
                        Correct Address Type - correct Address
                    """
                    if useAddressType == ExtendedAddressType.PUBLIC:
                        trace.trace(7, "-- (%s,%d) Using PUBLIC address..." % (addressType.name,i));
                        success = success and preamble_set_public_address( transport, lowerTester, toNumber(peerAddresses[0].address), trace );
                    else:
                        trace.trace(7, "-- (%s,%d) Using RANDOM static address..." % (addressType.name,i));
                        success = success and preamble_set_random_address( transport, lowerTester, toNumber(peerAddresses[1].address), trace );

                scanner.ownAddress.type = useAddressType;
                scanner.expectedReports = 30;
                scanner.expectedResponses = 1 if (i == 2) else None;

                success = scanner.enable() and success;
                scanner.monitor();
                success = scanner.disable() and success;
                success = success and scanner.qualifyReports( scanner.expectedReports );
                if not scanner.expectedResponses is None:
                    success = success and scanner.qualifyResponses( 1, advertiser.responseData );
                else:
                    success = success and not scanner.qualifyResponses( 1 );

            success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-09-C [Advertiser Filtering Connection requests]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_09_c(transport, upperTester, lowerTester, trace):

    """
        Place Public address and Random static address of lowerTester in the Filter Accept List for the Advertiser
    """
    ownAddress = Address( ExtendedAddressType.PUBLIC );
    peerAddresses = [ Address( IdentityAddressType.PUBLIC, 0x456789ABCDEF ), Address( IdentityAddressType.RANDOM, 0x456789ABCDEF | 0xC00000000000 ) ];
    success = addAddressesToFilterAcceptList(transport, upperTester, peerAddresses, trace);
    """
        Initialize Advertiser with Connectable Undirected advertising using a Public Address
    """
    advertiser = Advertiser(transport, upperTester, trace, AdvertiseChannel.ALL_CHANNELS, Advertising.CONNECTABLE_UNDIRECTED, \
                            ownAddress, peerAddresses[0], AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);

    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT' );

    for filterPolicy in [ AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS, AdvertisingFilterPolicy.FILTER_CONNECTION_REQUESTS, AdvertisingFilterPolicy.FILTER_SCAN_REQUESTS ]:
        trace.trace(7, "\nTesting Advertising Filter Policy: %s" % filterPolicy.name);
        advertiser.filterPolicy = filterPolicy;

        for addressType, peerAddress in zip([ ExtendedAddressType.PUBLIC, ExtendedAddressType.RANDOM ], peerAddresses):

            advertiser.peerAddress = peerAddress;

            for i in range(3):
                useAddressType = addressType;
                success = advertiser.enable() and success;
                trace.trace(7, '-'*80);
                if   i == 0:
                    """
                        Correct Address Type - scrambled Address
                    """
                    if useAddressType == ExtendedAddressType.PUBLIC:
                        trace.trace(7, "-- (%s,%d) Using scrambled PUBLIC address..." % (addressType.name,i));
                        success = success and preamble_set_public_address( transport, lowerTester, address_scramble_OUI( 0x456789ABCDEF ), trace );
                    else:
                        trace.trace(7, "-- (%s,%d) Using scrambled RANDOM static address..." % (addressType.name,i));
                        success = success and preamble_set_random_address( transport, lowerTester, address_scramble_OUI( 0x456789ABCDEF ) | 0xC00000000000, trace );
                elif i == 1:
                    """
                        Incorrect Address Type - correct Address
                    """
                    useAddressType = ExtendedAddressType.RANDOM if addressType == ExtendedAddressType.PUBLIC else ExtendedAddressType.PUBLIC;
                    if useAddressType == ExtendedAddressType.PUBLIC:
                        trace.trace(7, "-- (%s,%d) Using incorrect PUBLIC address..." % (addressType.name,i));
                        success = success and preamble_set_public_address( transport, lowerTester, toNumber(peerAddresses[1].address), trace );
                    else:
                        trace.trace(7, "-- (%s,%d) Using incorrect RANDOM static address..." % (addressType.name,i));
                        success = success and preamble_set_random_address( transport, lowerTester, toNumber(peerAddresses[0].address), trace );
                else:
                    """
                        Correct Address Type - correct Address
                    """
                    if useAddressType == ExtendedAddressType.PUBLIC:
                        trace.trace(7, "-- (%s,%d) Using correct PUBLIC address..." % (addressType.name,i));
                        success = success and preamble_set_public_address( transport, lowerTester, toNumber(peerAddresses[0].address), trace );
                    else:
                        trace.trace(7, "-- (%s,%d) Using correct RANDOM static address..." % (addressType.name,i));
                        success = success and preamble_set_random_address( transport, lowerTester, toNumber(peerAddresses[1].address), trace );

                initiatorAddress = Address( useAddressType );
                initiator = Initiator(transport, lowerTester, upperTester, trace, initiatorAddress, publicIdentityAddress(upperTester));

                for j in range(30):
                    connected = initiator.connect();
                    success = success and (connected if (i == 2 or filterPolicy == AdvertisingFilterPolicy.FILTER_SCAN_REQUESTS) else not connected);

                    if connected:
                        """
                            If a connection was established - disconnect...
                        """
                        success = initiator.disconnect(0x13) and success;
                        break;

                if not connected:
                    success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-11-C [High Duty Cycle Connectable Directed Advertising on all channels]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_11_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED, 30, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    ownAddress = Address( ExtendedAddressType.PUBLIC );
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    success = scanner.enable() and success;
    success = advertiser.enable() and success;
    scanner.monitor(True);
    success = advertiser.timeout() and success;
    success = scanner.disable() and success;

    success = success and scanner.qualifyReportTime( 30, 1300 );

    success = advertiser.enable() and success;

    initiator = Initiator(transport, lowerTester, upperTester, trace, ownAddress, publicIdentityAddress(upperTester));
    connected = initiator.connect();
    success = success and connected;
    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-15-C [Discoverable Undirected Advertising on all channels]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_15_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 100, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    success = scanner.enable() and success;
    success = advertiser.enable() and success;
    scanner.monitor();
    success = advertiser.disable() and success;
    success = scanner.disable() and success;
    success = success and scanner.qualifyReports( 100, None, advertiser.advertiseData );

    return success;

"""
    LL/DDI/ADV/BV-16-C [Discoverable Undirected Advertising with Data on all channels]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_16_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 50);

    success = True;

    for i in range(4):
        advertiser.advertiseData = [ 0x01 ] if i == 0 else [ ] if i == 1 or i == 3 else [ 0x1E if _ == 0 else 0x00 for _ in range(31) ];

        success = scanner.enable() and success;
        success = advertiser.enable() and success;
        scanner.monitor();
        success = advertiser.disable()and success;
        success = scanner.disable() and success;
        success = success and scanner.qualifyReports( 50, None, advertiser.advertiseData );

    return success;

"""
    LL/DDI/ADV/BV-17-C [Discoverable Undirected Advertising with Scan Request/Response]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_17_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 30, 5);

    success = True;
    adData = ADData();

    for i in range(3):
        for j in range(2):
            advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, '' ) if j == 0 else \
                                      adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT IUT IUT IUT IUT IUT IUT I' );
            if   i == 1:
                success = success and preamble_set_public_address( transport, lowerTester, address_scramble_OUI( 0x456789ABCDEF ), trace );
            elif i == 2:
                success = success and preamble_set_public_address( transport, lowerTester, address_exchange_OUI_LAP( 0x456789ABCDEF ), trace );

            success = scanner.enable() and success;
            success = advertiser.enable() and success;
            scanner.monitor();
            success = advertiser.disable() and success;
            success = scanner.disable() and success;
            success = success and scanner.qualifyReports( 5 );
            success = success and scanner.qualifyResponses( 5, advertiser.responseData );

    return success;

"""
    LL/DDI/ADV/BV-18-C [Discoverable Undirected Advertiser Filtering Scan requests ]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen
"""
def ll_ddi_adv_bv_18_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 30, None, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    ownAddress = Address( ExtendedAddressType.PUBLIC );
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT' );

    success = advertiser.enable() and success;

    for i, addressType in enumerate([ ExtendedAddressType.PUBLIC, ExtendedAddressType.RANDOM, ExtendedAddressType.PUBLIC ]):
        if   i == 0:
            success = success and preamble_set_public_address( transport, lowerTester, address_scramble_LAP( 0x456789ABCDEF ), trace);
        elif i == 1:
            success = success and preamble_set_random_address( transport, lowerTester, 0x456789ABCDEF, trace );
        else:
            success = success and preamble_set_public_address( transport, lowerTester, 0x456789ABCDEF, trace );
            scanner.expectedResponses = 1;

        scanner.ownAddress = Address( addressType );

        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = success and scanner.qualifyReports( 1 if i > 1 else 30 );
        success = success and scanner.qualifyResponses( 1 if i > 1 else 0, advertiser.responseData if i > 1 else None );

    success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-19-C [Low Duty Cycle Directed Advertising on all channels]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen (NOTE: The automatic disconnect due to supervision timeout cannot be achieved)
"""
def ll_ddi_adv_bv_19_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED, 100, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    ownAddress = Address( ExtendedAddressType.PUBLIC );
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    success = advertiser.enable() and success;

    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = success and scanner.qualifyReports( 100 );

    initiator = Initiator(transport, lowerTester, upperTester, trace, ownAddress, publicIdentityAddress(upperTester));
    connected = initiator.connect();
    success = success and connected;

    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/DDI/ADV/BV-20-C [Advertising on the LE 1M PHY on all channels]

    Last modified: 30-07-2019
    Reviewed and verified: 30-07-2019 Henrik Eriksen (NOTE: The PHY channel used in advertising can only be verified by looking at the Air trace)
"""
def ll_ddi_adv_bv_20_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 100, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    AllPhys, TxPhys, RxPhys = 0, PreferredPhysicalChannel.LE_2M, PreferredPhysicalChannel.LE_2M;

    success = success and preamble_default_physical_channel(transport, upperTester, AllPhys, TxPhys, RxPhys, trace);

    success = scanner.enable() and success;
    success = advertiser.enable() and success;
    scanner.monitor();
    success = advertiser.disable() and success;
    success = scanner.disable() and success;
    success = success and scanner.qualifyReports( 100 );

    return success;

"""
    LL/DDI/ADV/BV-21-C [Non-Connectable Extended Legacy Advertising with Data on all channels]
"""
def ll_ddi_adv_bv_21_c(transport, upperTester, lowerTester, trace):

    Handle          = 0;
    Properties      = ExtAdvertiseType.LEGACY;
    PrimMinInterval = toArray(0x0020, 3); # Minimum Advertise Interval = 32 x 0.625 ms = 20.00 ms
    PrimMaxInterval = toArray(0x0022, 3); # Maximum Advertise Interval = 34 x 0.625 ms = 21.25 ms
    PrimChannelMap  = 0x07;  # Advertise on all three channels (#37, #38 and #39)
    OwnAddrType     = SimpleAddressType.PUBLIC;
    PeerAddrType, PeerAddress = random_address( 0x456789ABCDEF );
    FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE;
    TxPower         = 0;
    PrimAdvPhy      = PhysicalChannel.LE_1M; # Primary advertisement PHY is LE 1M
    SecAdvMaxSkip   = 0;     # AUX_ADV_IND shall be sent prior to the next advertising event
    SecAdvPhy       = PhysicalChannel.LE_2M;
    Sid             = 0;
    ScanReqNotifyEnable = 0; # Scan request notifications disabled

    success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval, \
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower, \
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace);

    for i in range(3):
        if i == 0:
            AdvData = [ 0x01 ];
        elif i == 1:
            AdvData = [ ];
        else:
            AdvData = [ 0x1F ] + [ 0 for _ in range(30) ];

        Operation      = FragmentOperation.COMPLETE_FRAGMENT;
        FragPreference = FragmentPreference.FRAGMENT_ALL_DATA;

        success = success and preamble_ext_advertising_data_set(transport, upperTester, Handle, Operation, FragPreference, AdvData, trace);

        scanInterval = 32; # Scan Interval = 32 x 0.625 ms = 20.0 ms
        scanWindow   = 32; # Scan Window   = 32 x 0.625 ms = 20.0 ms
        addrType     = SimpleAddressType.RANDOM;
        filterPolicy = ScanningFilterPolicy.FILTER_NONE;

        success = success and preamble_passive_scanning(transport, lowerTester, scanInterval, scanWindow, addrType, filterPolicy, trace);

        SHandle        = [ Handle ];
        SDuration      = [ 0 ];
        SMaxExtAdvEvts = [ 0 ];

        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, SHandle, SDuration, SMaxExtAdvEvts, trace);

        deltas = [];
        reports = 0;
        while reports < 50:
            if has_event(transport, lowerTester, 100)[0]:
                event = get_event(transport, lowerTester, 100);
                # showEvent(event, eventData, trace);
                isReport = event.subEvent == MetaEvents.BT_HCI_EVT_LE_ADVERTISING_REPORT;
                if isReport:
                    eventType, data = event.decode()[0:3:2];
                    if eventType == AdvertisingReport.ADV_NONCONN_IND:
                        reports += 1;
                        reportData = data;
                        if reports > 1:
                            deltas += [event.time - prevTime];
                        prevTime = event.time;

        success = success and preamble_scan_enable(transport, lowerTester, Scan.DISABLE, ScanFilterDuplicate.DISABLE, trace);
        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.DISABLE, SHandle, SDuration, SMaxExtAdvEvts, trace);
        success = success and (reportData == AdvData);

        trace.trace(7, "Mean distance between Advertise Events %d ms std. deviation %.1f ms" % (statistics.mean(deltas), statistics.pstdev(deltas)));

    return success;

"""
    LL/DDI/ADV/BV-22-C [Extended Advertising, Legacy PDUs, Undirected, CSA #2]
"""
def ll_ddi_adv_bv_22_c(transport, upperTester, lowerTester, trace, packets):

    RoundData = namedtuple('RoundData', ['AdvData', 'ChIdxToScan'])
    rounds = [
        RoundData([0x01], 37),
        RoundData([], 38),
        RoundData([0xF8] + [0x00]*30, 39),
    ]

    advInterval = 0x20 # 32 x 0.625 ms = 20.00 ms
    Handle          = 0
    Properties      = 0b00010011 # ADV_IND legacy PDU
    PrimMinInterval = toArray(advInterval, 3)
    PrimMaxInterval = toArray(advInterval, 3)
    PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
    OwnAddrType     = SimpleAddressType.PUBLIC
    PeerAddrType    = SimpleAddressType.PUBLIC
    PeerAddress     = toArray(0x456789ABCDEF, 6)
    FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
    TxPower         = 0
    PrimAdvPhy      = PhysicalChannel.LE_1M
    SecAdvMaxSkip   = 0
    SecAdvPhy       = 0
    Sid             = 0
    ScanReqNotifyEnable = 0

    success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
    if not success:
        return success

    for round in rounds:

        success = success and le_set_extended_advertising_data(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, 0x00, round.AdvData, 100) == 0
        if not success:
            return False

        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0x00], [0x00], trace)
        if not success:
            return success

        # Wait until at least 50 advertising events should have been sent
        transport.wait(math.ceil((advInterval*0.625 + 10) * 50))

        # "Scan" on a single primary advertising channel as indicated in RoundData and expect
        # the IUT to send ADV_IND packets, with ChSel set as specified, including the data submitted
        packetCount = 0
        chNumToScan = 0 if round.ChIdxToScan == 37 else 12 if round.ChIdxToScan == 38 else 39
        for packet in packets.fetch(packet_filter=('ADV_IND')):
            if packet.channel_num == chNumToScan:
                packetCount += 1
                success = success and packet.header.ChSel == 1
                success = success and len(packet.payload.AdvData) == len(round.AdvData)
                for i in range(len(round.AdvData)):
                    success = success and packet.payload.AdvData[i] == round.AdvData[i]

        # Check that the packets have been sent
        success = success and packetCount >= 50

        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.DISABLE, [Handle], [0x00], [0x00], trace)
        if not success:
            return success

        # Flush events and packets for next round
        flush_events(transport, upperTester, 100)
        packets.flush()

    return success


"""
    LL/DDI/ADV/BV-27-C [Extended Advertising, Host Modifying Data and ADI]
"""
def ll_ddi_adv_bv_27_c(transport, upperTester, lowerTester, trace, packets):
    status, MaxAdvDataLen = le_read_maximum_advertising_data_length(transport, upperTester, 200)

    if status != 0 or MaxAdvDataLen < 0x001F or MaxAdvDataLen > 0x0672:
        return False

    # Input data for each round
    RoundData = namedtuple('RoundData', ['DataLength'])
    rounds = [
        RoundData(MaxAdvDataLen),
        RoundData(1),
        RoundData(251),
    ]

    advInterval = 0xA0 # 160 x 0.625 ms = 100.00 ms
    Handle          = 0
    Properties      = 0x0000
    PrimMinInterval = toArray(advInterval, 3)
    PrimMaxInterval = toArray(advInterval, 3)
    PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
    OwnAddrType     = SimpleAddressType.PUBLIC
    PeerAddrType    = SimpleAddressType.PUBLIC
    PeerAddress     = toArray(0x456789ABCDEF, 6)
    FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
    TxPower         = 0
    PrimAdvPhy      = PhysicalChannel.LE_1M
    SecAdvMaxSkip   = 0
    SecAdvPhy       = PhysicalChannel.LE_1M
    Sid             = 0
    ScanReqNotifyEnable = 0

    success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
    if not success:
        return success

    advertisingStarted = False
    prevRoundAdvData = None
    prevDID = None
    prevAdvData = None

    for roundNumber in range(len(rounds)):
        round = rounds[roundNumber]
        if round.DataLength > MaxAdvDataLen:
            # Skip unsupported advertising data length
            continue

        # Set adv data
        advData = random.randint(1, 255, round.DataLength)
        if not set_complete_ext_adv_data(transport, upperTester, Handle, 0x00, advData):
            return False

        flush_events(transport, upperTester, 100)
        if not advertisingStarted:
            success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0x00], [0x00], trace)
            if not success:
                return success
            advertisingStarted = True

        # Wait until 10 events have been received
        transport.wait(math.ceil(10.5*advInterval*0.625))

        # Check ADV_EXT_INDs
        # AdvMode set to 00b; AuxPtr Extended Header field present. The ADI field shall be present and contain the 
        # Advertising Set ID (SID) used by the Upper Tester in step 3 and an Advertising Data ID.
        for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
            success = success and packet.payload['AdvMode'] == 0b00
            success = success and 'AuxPtr' in packet.payload
            success = success and 'ADI' in packet.payload
            success = success and packet.payload['ADI'].SID == Sid
        
        def isAdvDataSame(advDataA, advDataB):
            if len(advDataA) != len(advDataB):
                return False
            for i in range(len(advDataA)):
                if advDataA[i] != advDataB[i]:
                    return False
            return True

        completeAdvDataFound = 0

        # Check AUX_ADV_INDs
        # Uses the LE 1M PHY with the AdvMode field set to 00b and an ADI field matching the ADI field of the ADV_EXT_IND
        # If the AUX_ADV_IND PDU does not contain all the advertising data submitted, it shall include an AuxPtr field
        # The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
        for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
            success = success and packet.phy == '1M'
            success = success and packet.payload['AdvMode'] == 0b00
            success = success and 'ADI' in packet.payload
            success = success and packet.payload['ADI'].SID == Sid
            for superiorPacket in packet.payload['SuperiorPackets']:
                success = success and packet.payload['ADI'].DID == superiorPacket.payload['ADI'].DID
                # Packet air length is: pre-amble + AA + header + payload + CRC
                packetLength = 1 + 4 + 2 + len(superiorPacket) + 3
                packetAirtime = 8*packetLength
                success = success and packet.ts >= superiorPacket.ts + packetAirtime + 300
            if round.DataLength > len(packet.payload['AD']):
                success = success and 'AuxPtr' in packet.payload
            else:
                # Check advertising data against input (shall match either advData or prevRoundAdvData)
                success = success and (isAdvDataSame(advData, packet.payload['AD']) or isAdvDataSame(prevRoundAdvData, packet.payload['AD']))
                if isAdvDataSame(advData, packet.payload['AD']):
                    completeAdvDataFound += 1
                if prevAdvData != None:
                    # If advertising data is not the same as previous event but the DID field has not changed, a Fail Verdict is recorded
                    if not isAdvDataSame(prevAdvData, packet.payload['AD']):
                        success = success and prevDID != packet.payload['ADI'].DID
                prevAdvData = packet.payload['AD']
                prevDID = packet.payload['ADI'].DID

        def collectChainedAdvData(packet):
            advData = bytes([])
            if packet.payload['SuperiorPackets'][0].type.name != 'ADV_EXT_IND':
                advData = collectChainedAdvData(packet.payload['SuperiorPackets'][0])
            advData += packet.payload['AD']
            return advData

        # Check AUX_CHAIN_INDs
        # AdvMode set to 00b; contains additional advertising data submitted
        # The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
        for packet in packets.fetch(packet_filter=('AUX_CHAIN_IND')):
            success = success and packet.payload['AdvMode'] == 0b00
            for superiorPacket in packet.payload['SuperiorPackets']:
                # Packet air length is: pre-amble + AA + header + payload + CRC
                packetLength = 1 + 4 + 2 + len(superiorPacket) + 3
                packetAirtime = 8*packetLength
                success = success and packet.ts >= superiorPacket.ts + packetAirtime + 300
            if 'AuxPtr' not in packet.payload:
                # Collect complete advertising data and check advertising data against input (shall match either advData or prevRoundAdvData)
                collectedAdvData = collectChainedAdvData(packet)
                success = success and (isAdvDataSame(advData, collectedAdvData) or isAdvDataSame(prevRoundAdvData, collectedAdvData))
                if isAdvDataSame(advData, collectedAdvData):
                    completeAdvDataFound += 1
                if prevAdvData != None:
                    # If advertising data is not the same as previous event but the DID field has not changed, a Fail Verdict is recorded
                    if not isAdvDataSame(prevAdvData, collectedAdvData):
                        success = success and prevDID != packet.payload['ADI'].DID
                prevAdvData = collectedAdvData
                prevDID = packet.payload['ADI'].DID

        # Check that we actually got the complete data (ie. that some AUX_CHAIN_INDs/AUX_ADV_INDs do not have an aux ptr)
        success = success and completeAdvDataFound > 0

        prevRoundAdvData = advData

        # Flush events and packets for next round
        flush_events(transport, upperTester, 100)
        packets.flush()

    # Stop advertising
    success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.DISABLE, [Handle], [0x00], [0x00], trace)

    return success

"""
    LL/DDI/ADV/BV-28-C [Extended Advertising, Overlapping Extended Advertising Events]
"""
def ll_ddi_adv_bv_28_c(transport, upperTester, lowerTester, trace, packets):
    RoundData = namedtuple('RoundData', ['EventProperties', 'SecondaryAdvertisingMaxSkip', 'RepeatCount'])
    rounds = [
        RoundData(0x0000, 0x01, 100),
        RoundData(0x0000, 0x0F, 50),
        RoundData(0x0000, 0xFF, 10),
        RoundData(0x0001, 0x08, 50),
        RoundData(0x0004, 0x08, 50),
        RoundData(0x0005, 0x08, 50),
    ]

    success = True

    for round in rounds:

        advInterval = 0x20 # 32 x 0.625 ms = 20.00 ms
        Handle          = 0
        Properties      = round.EventProperties
        PrimMinInterval = toArray(advInterval, 3)
        PrimMaxInterval = toArray(advInterval, 3)
        PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
        OwnAddrType     = SimpleAddressType.PUBLIC
        PeerAddrType    = SimpleAddressType.PUBLIC
        PeerAddress     = toArray(0x456789ABCDEF, 6)
        FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
        TxPower         = 0
        PrimAdvPhy      = PhysicalChannel.LE_1M
        SecAdvMaxSkip   = round.SecondaryAdvertisingMaxSkip
        SecAdvPhy       = PhysicalChannel.LE_1M
        Sid             = 0
        ScanReqNotifyEnable = 0

        success = success and preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
        if not success:
            return success

        advData = random.randint(1, 255, 1)
        status = le_set_extended_advertising_data(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, 0x00, advData, 100)
        if status != 0:
            return False

        flush_events(transport, upperTester, 100)
        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0x00], [0x00], trace)
        if not success:
            return success

        # Wait until we have RepeatCount AUX_ADV_INDs
        auxAdvIndCount = 0
        while auxAdvIndCount < round.RepeatCount:
            waitForMs = math.ceil(advInterval * 0.625 * (round.RepeatCount - auxAdvIndCount))
            transport.wait(waitForMs)
            auxAdvIndCount = 0
            for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
                auxAdvIndCount += 1

        def getAuxPtrTime(packet):
            units = 30 if packet.payload['AuxPtr'].offsetUnits == 0 else 300
            return packet.ts + packet.payload['AuxPtr'].auxOffset * units

        # Check ADV_EXT_INDs:
        # - AuxPtr Extended Header field present and the AdvMode set according to expected properties
        # - The AuxPtrs in each ADV_EXT_IND PDU sent in overlapping extended advertising events have 
        #   Aux Offset and Offset Units values that refer to the same time within one Offset Unit
        # Last part requires grouping ADV_EXT_INDs that refer to the same AUX_ADV_IND together
        groups = []
        currentGroup = []
        for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
            success = success and packet.payload['AdvMode'] == round.EventProperties & 0x03
            success = success and 'AuxPtr' in packet.payload
            # Put into current group if channel idx and offset matches (offset is matched if within 1 ms)
            if len(currentGroup) == 0 or (currentGroup[0].payload['AuxPtr'].chIdx == packet.payload['AuxPtr'].chIdx and
               abs(getAuxPtrTime(packet) - getAuxPtrTime(currentGroup[0])) < 1000):
                currentGroup += [packet]
            else:
                groups += [currentGroup]
                currentGroup = [packet]
        if len(currentGroup) > 0:
            groups += [currentGroup]

        for group in groups:
            auxPtrMax = None
            auxPtrMin = None
            offsetUnit = 30
            for packet in group:
                auxPtrTime = getAuxPtrTime(packet)
                if auxPtrMax == None or auxPtrTime > auxPtrMax:
                    auxPtrMax = auxPtrTime
                if auxPtrMin == None or auxPtrTime < auxPtrMin:
                    auxPtrMin = auxPtrTime
                if packet.payload['AuxPtr'].offsetUnits == 1:
                    offsetUnit = 300
            success = success and (auxPtrMin - auxPtrMax) <= offsetUnit

        # Check AUX_ADV_INDs
        # Uses the LE 1M PHY with the AdvMode field set according to expected properties
        # The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
        # Check that no more than Secondary_Advertising_Max_Skip+1 extended advertising events overlap
        for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
            success = success and packet.payload['AdvMode'] == round.EventProperties & 0x03
            success = success and packet.phy == '1M'

            lastSuperiorPacket = None
            currentEventPacketCount = 0
            eventCount = 0
            for superiorPacket in packet.payload['SuperiorPackets']:
                # Packet air length is: pre-amble + AA + header + payload + CRC
                packetLength = 1 + 4 + 2 + len(superiorPacket) + 3
                packetAirtime = 8*packetLength
                success = success and packet.ts >= superiorPacket.ts + packetAirtime + 300

                if lastSuperiorPacket == None:
                    eventCount = 1
                    currentEventPacketCount = 1
                else:
                    # Assume new advertising event starting if a) there already is 3 ADV_EXT_INDs for the current event or b) there is a gap of more than 5 ms
                    if currentEventPacketCount == 3 or lastSuperiorPacket.ts > superiorPacket.ts + 5000:
                        currentEventPacketCount = 1
                        eventCount += 1
                    else:
                        currentEventPacketCount += 1
                lastSuperiorPacket = superiorPacket

            success = success and eventCount <= round.SecondaryAdvertisingMaxSkip + 1

        # Disable advertising
        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.DISABLE, [Handle], [0x00], [0x00], trace)
        if not success:
            return success

        # Flush events and packets for next round
        flush_events(transport, upperTester, 100)
        packets.flush()

    return success

# Implemements LL/DDI/ADV/BV-45-C and LL/DDI/ADV/BV-52-C (only difference is the PHY)
def do_ll_ddi_adv_bv_45_52_c(transport, upperTester, lowerTester, trace, packets, phy):

    status, MaxAdvDataLen = le_read_maximum_advertising_data_length(transport, upperTester, 200)

    if status != 0:
        return False

    if MaxAdvDataLen < 0x001F or MaxAdvDataLen > 0x0672:
        return False

    AdvA_IUT = 0x123456789ABC
    AdvA_NonIUT = 0xCBA987654321

    # Input data for each round
    RoundData = namedtuple('RoundData', ['EventProperties', 'ScanRequestNotification', 'AdvDataLen', 'FragmentPref', 'AdvA'])
    rounds = [
        RoundData(0x0002, 0x00, 1, 0x00, AdvA_IUT),
        RoundData(0x0002, 0x00, 31, 0x00, AdvA_IUT),
        RoundData(0x0002, 0x00, 474, 0x00, AdvA_IUT),
        RoundData(0x0002, 0x00, 711, 0x00, AdvA_IUT),
        RoundData(0x0002, 0x00, 948, 0x00, AdvA_IUT),
        RoundData(0x0002, 0x00, MaxAdvDataLen, 0x00, AdvA_IUT),
        RoundData(0x0002, 0x01, MaxAdvDataLen, 0x01, AdvA_IUT),
        RoundData(0x0002, 0x00, 31, 0x00, AdvA_NonIUT),
        RoundData(0x0006, 0x00, 1, 0x00, AdvA_IUT),
        RoundData(0x0006, 0x00, 251, 0x00, AdvA_IUT),
        RoundData(0x0006, 0x00, MaxAdvDataLen, 0x00, AdvA_IUT),
        RoundData(0x0006, 0x00, 31, 0x00, AdvA_NonIUT),
    ]

    success = True

    for round in rounds:

        if round.AdvDataLen > MaxAdvDataLen:
            # Skip unsupported advertising data length
            continue

        advInterval = 0xA0 # 160 x 0.625 ms = 100.00 ms
        Handle          = 0
        Properties      = round.EventProperties
        PrimMinInterval = toArray(advInterval, 3)
        PrimMaxInterval = toArray(advInterval, 3)
        PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
        OwnAddrType     = SimpleAddressType.PUBLIC
        PeerAddrType    = SimpleAddressType.PUBLIC
        PeerAddress     = toArray(0x456789ABCDEF, 6)
        FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
        TxPower         = 0
        PrimAdvPhy      = PhysicalChannel.LE_1M
        SecAdvMaxSkip   = 0
        SecAdvPhy       = phy
        Sid             = 0
        ScanReqNotifyEnable = round.ScanRequestNotification

        success = success and preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
        if not success:
            return success

        # Set scan response data
        advData = random.randint(1, 255, round.AdvDataLen)
        if not set_complete_ext_scan_response_data(transport, upperTester, Handle, round.FragmentPref, advData):
            return False

        flush_events(transport, upperTester, 100)
        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0], [0], trace)

        if not success:
            return success

        auxAdvIndPacket = wait_for_AUX_ADV_IND_end(transport, packets)

        # Transmit an AUX_SCAN_REQ
        packetData = (0b0011 + (12 << 8)).to_bytes(2, 'little', signed=False) # header - PDU Type 0b0011, ChSel, TxAdd and RxAdd all 0, length 12
        packetData = b''.join([packetData, 0x456789ABCDEF.to_bytes(6, 'little', signed=False)]) # ScanA
        packetData = b''.join([packetData, round.AdvA.to_bytes(6, 'little', signed=False)]) # AdvA
        CRC = calcBLECRC(0x555555, packetData)
        packetData = b''.join([packetData, CRC.to_bytes(3, 'little', signed=False)])

        # Calculate transmit timestamp (T_IFS from end of AUX_ADV_IND)
        reqTransmitTime = auxAdvIndPacket.ts + get_packet_air_time(auxAdvIndPacket) + 150
        # Note: Packet air length is: pre-amble + AA + packetData (which includes header and CRC)
        reqAirTime = math.ceil(((2 if auxAdvIndPacket.phy == '2M' else 1) + 4 + len(packetData))*8/(2 if auxAdvIndPacket.phy == '2M' else 1))

        transport.low_level_device.tx(channel_num_to_index(auxAdvIndPacket.channel_num), auxAdvIndPacket.phy, auxAdvIndPacket.aa, reqTransmitTime, packetData)

        def check_ADV_EXT_INDs():
            # Check ADV_EXT_INDs
            # AdvMode set to 10b with the AuxPtr Extended Header field present. The ADV_EXT_IND PDU shall include the ADI field
            # with the SID set to the value used in step 3. The ADV_EXT_IND PDU shall not include the CTEInfo, SyncInfo, ACAD, or TxPower fields
            success = True
            for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
                success = success and packet.payload['AdvMode'] == 0b10
                success = success and 'AuxPtr' in packet.payload
                success = success and 'ADI' in packet.payload
                success = success and packet.payload['ADI'].SID == Sid
                success = success and 'CTEInfo' not in packet.payload
                success = success and 'SyncInfo' not in packet.payload
                success = success and 'ACAD' not in packet.payload
                success = success and 'TxPower' not in packet.payload
            return success

        def check_AUX_ADV_INDs():
            # Check AUX_ADV_IND(s)
            # AdvMode field set to 10b. The AUX_ADV_IND PDU shall include the ADI field matching the ADI field from earlier. The AUX_ADV_IND PDU shall not include the
            # CTEInfo, AuxPtr, SyncInfo, TxPower, or AdvData fields
            # The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
            success = True
            for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
                success = success and packet.payload['AdvMode'] == 0b10
                success = success and 'ADI' in packet.payload
                success = success and packet.payload['ADI'].SID == Sid
                success = success and 'CTEInfo' not in packet.payload
                success = success and 'AuxPtr' not in packet.payload
                success = success and 'SyncInfo' not in packet.payload
                success = success and 'TxPower' not in packet.payload
                success = success and 'AD' not in packet.payload
                for superiorPacket in packet.payload['SuperiorPackets']:
                    success = success and packet.payload['ADI'].DID == superiorPacket.payload['ADI'].DID
                    success = success and packet.ts >= superiorPacket.ts + get_packet_air_time(superiorPacket) + 300
            return success

        if round.AdvA != AdvA_IUT:
            # Wait for 10 ms and check that there was no reply
            transport.wait(10)
            success = success and check_ADV_EXT_INDs()
            success = success and check_AUX_ADV_INDs()

            # No AUX_SCAN_RSP packet should have been sent from the IUT
            success = success and not packets.findLast(packet_filter='AUX_SCAN_RSP')

        else:
            # Wait for complete reply
            transport.wait(10)
            prevPacket = None
            lastPacket = packets.findLast(packet_filter='AUX_SCAN_RSP')
            if not lastPacket:
                success = False
            elif 'AuxPtr' in lastPacket.payload:
                # Wait for all chained PDUs
                while ('AuxPtr' in lastPacket.payload and prevPacket != lastPacket):
                    prevPacket = lastPacket
                    # Wait for aux ptr offset + 10 ms (to be on the safe side) and check for the chained PDU
                    offsetEnd = (lastPacket.payload['AuxPtr'].auxOffset + 1) * (30 if lastPacket.payload['AuxPtr'].offsetUnits == 0 else 300)
                    transport.wait(math.ceil(offsetEnd/1000) + 10)
                    lastPacket = packets.findLast(packet_filter='AUX_CHAIN_IND')

            success = success and check_ADV_EXT_INDs()
            success = success and check_AUX_ADV_INDs()

            completeAdvDataFound = False

            # Check AUX_SCAN_RSP
            # T_IFS after the end of the AUX_SCAN_REQ PDU with AdvMode set to 00b, AdvA set to the IUT’s advertising address,
            # TargetA and CTEInfo not present, and ADI field either not present or matches the AUX_ADV_IND. If the AUX_SCAN_RSP PDU does not contain all
            # the data submitted, it shall include an AuxPtr field
            packet = packets.findLast(packet_filter='AUX_SCAN_RSP')
            # Check transmission time (note: 2 microseconds jitter is accepted)
            success = success and packet.ts >= reqTransmitTime + reqAirTime + 150 - 2
            success = success and packet.ts <= reqTransmitTime + reqAirTime + 150 + 2
            success = success and packet.payload['AdvMode'] == 0b00
            success = success and packet.payload['AdvA'] == AdvA_IUT
            success = success and 'TargetA' not in packet.payload
            success = success and 'CTEInfo' not in packet.payload
            if 'ADI' in packet.payload:
                success = success and packet.payload['ADI'].SID == auxAdvIndPacket.payload['ADI'].SID
                success = success and packet.payload['ADI'].DID == auxAdvIndPacket.payload['ADI'].DID
            if len(packet.payload['AD']) < round.AdvDataLen:
                success = success and 'AuxPtr' in packet.payload
            else:
                completeAdvDataFound = True
                # check advertising data against input
                for i in range(round.AdvDataLen):
                    success = success and packet.payload['AD'][i] == advData[i]

            def collectChainedAdvData(packet):
                advData = bytes([])
                if packet.type.name != 'AUX_SCAN_RSP':
                    advData = collectChainedAdvData(packet.payload['SuperiorPackets'][0])
                advData += packet.payload['AD']
                return advData
    
            # Check AUX_CHAIN_INDs
            # Shall include the AdvData field containing additional data submitted. The AUX_CHAIN_IND 
            # PDU shall not include the AdvA, TargetA, CTEInfo, TxPower, or SyncInfo fields
            # The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
            for packet in packets.fetch(packet_filter=('AUX_CHAIN_IND')):
                success = success and 'AdvA' not in packet.payload
                success = success and 'TargetA' not in packet.payload
                success = success and 'CTEInfo' not in packet.payload
                success = success and 'TxPower' not in packet.payload
                success = success and 'SyncInfo' not in packet.payload
                for superiorPacket in packet.payload['SuperiorPackets']:
                    success = success and packet.ts >= superiorPacket.ts + get_packet_air_time(superiorPacket) + 300
                if 'AuxPtr' not in packet.payload:
                    # Collect complete advertising data and check advertising data against input
                    completeAdvDataFound = True
                    collectedAdvData = collectChainedAdvData(packet)
                    success = success and len(collectedAdvData) == round.AdvDataLen
                    for i in range(round.AdvDataLen):
                        success = success and collectedAdvData[i] == advData[i]

            # Check that the full advertising data was sent
            success = success and completeAdvDataFound

            # If scan request notifications are enabled,  Upper Tester receives an HCI_LE_Scan_Request_Received event
            # from the IUT with the advertising handle used and the Lower Tester’s address
            if round.ScanRequestNotification == 0x01:
                if has_event(transport, upperTester, 200)[0]:
                    event = get_event(transport, upperTester, 100)
                    eventHandle, eventAddress = event.decode()
                    success = success and eventHandle == Handle
                    success = success and eventAddress == Address(ExtendedAddressType.PUBLIC, 0x456789ABCDEF)
                else:
                    success = False

        flush_events(transport, upperTester, 100)
        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.DISABLE, [Handle], [0], [0], trace)
        if not success:
            return success

        # Flush events and packets for next round
        flush_events(transport, upperTester, 100)
        packets.flush()

    return success

"""
    LL/DDI/ADV/BV-45-C [Extended Advertising, Scannable - ADI allowed in scan response]
"""
def ll_ddi_adv_bv_45_c(transport, upperTester, lowerTester, trace, packets):
    return do_ll_ddi_adv_bv_45_52_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_1M)

# Implements LL/DDI/ADV/BV-47-C and LL/DDI/ADV/BV-49-C (only difference is the PHY)
def do_ll_ddi_adv_bv_47_49_c(transport, upperTester, lowerTester, trace, packets, phy):

    status, MaxAdvDataLen = le_read_maximum_advertising_data_length(transport, upperTester, 200)

    if status != 0:
        return False

    # Input data for each round
    RoundData = namedtuple('RoundData', ['EventProperties', 'DataLength', 'FragmentPref', 'Duration', 'MaxEvents'])
    rounds = [
        RoundData(0x0000, 0, 0x00, 0x0000, 0x00),
        RoundData(0x0000, 31, 0x00, 0x0000, 0x00),
        RoundData(0x0000, 474, 0x00, 0x0000, 0x00),
        RoundData(0x0000, 711, 0x00, 0x0000, 0x00),
        RoundData(0x0000, 948, 0x00, 0x0000, 0x00),
        RoundData(0x0000, MaxAdvDataLen, 0x00, 0x0000, 0x00),
        RoundData(0x0000, MaxAdvDataLen, 0x01, 0x0000, 0x00),
        RoundData(0x0004, 0, 0x00, 0x0000, 0x00),
        RoundData(0x0004, 251, 0x00, 0x0000, 0x00),
        RoundData(0x0004, MaxAdvDataLen, 0x00, 0x0000, 0x00),
        RoundData(0x0000, 0, 0x00, 0x01F4, 0x00),
        RoundData(0x0004, 0, 0x00, 0x01F4, 0x00),
        RoundData(0x0000, 0, 0x00, 0x0000, 0x32),
        RoundData(0x0004, 0, 0x00, 0x0000, 0x32),
    ]

    success = True

    for round in rounds:

        if round.DataLength > MaxAdvDataLen:
            # Skip unsupported advertising data length
            continue

        advInterval = 0xA0 # 160 x 0.625 ms = 100.00 ms
        Handle          = 0
        Properties      = round.EventProperties
        PrimMinInterval = toArray(advInterval, 3)
        PrimMaxInterval = toArray(advInterval, 3)
        PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
        OwnAddrType     = SimpleAddressType.PUBLIC
        PeerAddrType    = SimpleAddressType.PUBLIC
        PeerAddress     = toArray(0x456789ABCDEF, 6)
        FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
        TxPower         = 0
        PrimAdvPhy      = PhysicalChannel.LE_1M
        SecAdvMaxSkip   = 0
        SecAdvPhy       = phy
        Sid             = 0
        ScanReqNotifyEnable = 0

        success = success and preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
        if not success:
            return success

        if round.DataLength > 0:
            # Set adv data
            advData = random.randint(1, 255, round.DataLength)
            if not set_complete_ext_adv_data(transport, upperTester, Handle, round.FragmentPref, advData):
                return False
        else:
            advData = []
            status = le_set_extended_advertising_data(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, round.FragmentPref, advData, 100)
            if status != 0:
                return False

        flush_events(transport, upperTester, 100)
        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [round.Duration], [round.MaxEvents], trace)

        if not success:
            return success

        if round.Duration != 0:
            # Wait until end of duration + 500ms (to make sure advertising has stopped)
            transport.wait(round.Duration*10 + 500)
        elif round.MaxEvents != 0:
            # Wait for max_events*adv_interval + 500 ms (to make sure advertising has stopped)
            transport.wait(math.ceil(round.MaxEvents*advInterval*0.625 + 500))
        else:
            # Wait until ~10 events have been received
            transport.wait(math.ceil(10.5*advInterval*0.625))

        if round.Duration == 0 and round.MaxEvents == 0:
            # Disable advertising now
            success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.DISABLE, [Handle], [round.Duration], [round.MaxEvents], trace)
        else:
            # LE Advertising Set Terminated event shall be received with correct error code
            event = get_event(transport, upperTester, 200)
            success = success and event
            if event:
                status, advertiseHandle, connectionHandle, completedEvents = event.decode()
                success = success and advertiseHandle == Handle
                if round.Duration != 0:
                    success = success and status == 0x3C # Advertising Timeout
                else:
                    success = success and status == 0x43 # Limit Reached

        # Check ADV_EXT_INDs
        # AdvMode set to 00b; The ADV_EXT_IND PDU shall not include the SyncInfo, TxPower, ACAD, or AdvData
        # fields. If advertising data was set, the ADV_EXT_IND PDU shall include the AuxPtr field;
        # otherwise, the ADV_EXT_IND PDU *may* include the AuxPtr field. If the AuxPtr field is included,
        # the ADV_EXT_IND PDU shall also include the ADI field with the SID set to the value used; otherwise that field shall not be included
        for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
            success = success and packet.payload['AdvMode'] == 0b00
            success = success and 'SyncInfo' not in packet.payload
            success = success and 'TxPower' not in packet.payload
            success = success and 'ACAD' not in packet.payload
            success = success and 'AD' not in packet.payload
            if round.DataLength > 0:
                success = success and 'AuxPtr' in packet.payload
            if 'AuxPtr' in packet.payload:
                success = success and 'ADI' in packet.payload
                if 'ADI' in packet.payload:
                    success = success and packet.payload['ADI'].SID == Sid

        completeAdvDataFound = 0
        # Check AUX_ADV_INDs
        # AdvMode set to 00b; The AUX_ADV_IND PDU shall not include
        # the SyncInfo, or TxPower fields. The AUX_ADV_IND PDU shall include the ADI field
        # matching the ADI field from the ADV_EXT_IND. If the AUX_ADV_IND PDU does not contain all advertising data
        # it shall include an AuxPtr field.
        # The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
        for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
            success = success and packet.payload['AdvMode'] == 0b00
            success = success and 'SyncInfo' not in packet.payload
            success = success and 'TxPower' not in packet.payload
            success = success and 'ADI' in packet.payload
            success = success and packet.payload['ADI'].SID == Sid
            for superiorPacket in packet.payload['SuperiorPackets']:
                success = success and packet.payload['ADI'].DID == superiorPacket.payload['ADI'].DID
                success = success and packet.ts >= superiorPacket.ts + 300
            if round.DataLength > 0:
                if round.DataLength > len(packet.payload['AD']):
                    success = success and 'AuxPtr' in packet.payload
                else:
                    # Check advertising data against input
                    completeAdvDataFound += 1
                    success = success and len(packet.payload['AD']) == round.DataLength
                    for i in range(round.DataLength):
                        success = success and packet.payload['AD'][i] == advData[i]
            else:
                success = 'AD' not in packet.payload

        def collectChainedAdvData(packet):
            advData = bytes([])
            if packet.payload['SuperiorPackets'][0].type.name != 'ADV_EXT_IND':
                advData = collectChainedAdvData(packet.payload['SuperiorPackets'][0])
            advData += packet.payload['AD']
            return advData

        # Check AUX_CHAIN_INDs
        # AdvMode set to 00b; The AUX_CHAIN_IND PDU shall include the ADI field matching the ADI field from AUX_ADV_IND
        # and the AdvData field containing additional data. The AUX_CHAIN_IND PDU shall not include the AdvA,
        # TargetA, TxPower, or SyncInfo fields
        # The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
        for packet in packets.fetch(packet_filter=('AUX_CHAIN_IND')):
            success = success and packet.payload['AdvMode'] == 0b00
            success = success and 'AdvA' not in packet.payload
            success = success and 'TargetA' not in packet.payload
            success = success and 'TxPower' not in packet.payload
            success = success and 'SyncInfo' not in packet.payload
            success = success and 'ADI' in packet.payload
            success = success and packet.payload['ADI'].SID == Sid
            for superiorPacket in packet.payload['SuperiorPackets']:
                success = success and packet.payload['ADI'].DID == superiorPacket.payload['ADI'].DID
                success = success and packet.ts >= superiorPacket.ts + 300
            if 'AuxPtr' not in packet.payload:
                # Collect complete advertising data and check advertising data against input
                completeAdvDataFound += 1
                collectedAdvData = collectChainedAdvData(packet)
                success = success and len(collectedAdvData) == round.DataLength
                for i in range(round.DataLength):
                    success = success and collectedAdvData[i] == advData[i]

        if round.DataLength > 0:
            # Check that we actually got the complete data (ie. that some AUX_CHAIN_INDs/AUX_ADV_INDs do not have an aux ptr)
            success = success and completeAdvDataFound > 0

        if round.MaxEvents != 0:
            # Calculate number of advertising events by grouping ADV_EXT_INDs (a delay of more than 10 ms, 3 packets already in the group or a duplicate channel number means a new group)
            numEvents = 0
            group = []
            lastTs = -10000
            for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
                if lastTs < packet.ts - 10000 or len(group) >= 3:
                    group = [packet]
                    numEvents += 1
                else:
                    # Check if this packets channel is already part of the current group
                    duplicateChannel = False
                    for groupPacket in group:
                        if groupPacket.channel_num == packet.channel_num:
                            duplicateChannel = True
                            break
                    if duplicateChannel:
                        group = [packet]
                        numEvents += 1
                    else:
                        group += [packet]
                lastTs = packet.ts
            # Verify that the IUT did not send more than Max_Extended_Advertising_Events advertising events
            success = success and numEvents <= round.MaxEvents
        elif round.Duration != 0:
            # Check that advertising is stopped after duration has elapsed
            maxDurationMs = (10 * round.Duration + 10.000) * (1 + 500.0/1000000.0) + 0.016

            firstAdv = None
            lastAdv = None
            for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
                if not firstAdv:
                    firstAdv = packet
                lastAdv = packet

            actualDurationMs = (lastAdv.ts - firstAdv.ts) / 1000
            success = success and actualDurationMs <= maxDurationMs

        if round.EventProperties == 0x0004:
            # TargetA field containing the Lower Tester’s address specified in the HCI_LE_Set_Extended_Advertising_Parameters command
            # is included in either the ADV_EXT_IND PDU or the AUX_ADV_IND PDU, but not both
            advExtIndHasTargetA = False
            auxAdvIndHasTargetA = False
            for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
                advExtIndHasTargetA = 'TargetA' in packet.payload
                if 'TargetA' in packet.payload:
                    success = success and packet.payload['TargetA'] == 0x456789ABCDEF
            for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
                auxAdvIndHasTargetA = 'TargetA' in packet.payload
                if 'TargetA' in packet.payload:
                    success = success and packet.payload['TargetA'] == 0x456789ABCDEF
            success = success and advExtIndHasTargetA != auxAdvIndHasTargetA

        if not success:
            return success

        # Flush events and packets for next round
        flush_events(transport, upperTester, 100)
        packets.flush()

    return success

"""
    LL/DDI/ADV/BV-47-C [Extended Advertising, Non-Connectable - LE 1M PHY]
"""
def ll_ddi_adv_bv_47_c(transport, upperTester, lowerTester, trace, packets):
    return do_ll_ddi_adv_bv_47_49_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_1M)

"""
    LL/DDI/ADV/BV-49-C [Extended Advertising, Non-Connectable - LE 2M PHY]
"""
def ll_ddi_adv_bv_49_c(transport, upperTester, lowerTester, trace, packets):
    return do_ll_ddi_adv_bv_47_49_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_2M)

"""
    LL/DDI/ADV/BV-52-C [Extended Advertising, Scannable - ADI allowed in scan response - LE 2M PHY]
"""
def ll_ddi_adv_bv_52_c(transport, upperTester, lowerTester, trace, packets):
    return do_ll_ddi_adv_bv_45_52_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_2M)


# Implements LL/DDI/ADV/BI-05-C and LL/DDI/ADV/BI-06-C test cases (only difference is the event properties)
def do_ll_ddi_adv_bi_05_06_c(transport, upperTester, lowerTester, trace, eventProperties):

    advInterval = 0x20 # 32 x 0.625 ms = 20.00 ms
    Handle          = 0
    Properties      = eventProperties
    PrimMinInterval = toArray(advInterval, 3)
    PrimMaxInterval = toArray(advInterval, 3)
    PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
    OwnAddrType     = SimpleAddressType.PUBLIC
    PeerAddrType    = SimpleAddressType.PUBLIC
    PeerAddress     = toArray(0, 6)
    FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
    TxPower         = 0
    PrimAdvPhy      = PhysicalChannel.LE_1M
    SecAdvMaxSkip   = 0
    SecAdvPhy       = 0x01
    Sid             = 0
    ScanReqNotifyEnable = 0

    success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)

    advData = random.randint(0, 256, 31)
    if eventProperties & 0x02:
        success = success and preamble_ext_scan_response_data_set(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, 0, advData, trace)
    else:
        success = success and preamble_ext_advertising_data_set(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, 0, advData, trace)

    success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0], [0], trace)

    if not success:
        return False

    advData = random.randint(0, 256, 32)
    if eventProperties & 0x02:
        status = le_set_extended_scan_response_data(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, 0, advData, 200)
    else:
        status = le_set_extended_advertising_data(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, 0, advData, 200)

    # Command should fail with error 0x12 (Invalid HCI Command Parameters)
    success = success and status == 0x12
    event = get_event(transport, upperTester, 200)
    success = success and event.event == Events.BT_HCI_EVT_CMD_COMPLETE
    if success:
        expectedOpCode = CmdOpcodes.BT_HCI_OP_LE_SET_EXT_SCAN_RSP_DATA if (eventProperties & 0x02) else CmdOpcodes.BT_HCI_OP_LE_SET_EXT_ADV_DATA
        numPackets, opCode, status = event.decode()
        success = success and opCode == expectedOpCode and status == 0x12

    return success

"""
    LL/DDI/ADV/BI-05-C [ Disallow Extended Advertising PDU sizes for Legacy Advertising when advertising enabled ]
"""
def ll_ddi_adv_bi_05_c(transport, upperTester, lowerTester, trace):
    # Advertising_Event_Properties set to “Use legacy advertising PDUs” -> bit 4 set
    return do_ll_ddi_adv_bi_05_06_c(transport, upperTester, lowerTester, trace, 0b00010000)

"""
    LL/DDI/ADV/BI-06-C [ Disallow Extended Advertising PDU sizes for Scannable Legacy Advertising when advertising enabled ]
"""
def ll_ddi_adv_bi_06_c(transport, upperTester, lowerTester, trace):
    # Advertising_Event_Properties set to “Scannable Legacy advertising” and “Use legacy advertising PDUs” -> bits 1 and 4 set
    return do_ll_ddi_adv_bi_05_06_c(transport, upperTester, lowerTester, trace, 0b00010010)

"""
    LL/DDI/SCN/BV-01-C [Passive Scanning of Non-Connectable Advertising Packets]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_01_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, upperTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 20);

    success = scanner.enable();

    for i, channel in enumerate([ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ]):
        if   i == 0:
            advertiser.ownAddress = publicIdentityAddress(lowerTester);
        elif i == 1:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
        elif i == 2:
            advertiser.ownAddress = Address( ExtendedAddressType.RANDOM, address_scramble_OUI(0x456789ABCDEF) | 0xC00000000000);

        if advertiser.ownAddress.type == ExtendedAddressType.PUBLIC:
            success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
        else:
            success = success and preamble_set_random_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);

        advertiser.channels = channel;
        advertiser.advertiseData = [ i + 1 ];

        success = advertiser.enable() and success;
        scanner.monitor();
        success = advertiser.disable() and success;
        success = success and scanner.qualifyReports( 20, advertiser.ownAddress, advertiser.advertiseData );

    success = scanner.disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-02-C [Filtered Passive Scanning of Non-Connectable Advertising Packets]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_02_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, upperTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 20, \
                                             AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.CHANNEL_37, ScanningFilterPolicy.FILTER_ACCEPT_LIST);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    success = scanner.enable() and success;

    for i in range(4):
        if   i == 0:
            advertiser.ownAddress = publicIdentityAddress(lowerTester);
        elif i == 1:
            advertiser.ownAddress = Address( ExtendedAddressType.RANDOM, 0x456789ABCDEF | 0xC00000000000);
        elif i == 2:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
        elif i == 3:
            advertiser.ownAddress = Address( ExtendedAddressType.RANDOM, address_exchange_OUI_LAP(0x456789ABCDEF) | 0xC00000000000);

        if advertiser.ownAddress.type == ExtendedAddressType.PUBLIC:
            success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
        else:
            success = success and preamble_set_random_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);

        advertiser.advertiseData = [ i + 1 ];

        success = advertiser.enable() and success;
        scanner.monitor();
        success = advertiser.disable() and success;
        if i == 0:
            success = success and scanner.qualifyReports( 20, advertiser.ownAddress, advertiser.advertiseData );
        else:
            success = success and scanner.qualifyReports( 0 );

    success = scanner.disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-03-C [Active Scanning of Connectable Undirected Advertising Packets]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_03_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 20, 1);

    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IX' );
    success = scanner.enable();

    for channel in [ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ]:
        for i in range(4):
            if   i == 0:
                advertiser.ownAddress = publicIdentityAddress(lowerTester);
            elif i == 1:
                advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_OUI(0x456789ABCDEF) );
            elif i == 2:
                advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
            else:
                advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_exchange_OUI_LAP(0x456789ABCDEF) );

            success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
            advertiser.channels = channel;
            advertiser.advertiseData = [ i + 1 ];

            success = advertiser.enable() and success;
            scanner.monitor();
            success = advertiser.disable() and success;
            success = success and scanner.qualifyReports( 20, advertiser.ownAddress, advertiser.advertiseData );
            success = success and scanner.qualifyResponses( 1, advertiser.responseData );

    success = scanner.disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-04-C [Filtered Active Scanning of Connectable Undirected Advertising Packets]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_04_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 20, 1, \
                                            AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.ALL_CHANNELS, ScanningFilterPolicy.FILTER_ACCEPT_LIST);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IX' );
    success = scanner.enable();

    for channel in [ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ]:
        for i in range(3):
            if   i == 0:
                advertiser.ownAddress = publicIdentityAddress(lowerTester);
            elif i == 1:
                advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
            else:
                advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_exchange_OUI_LAP(0x456789ABCDEF) );

            success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
            advertiser.channels = channel;
            advertiser.advertiseData = [ i + 1 ];

            success = advertiser.enable() and success;
            scanner.monitor();
            success = advertiser.disable() and success;
            if i == 0:
                success = success and scanner.qualifyReports( 20, advertiser.ownAddress, advertiser.advertiseData );
                success = success and scanner.qualifyResponses( 1, advertiser.responseData );
            else:
                success = success and scanner.qualifyReports( 0 );
                success = success and scanner.qualifyResponses( 0 );

    success = scanner.disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-05-C [Scanning for different Advertiser types with and without Data]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_05_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setActiveScanning(transport, upperTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 20, 1);

    adData = ADData();
    advertiser.advertiseData = adData.encode( ADType.FLAGS, ADFlag.BR_EDR_NOT_SUPPORTED );
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IX' );
    success = scanner.enable();

    for i, (channel, advertiseType, reports) in enumerate(zip( \
                             [ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ], \
                             [ Advertising.NON_CONNECTABLE_UNDIRECTED, Advertising.SCANNABLE_UNDIRECTED, Advertising.CONNECTABLE_HDC_DIRECTED ], \
                             [ 20, 30, 15 ] )):
        if   i == 0:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_OUI(0x456789ABCDEF) );
        elif i == 1:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
        else:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_exchange_OUI_LAP(0x456789ABCDEF) );

        success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
        advertiser.channels = channel;
        advertiser.advertiseType = advertiseType;
        advertiser.advertiseData = [ i + 1 ] if i < 2 else [ ];

        scanner.expectedReports = reports;
        scanner.reportType = matchingReportType(advertiseType);

        success = advertiser.enable() and success;
        scanner.monitor();
        success = advertiser.disable() and success;
        success = success and scanner.qualifyReports( reports, advertiser.ownAddress, advertiser.advertiseData );
        if i == 1:
            success = success and scanner.qualifyResponses( 1, advertiser.responseData );
        else:
            success = success and scanner.qualifyResponses( 0 );

    success = scanner.disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-10-C [Passive Scanning for Undirected Advertising Packets with Data]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_10_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 20);

    success = True;

    for i, channel in enumerate([ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ]):
        if   i == 0:
            advertiser.ownAddress = publicIdentityAddress(lowerTester);
        elif i == 1:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
        elif i == 2:
            advertiser.ownAddress = Address( ExtendedAddressType.RANDOM, address_scramble_OUI(0x456789ABCDEF) | 0xC00000000000);

        if (advertiser.ownAddress.type == ExtendedAddressType.PUBLIC):
            success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
        else:
            success = success and preamble_set_random_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);

        advertiser.channels = channel;
        advertiser.advertiseData = [ i + 1 ];

        success = advertiser.enable() and success;
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = advertiser.disable() and success;
        success = success and scanner.qualifyReports( 20, advertiser.ownAddress, advertiser.advertiseData );

    return success;

"""
    LL/DDI/SCN/BV-11-C [Passive Scanning for Directed Advertising Packets]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_11_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED, 20, \
                                             AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.ALL_CHANNELS, ScanningFilterPolicy.FILTER_ACCEPT_LIST);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    for i, channel in enumerate([ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ]):
        if   i == 0:
            advertiser.ownAddress = publicIdentityAddress(lowerTester);
        elif i == 1:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
        elif i == 2:
            advertiser.ownAddress = Address( ExtendedAddressType.RANDOM, 0x456789ABCDEF | 0xC00000000000 );

        if (advertiser.ownAddress.type == ExtendedAddressType.PUBLIC):
            success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
        else:
            success = success and preamble_set_random_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);

        advertiser.channels = channel;

        success = advertiser.enable() and success;
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = advertiser.disable() and success;
        if i == 0:
            success = success and scanner.qualifyReports( 20, advertiser.ownAddress, advertiser.advertiseData );
        else:
            success = success and scanner.qualifyReports( 0 );

    return success;

"""
    LL/DDI/SCN/BV-12-C [Passive Scanning for Discoverable Undirected Advertising Packets]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen (NOTE: Advertise data not modified for each advertise event)
"""
def ll_ddi_scn_bv_12_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, upperTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20, \
                                             AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.ALL_CHANNELS, ScanningFilterPolicy.FILTER_ACCEPT_LIST);
    """
        Place Public address of lowerTester in the Filter Accept List
    """
    peerAddress = publicIdentityAddress(lowerTester);
    success = addAddressesToFilterAcceptList(transport, upperTester, [ peerAddress ], trace);

    for i, channel in enumerate([ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ]):
        if   i == 0:
            advertiser.ownAddress = publicIdentityAddress(lowerTester);
        elif i == 1:
            advertiser.ownAddress = Address( ExtendedAddressType.PUBLIC, address_scramble_LAP(0x456789ABCDEF) );
        elif i == 2:
            advertiser.ownAddress = Address( ExtendedAddressType.RANDOM, address_scramble_OUI(0x456789ABCDEF) | 0xC00000000000);

        if (advertiser.ownAddress.type == ExtendedAddressType.PUBLIC):
            success = success and preamble_set_public_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);
        else:
            success = success and preamble_set_random_address(transport, lowerTester, toNumber(advertiser.ownAddress.address), trace);

        advertiser.channels = channel;
        advertiser.advertiseData = [ i + 1 ];

        success = advertiser.enable() and success;
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = advertiser.disable() and success;
        if i == 0:
            success = success and scanner.qualifyReports( 20, advertiser.ownAddress, advertiser.advertiseData );
        else:
            success = success and scanner.qualifyReports( 0 );

    return success;

"""
    LL/DDI/SCN/BV-13-C [Passive Scanning for Non-Connectable Advertising Packets using Network Privacy]

    Last modified: 31-07-2019
    Reviewed and verified: 31-07-2019 Henrik Eriksen
"""
def ll_ddi_scn_bv_13_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivatePassiveScanning(transport, upperTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 20);
    advertiser.channels = AdvertiseChannel.CHANNEL_37;
    """
        Add Public address of lowerTester to upperTesters Resolving List
        Add Public address of upperTester to lowerTesters Resolving List (to allow the Controller to generate a Private Resolvable Address)
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    """
        Set Resolvable Private Address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    """
        Verify that the Advertise address of the lowerTester has been correctly resolved
    """
    success = success and scanner.qualifyReports( 20, Address( ExtendedAddressType.RESOLVABLE_OR_PUBLIC, 0x456789ABCDEF ) );

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-14-C [Passive Scanning for Connectable Directed Advertising Packets using Network Privacy]

    Last modified: 01-08-2019
    Reviewed and verified: 01-08-2019 Henrik Eriksen
"""
def ll_ddi_scn_bv_14_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivatePassiveScanning(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED, 20, \
                                                    ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                    AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.CHANNEL_37, ScanningFilterPolicy.FILTER_ID_DIRECTED);
    """
        Add Public address of lowerTester to upperTesters Resolving List
        Add Public address of upperTester to lowerTesters Resolving List (to allow the Controller to generate a Private Resolvable Address)
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester) ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = success and scanner.qualifyDirectedReports( 20 );

    ok, address = readLocalResolvableAddress(transport, upperTester, publicIdentityAddress(upperTester), trace);
    trace.trace(8, "Address used by Scanner: %s" % str(Address( ExtendedAddressType.RESOLVABLE_OR_PUBLIC, address )));

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-15-C [Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with no Local or Peer IRK]

    Last modified: 01-08-2019
    Reviewed and verified: 01-08-2019 Henrik Eriksen
"""
def ll_ddi_scn_bv_15_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, upperTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20, 1, \
                                                   ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_RANDOM);

    adData = ADData();
    advertiser.channels = AdvertiseChannel.CHANNEL_37;
    advertiser.advertiseData = [ 0x00 ];
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IX' );
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;
    success = RPAs[upperTester].add( Address( SimpleAddressType.RANDOM, tests.test_utils.lowerRandomAddress ) ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = success and scanner.qualifyReports( 5, None, advertiser.advertiseData );
    success = success and scanner.qualifyResponses( 5, advertiser.responseData );

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-16-C [Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with Local and no Peer IRK]

    Last modified: 01-08-2019
    Reviewed and verified: 01-08-2019 Henrik Eriksen (Peer address type not set to 0x01 in entry added to RPA List)
"""
def ll_ddi_scn_bv_16_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, upperTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20, 1);

    adData = ADData();
    advertiser.channels = AdvertiseChannel.CHANNEL_37;
    advertiser.advertiseData = [ 0x00 ];
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IX' );

    lowerIdentityAddress = publicIdentityAddress(lowerTester);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace, upperIRK );
    success = RPA.clear();
    success = RPA.add( lowerIdentityAddress ) and success;
    """
        Set resolvable private address timeout in seconds ( two seconds )
    """
    success = RPA.timeout( 2 ) and success;
    success = RPA.enable() and success;

    success = advertiser.enable() and success;

    resolvableAddresses = [ 0, 0 ];

    for i in range(2):
        if i == 1:
            """
                Wait for Resolvable Private Address timeout to expire...
            """
            transport.wait(2000);
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = success and scanner.qualifyReports( 5, lowerIdentityAddress, advertiser.advertiseData );
        success = success and scanner.qualifyResponses( 5, advertiser.responseData );

        addressRead, resolvableAddresses[i] = readLocalResolvableAddress(transport, upperTester, lowerIdentityAddress, trace);
        trace.trace(6, "Local Resolvable Address: %s" % formatAddress(resolvableAddresses[i]));

    success = advertiser.disable() and success;
    success = success and toNumber(resolvableAddresses[0]) != toNumber(resolvableAddresses[1]);
    success = RPA.disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-17-C [Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with no Local and a Peer IRK]

    Last modified: 01-08-2019
    Reviewed and verified: 01-08-2019 Henrik Eriksen
"""
def ll_ddi_scn_bv_17_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, upperTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20, 1, \
                                                   ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_RANDOM);
    adData = ADData();
    advertiser.channels = AdvertiseChannel.CHANNEL_37;
    advertiser.advertiseData = [ 0x00 ];
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IX' );
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = success and scanner.qualifyReports( 5, None, advertiser.advertiseData );
    success = success and scanner.qualifyResponses( 5, advertiser.responseData );

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-18-C [Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with both Local and Peer IRKs]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_ddi_scn_bv_18_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, upperTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20);

    adData = ADData();
    advertiser.channels = AdvertiseChannel.CHANNEL_37;
    advertiser.advertiseData = [ 0x00 ];
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IX' );
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[upperTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = success and scanner.qualifyReports( 5, None, advertiser.advertiseData );
    success = success and scanner.qualifyResponses( 5, advertiser.responseData );

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-26-C [Passive Scanning for Non-Connectable Advertising Packets using Network Privacy]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_ddi_scn_bv_26_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivatePassiveScanning(transport, upperTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 20);
    advertiser.channels = AdvertiseChannel.CHANNEL_37;
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace );
    success = RPA.clear();
    success = RPA.add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPA.timeout( 60 ) and success;
    success = RPA.enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = success and scanner.qualifyReports( 0 );

    success = RPA.disable() and success;

    return success;

"""
    LL/DDI/SCN/BV-28-C [Passive Scanning for Non-Connectable Advertising Packets using Device Privacy]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_ddi_scn_bv_28_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivatePassiveScanning(transport, upperTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 20);
    advertiser.channels = AdvertiseChannel.CHANNEL_37;

    """
        Add Public address of lowerTester to the Resolving List
    """
    lowerIdentityAddress = publicIdentityAddress(lowerTester);
    RPA = ResolvableAddresses( transport, upperTester, trace );
    success = RPA.clear();
    success = RPA.add( lowerIdentityAddress, lowerIRK ) and success;
    """
        Set Device Privacy Mode
    """
    success = setPrivacyMode(transport, upperTester, lowerIdentityAddress, PrivacyMode.DEVICE_PRIVACY, trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPA.timeout( 60 ) and success;
    success = RPA.enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = success and scanner.qualifyReports( 20, lowerIdentityAddress );

    success = RPA.disable() and success;

    return success;

"""
    LL/CON/ADV/BV-01-C [Accepting Connection Request]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_con_adv_bv_01_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 5);
    advertiser.channels = AdvertiseChannel.CHANNEL_37;

    initiatorAddress = Address( ExtendedAddressType.PUBLIC );
    initiator = Initiator(transport, lowerTester, upperTester, trace, initiatorAddress, publicIdentityAddress(upperTester));

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;
    """
        If a connection was established Advertising should have seized...
    """
    if connected:
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = success and not scanner.qualifyReports( 1 );

        transport.wait(24820);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/ADV/BV-04-C [Accepting Connection Request after Directed Advertising]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_con_adv_bv_04_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPassiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED);

    initiatorAddress = Address( ExtendedAddressType.PUBLIC );
    initiator = Initiator(transport, lowerTester, upperTester, trace, initiatorAddress, Address( ExtendedAddressType.PUBLIC, address_scramble_OUI(0x123456789ABC) ));
    """
        Verify that connection is not established to wrong Init Address
    """
    success = advertiser.enable();
    connected = initiator.connect();
    success = success and not connected;
    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        """
            Need to stop connection attempt - otherwise following commands will fail with not allowed...
        """
        success = advertiser.disable() and success;
        success = initiator.cancelConnect() and success;
    """
        Verify that the upper Tester continues to Advertise for 1280 ms.
    """
    transport.wait(200);
    success = scanner.enable() and success;
    success = advertiser.enable() and success;
    scanner.monitor(True);
    success = advertiser.timeout() and success;
    success = scanner.disable() and success;
    success = success and not scanner.qualifyReportTime( 100, 1200 );

    initiator = Initiator(transport, lowerTester, upperTester, trace, initiatorAddress, publicIdentityAddress(upperTester));
    """
        Verify that connection is established to correct Init Address
    """
    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(2660);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

# Implements LL/CON/ADV/BV-05-C and LL/CON/ADV/BV-12-C test cases (only difference is the PHY)
# Note: These tests requires a low level device to be available
def do_ll_con_adv_bv_05_12_c(transport, upperTester, lowerTester, trace, packets, phy):

    advEventProperties = [
        0x0001, # Connectable advertising
        0x0001, # Connectable advertising
        0x0005, # Connectable and directed advertising
        0x0005, # Connectable and directed advertising
    ]

    connectReqAdvA = [
        0x123456789ABC, # IUT
        0xCBA987654321, # not IUT
        0x123456789ABC, # IUT
        0xCBA987654321, # not IUT
    ]

    success = True

    for round in range(len(advEventProperties)):

        advInterval = 0x20 # 32 x 0.625 ms = 20.00 ms
        Handle          = 0
        Properties      = advEventProperties[round]
        PrimMinInterval = toArray(advInterval, 3)
        PrimMaxInterval = toArray(advInterval, 3)
        PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
        OwnAddrType     = SimpleAddressType.PUBLIC
        PeerAddrType    = SimpleAddressType.PUBLIC
        PeerAddress     = toArray(0x456789ABCDEF, 6)
        FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
        TxPower         = 0
        PrimAdvPhy      = PhysicalChannel.LE_1M
        SecAdvMaxSkip   = 0     # AUX_ADV_IND shall be sent prior to the next advertising event
        SecAdvPhy       = phy
        Sid             = 0
        ScanReqNotifyEnable = 0; # Scan request notifications disabled

        success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                          PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                          PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
    
        if not success:
            return False

        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0], [0], trace)

        if not success:
            return False

        # Enable LE Channel Selection Algorithm in event mask (it is disabled by default for some reason)
        events = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00]
        status = le_set_event_mask(transport, upperTester, events, 100)
        if status != 0:
            return False
        get_event(transport, upperTester, 200) # Read out command complete event for le_set_event_mask

        advA = publicIdentityAddress(upperTester)

        if not success:
            return False

        def checkAdvPackets():
            success = True

            # Test: The Lower Tester receives an ADV_EXT_IND packet from the IUT with AdvMode set to 01b with only the AuxPtr Extended Header field (and ADI);
            #       The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
            for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
                success = success and packet.payload['AdvMode'] == 0b01
                success = success and 'AdvA' not in packet.payload
                success = success and 'TargetA' not in packet.payload
                success = success and 'CTEInfo' not in packet.payload
                success = success and 'ADI' in packet.payload
                success = success and 'AuxPtr' in packet.payload
                success = success and 'SyncInfo' not in packet.payload
                success = success and 'TxPower' not in packet.payload
                success = success and 'ACAD' not in packet.payload
                success = success and 'AD' not in packet.payload
                success = success and packet.phy == '1M'

                offset = packet.payload['AuxPtr'].auxOffset * (30 if packet.payload['AuxPtr'].offsetUnits == 0 else 300)
                # Packet air length is: pre-amble + AA + header + payload + CRC
                packetLength = 1 + 4 + 2 + len(packet) + 3
                packetAirtime = 8*packetLength
                success = success and offset >= 300 + packetAirtime

            # Test AUX_ADV_IND:
            # - AdvMode field set to 01b
            # - The AdvA field shall contain the IUT’s Advertising Address
            # - If this round used directed advertising, AUX_ADV_IND PDU shall also include the TargetA Extended Header field set to the Lower Tester’s Public Device Address
            # - The IUT sends and receives data on the expected PHY selected in step 1
            for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
                success = success and packet.payload['AdvMode'] == 0b01
                success = success and packet.header.TxAdd == 0x00 # AdvA is public
                success = success and Address(SimpleAddressType.PUBLIC, packet.payload['AdvA']) == advA
                if advEventProperties[round] & 0x04:
                    success = success and 'TargetA' in packet.payload and packet.payload['TargetA'] == 0x456789ABCDEF
                else:
                    success = success and 'TargetA' not in packet.payload
                success = success and packet.phy == ('2M' if phy == PhysicalChannel.LE_2M else '1M')

            return success

        if connectReqAdvA[round] == 0x123456789ABC:
            # Round is using AUX_CONNECT_REQ with a matching AdvA

            initiator = Initiator(transport, lowerTester, None, trace, Address(ExtendedAddressType.PUBLIC), advA, InitiatorFilterPolicy.FILTER_NONE,
                              True, 0x01 if phy == PhysicalChannel.LE_1M else 0x03)
            initiator.checkPrematureDisconnect = False
            success = initiator.connect()

            if not success:
                return False

            success = success and checkAdvPackets()

            if not success:
                return False

            # Test AUX_CONNECT_RSP: The IUT responds to the AUX_CONNECT_REQ within the 2 μs range around T_IFS
            auxConnectReqEndTS = 0
            for packet in packets.fetch(packet_filter=('AUX_CONNECT_RSP', 'AUX_CONNECT_REQ')):
                if packet.type == PacketType.AUX_CONNECT_REQ:
                    # Packet air length is: pre-amble + AA + header + payload + CRC
                    packetLength = (2 if packet.phy == '2M' else 1) + 4 + 2 + len(packet) + 3
                    auxConnectReqEndTS = packet.ts + 8*packetLength/(2 if packet.phy == '2M' else 1)
                else:
                    success = success and packet.header.TxAdd == 0x00 # AdvA is public
                    success = success and Address(SimpleAddressType.PUBLIC, packet.payload['AdvA']) == advA
                    airTimeDiff = packet.ts - auxConnectReqEndTS
                    success = success and airTimeDiff >= 150 - 2 and airTimeDiff <= 150 + 2

            # Only 20 tries with AUX_CONNECT_REQ allowed, check that we didn't exceed that
            connectReqCount = 0
            for packet in packets.fetch(packet_filter=('AUX_CONNECT_REQ')):
                connectReqCount += 1
            success = success and connectReqCount <= 20

            # Expect to receive an HCI_LE_Enhanced_Connection_Complete event followed by a HCI_LE_Channel_Selection_Algorithm and a HCI_LE_Advertising_Set_Terminated event
            connectionHandle = None
            eventsWaiting = has_event(transport, upperTester, 200)[1]
            success = success and eventsWaiting >= 2

            if not success:
                return False

            # Consume all outstanding HCI events
            for i in range(eventsWaiting):
                event = get_event(transport, upperTester, 200)
                if i == 0 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_ENH_CONN_COMPLETE:
                    status, handle, role, peerAddress, localResolvableAddress, peerResolvableAddress, interval, latency, timeout, accuracy = event.decode()
                    success = success and status == 0x00
                    success = success and role == 0x01 # Peripheral
                    connectionHandle = handle
                elif i == 1 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_CHAN_SEL_ALGO:
                    handle, algorithm = event.decode()
                    success = success and algorithm == 0x01 # LE Channel Selection Algorithm #2
                elif i == 2 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_ADV_SET_TERMINATED:
                    # The Upper Tester receives an HCI_LE_Advertising_Set_Terminated event from the IUT with the 
                    # Status set to 0x00, the Advertising_Handle from step 1, and the Connection_Handle from step 8.
                    status, advertiseHandle, advConnectionHandle, completedEvents = event.decode()
                    success = success and status == 0x00
                    success = success and advertiseHandle == Handle
                    success = success and advConnectionHandle == connectionHandle
                elif i < 3:
                    # Unexpected/wrong event received
                    success = False

            # Keep connection open until we have (at least) 100 LL data PDUs
            while True:
                LLDataCount = 0
                for packet in packets.fetch('LL_DATA_PDU'):
                    if packet.idx == upperTester:
                        LLDataCount += 1

                if LLDataCount >= 100:
                    break

                # Wait for the expected time to get the remaining packets based on the connection interval
                transport.wait(math.ceil((100 - LLDataCount)*(initiator.intervalMax*1.25)))

            # Test: The Lower Tester receives no further ADV_EXT_IND PDUs after the advertising interval from the IUT
            cutOffTime = packets.find('AUX_CONNECT_RSP').ts + (advInterval * 0.625) * 1000
            for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
                success = success and packet.ts <= cutOffTime

            # Test: The Lower Tester receives a correctly formatted LL Data Channel PDU using the acknowledgement scheme, from the IUT on the same data channel and on the expected PHY
            lowerTesterLLData = None
            for packet in packets.fetch(packet_filter=('LL_DATA_PDU')):
                if packet.direction == 'Tx' and packet.idx == lowerTester:
                    lowerTesterLLData = packet
                elif packet.direction == 'Tx' and packet.idx == upperTester:
                    # LLID should be either 1 (for an empty packet) or 2 (for a non-empty packet)
                    if packet.header.Length > 0:
                        success = success and packet.header.LLID == 0b10
                    else:
                        success = success and packet.header.LLID == 0b01
                    # Channel should match the lowerTesters packet
                    success = success and packet.channel_num == lowerTesterLLData.channel_num
                    # SN and NESN should be correct (note: we should never see re-transmissions in this case, since there is no other traffic to collide with)
                    success = success and packet.header.SN == lowerTesterLLData.header.SN and packet.header.NESN == (1 if lowerTesterLLData.header.SN == 0 else 0)
                    success = success and packet.phy == ('2M' if phy == PhysicalChannel.LE_2M else '1M')

            # Disconnect with "Remote User Terminated Connection"
            status = disconnect(transport, upperTester, connectionHandle, 0x13, 200)
            success = success and (status == 0)

            if not success:
                return False

            # Make sure the disconnect gets handled before starting next round
            transport.wait(500)

        else:
            # Round is using AUX_CONNECT_REQ with a non-matching AdvA - cannot use standard HCI commands for this, use low_level_device instead
            auxAdvIndPacket = wait_for_AUX_ADV_IND_end(transport, packets)

            # Transmit a AUX_CONNECT_REQ
            packetData = (0b0101 + (34 << 8)).to_bytes(2, 'little', signed=False) # header - PDU Type 0b0101, ChSel, TxAdd and RxAdd all 0, length 34
            packetData = b''.join([packetData, 0x456789ABCDEF.to_bytes(6, 'little', signed=False)]) # InitA
            packetData = b''.join([packetData, connectReqAdvA[round].to_bytes(6, 'little', signed=False)]) # AdvA
            packetData = b''.join([packetData, 0xEF11D7A8.to_bytes(4, 'little', signed=False)]) # LLData: AA
            packetData = b''.join([packetData, 0xE4C5A3.to_bytes(3, 'little', signed=False)]) # LLData: CRCInit
            packetData = b''.join([packetData, 0x01.to_bytes(1, 'little', signed=False)]) # LLData: WinSize
            packetData = b''.join([packetData, 0x0000.to_bytes(2, 'little', signed=False)]) # LLData: WinOffset
            packetData = b''.join([packetData, 0x0018.to_bytes(2, 'little', signed=False)]) # LLData: Interval
            packetData = b''.join([packetData, 0x0000.to_bytes(2, 'little', signed=False)]) # LLData: Latency
            packetData = b''.join([packetData, 0x0019.to_bytes(2, 'little', signed=False)]) # LLData: Timeout
            packetData = b''.join([packetData, 0x1FFFFFFFFF.to_bytes(5, 'little', signed=False)]) # LLData: ChM
            packetData = b''.join([packetData, (0x07 + (0x05 << 5)).to_bytes(1, 'little', signed=False)]) # LLData: Hop and SCA (Hop: 7 and SCA: 5)
            CRC = calcBLECRC(0x555555, packetData)
            packetData = b''.join([packetData, CRC.to_bytes(3, 'little', signed=False)])

            # Calculate transmit timestamp (T_IFS from end of AUX_ADV_IND)
            transmitTime = auxAdvIndPacket.ts + get_packet_air_time(auxAdvIndPacket) + 150

            transport.low_level_device.tx(channel_num_to_index(auxAdvIndPacket.channel_num), auxAdvIndPacket.phy, auxAdvIndPacket.aa, transmitTime, packetData)

            # Wait and check that no AUX_CONNECT_RSP is sent in response
            transport.wait(10)

            success = success and not packets.find('AUX_CONNECT_RSP')

            success = success and checkAdvPackets()

            # Stop advertising
            success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.DISABLE, [Handle], [0], [0], trace)

            if not success:
                return False

        # Flush any outstanding HCI events
        get_event(transport, upperTester, 200, True)
        get_event(transport, lowerTester, 200, True)

        # Flush packets
        packets.flush()

    return success

"""
    LL/CON/ADV/BV-05-C [Extended Advertising, Accepting Connections; LE 1M PHY]
"""
def ll_con_adv_bv_05_c(transport, upperTester, lowerTester, trace, packets):
    return do_ll_con_adv_bv_05_12_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_1M)

"""
    LL/CON/ADV/BV-06-C [Extended Advertising, Legacy PDUs, Accepting Connections]
"""
def ll_con_adv_bv_06_c(transport, upperTester, lowerTester, trace, packets):

    advInterval = 0x20 # 32 x 0.625 ms = 20.00 ms
    Handle          = 0
    Properties      = 0b00010011 # ADV_IND legacy PDU
    PrimMinInterval = toArray(advInterval, 3)
    PrimMaxInterval = toArray(advInterval, 3)
    PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
    OwnAddrType     = SimpleAddressType.PUBLIC
    PeerAddrType    = SimpleAddressType.PUBLIC
    PeerAddress     = toArray(0x456789ABCDEF, 6)
    FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
    TxPower         = 0
    PrimAdvPhy      = PhysicalChannel.LE_1M
    SecAdvMaxSkip   = 0
    SecAdvPhy       = 0
    Sid             = 0
    ScanReqNotifyEnable = 0

    success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)

    if not success:
        return False

    success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0], [0], trace)
    if not success:
        return False

    # Enable LE Channel Selection Algorithm in event mask (it is disabled by default for some reason)
    events = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00]
    status = le_set_event_mask(transport, upperTester, events, 100)
    if status != 0:
        return False
    get_event(transport, upperTester, 200) # Read out command complete event for le_set_event_mask

    initiator = Initiator(transport, lowerTester, None, trace, Address(ExtendedAddressType.PUBLIC), publicIdentityAddress(upperTester),
                          InitiatorFilterPolicy.FILTER_NONE, True, 0x01)
    initiator.checkPrematureDisconnect = False
    success = initiator.connect()

    if not success:
        return False

    # Check ADV_INDs
    # ChSel set to 1 (Channel Selection Algorithm #2). The AdvA field shall contain the IUT’s Advertising Address
    # On the primary advertising channel(s) using the LE 1M PHY
    for packet in packets.fetch(packet_filter=('ADV_IND')):
        success = success and packet.header.ChSel == 1
        success = success and int.from_bytes(packet.payload.AdvA, 'little', signed=False) == 0x123456789ABC
        success = success and (packet.channel_num == 0 or packet.channel_num == 12 or packet.channel_num == 39)
        success = success and packet.phy == '1M'

    if not success:
        return False

    # Only 20 tries with CONNECT_IND allowed, check that we didn't exceed that
    connectIndCount = 0
    for packet in packets.fetch(packet_filter=('ADV_IND')):
        connectIndCount += 1
    success = success and connectIndCount <= 20

    # Expect to receive an HCI_LE_Enhanced_Connection_Complete event followed by a HCI_LE_Channel_Selection_Algorithm and a HCI_LE_Advertising_Set_Terminated event
    connectionHandle = None
    eventsWaiting = has_event(transport, upperTester, 200)[1]
    success = success and eventsWaiting >= 2

    if not success:
        return False

    # Consume all outstanding HCI events
    for i in range(eventsWaiting):
        event = get_event(transport, upperTester, 200)
        if i == 0 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_ENH_CONN_COMPLETE:
            status, handle, role, peerAddress, localResolvableAddress, peerResolvableAddress, interval, latency, timeout, accuracy = event.decode()
            success = success and status == 0x00
            success = success and role == 0x01 # Peripheral
            connectionHandle = handle
        elif i == 1 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_CHAN_SEL_ALGO:
            handle, algorithm = event.decode()
            success = success and algorithm == 0x01 # LE Channel Selection Algorithm #2
        elif i == 2 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_ADV_SET_TERMINATED:
            # Status set to 0x00, the Advertising_Handle used, and the Connection_Handle received in HCI_LE_Enhanced_Connection_Complete
            status, advertiseHandle, advConnectionHandle, completedEvents = event.decode()
            success = success and status == 0x00
            success = success and advertiseHandle == Handle
            success = success and advConnectionHandle == connectionHandle
        elif i < 3:
            # Unexpected/wrong event received
            success = False

    # Keep connection open until we have (at least) 100 LL data PDUs from Lower Tester
    while True:
        LLDataCount = 0
        for packet in packets.fetch('LL_DATA_PDU'):
            if packet.idx == lowerTester:
                LLDataCount += 1
        if LLDataCount >= 100:
            break

        # Wait for the expected time to get the remaining packets based on the connection interval
        transport.wait(math.ceil((100 - LLDataCount)*(initiator.intervalMax*1.25)))

    # Check LL_DATA_PDUs
    # The IUT sends and receives data using the LE 1M PHY
    for packet in packets.fetch(packet_filter=('LL_DATA_PDU')):
        success = success and packet.phy == '1M'

    # The Lower Tester receives no further advertising packets while maintaining the connection
    firstLLDataPDUTs = packets.find('LL_DATA_PDU').ts
    lastAdvIndTs = packets.findLast('ADV_IND').ts
    success = success and lastAdvIndTs <= firstLLDataPDUTs

    # Disconnect with "Remote User Terminated Connection"
    status = disconnect(transport, upperTester, connectionHandle, 0x13, 200)
    success = success and (status == 0)

    # Wait for disconnect to fully complete
    transport.wait(500)

    # Flush system before the next test steps
    packets.flush()
    flush_events(transport, lowerTester, 100)
    flush_events(transport, upperTester, 100)

    # Set advertising parameters and start advertising again
    success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)

    if not success:
        return False

    success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0], [0], trace)
    if not success:
        return False

    # Send CONNECT_INDs with the AdvA field set to an address other than the IUT's address 5 times; Advertising should continue
    for i in range(5):
        advIndPacket = wait_for_ADV_IND_end(transport, packets, math.ceil(advInterval*0.625+10))
        if not advIndPacket:
            return False

        # Transmit a CONNECT_IND
        packetData = (0b00100101 + (34 << 8)).to_bytes(2, 'little', signed=False) # header - PDU Type 0b0101, ChSel 1, TxAdd and RxAdd 0, length 34
        packetData = b''.join([packetData, 0x456789ABCDEF.to_bytes(6, 'little', signed=False)]) # InitA
        packetData = b''.join([packetData, 0xCBA987654321.to_bytes(6, 'little', signed=False)]) # AdvA (not matching IUT)
        packetData = b''.join([packetData, 0xEF11D7A8.to_bytes(4, 'little', signed=False)]) # LLData: AA
        packetData = b''.join([packetData, 0xE4C5A3.to_bytes(3, 'little', signed=False)]) # LLData: CRCInit
        packetData = b''.join([packetData, 0x01.to_bytes(1, 'little', signed=False)]) # LLData: WinSize
        packetData = b''.join([packetData, 0x0000.to_bytes(2, 'little', signed=False)]) # LLData: WinOffset
        packetData = b''.join([packetData, 0x0018.to_bytes(2, 'little', signed=False)]) # LLData: Interval
        packetData = b''.join([packetData, 0x0000.to_bytes(2, 'little', signed=False)]) # LLData: Latency
        packetData = b''.join([packetData, 0x0019.to_bytes(2, 'little', signed=False)]) # LLData: Timeout
        packetData = b''.join([packetData, 0x1FFFFFFFFF.to_bytes(5, 'little', signed=False)]) # LLData: ChM
        packetData = b''.join([packetData, (0x07 + (0x05 << 5)).to_bytes(1, 'little', signed=False)]) # LLData: Hop and SCA (Hop: 7 and SCA: 5)
        CRC = calcBLECRC(0x555555, packetData)
        packetData = b''.join([packetData, CRC.to_bytes(3, 'little', signed=False)])

        # Calculate transmit timestamp (T_IFS from end of ADV_IND)
        transmitTime = advIndPacket.ts + get_packet_air_time(advIndPacket) + 150

        transport.low_level_device.tx(channel_num_to_index(advIndPacket.channel_num), advIndPacket.phy, advIndPacket.aa, transmitTime, packetData)

        # Wait for transmit to happen
        transport.wait(10)

    # Wait an advertising interval and ensure that advertising is still active
    transport.wait(math.ceil(advInterval*0.625+10))

    success = success and packets.findLast(packet_filter=('ADV_IND')).ts > transmitTime

    return success

"""
    LL/CON/ADV/BV-09-C [Accepting Connection Request using Channel Selection Algorithm #2]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_con_adv_bv_09_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
       Enable the LE Channel Selection Algorithm Event
    """
    events = [0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00];

    success = setLEEventMask(transport, upperTester, events, trace);

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Check for LE Channel Selection Algorithm Event in upper Tester...
        """
        success, handle, chSelAlgorithm = hasChannelSelectionAlgorithmEvent(transport, upperTester, trace);
        success = success and (chSelAlgorithm == 1);

        transport.wait(2600);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/ADV/BV-10-C [Accepting Connection Request after Directed Advertising using Channel Selection Algorithm #2]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_con_adv_bv_10_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);
    """
        Enable the LE Channel Selection Algorithm Event
    """
    events = [0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00];

    success = setLEEventMask(transport, upperTester, events, trace);

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Check for LE Channel Selection Algorithm Event in upper Tester...
        """
        success, handle, chSelAlgorithm = hasChannelSelectionAlgorithmEvent(transport, upperTester, trace);
        success = success and (chSelAlgorithm == 1);

        transport.wait(2600);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/ADV/BV-12-C [Extended Advertising, Accepting Connections; LE 2M PHY]
"""
def ll_con_adv_bv_12_c(transport, upperTester, lowerTester, trace, packets):
    return do_ll_con_adv_bv_05_12_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_2M)

# Implements LL/CON/ADV/BV-14-C and LL/CON/ADV/BV-15-C test cases (only difference is the PHY)
def do_ll_con_adv_bv_14_15_c(transport, upperTester, lowerTester, trace, packets, phy):
    initAddresses = [
        0xDEAD42E98020, # random static address
        0x2EAD2C3510BF, # random, non-resolvable private address
        0x7FD2613EF26C  # random, resolvable private address
    ]
    
    success = True

    for round in range(len(initAddresses)):
        lowerRandomA = initAddresses[round]

        Handle          = 0
        Properties      = 0x01 # Connectable advertising
        PrimMinInterval = toArray(0x0020, 3) # Minimum Advertise Interval = 32 x 0.625 ms = 20.00 ms
        PrimMaxInterval = toArray(0x0020, 3) # Maximum Advertise Interval = 32 x 0.625 ms = 20.00 ms
        PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
        OwnAddrType     = SimpleAddressType.PUBLIC
        PeerAddrType    = 0
        PeerAddress     = toArray(0, 6)
        FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
        TxPower         = 0
        PrimAdvPhy      = PhysicalChannel.LE_CODED if phy == PhysicalChannel.LE_CODED else PhysicalChannel.LE_1M # Primary advertisement PHY is LE 1M except for coded PHY
        SecAdvMaxSkip   = 0     # AUX_ADV_IND shall be sent prior to the next advertising event
        SecAdvPhy       = phy
        Sid             = 0
        ScanReqNotifyEnable = 0; # Scan request notifications disabled

        success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                          PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                          PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
    
        if not success:
            return False

        success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0], [0], trace)

        if not success:
            return False

        # Enable LE Channel Selection Algorithm in event mask (it is disabled by default for some reason)
        events = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00]
        status = le_set_event_mask(transport, upperTester, events, 100)
        if status != 0:
            return False
        get_event(transport, upperTester, 200) # Read out command complete event for le_set_event_mask

        advA = publicIdentityAddress(upperTester)
    
        # Set initiator/lower tester random address
        success = preamble_set_random_address(transport, lowerTester, lowerRandomA, trace)

        if not success:
            return False

        initiator = Initiator(transport, lowerTester, None, trace, Address(ExtendedAddressType.RANDOM), advA, InitiatorFilterPolicy.FILTER_NONE,
                              True, 0x01 if phy == PhysicalChannel.LE_1M else 0x03)
        initiator.checkPrematureDisconnect = False
        success = initiator.connect()

        if not success:
            return False
    
        # Test: The Lower Tester receives an ADV_EXT_IND packet from the IUT with AdvMode set to 01b with only the AuxPtr Extended Header field (and ADI);
        #       The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
        for packet in packets.fetch(packet_filter=('ADV_EXT_IND')):
            success = success and packet.payload['AdvMode'] == 0b01
            success = success and 'AdvA' not in packet.payload
            success = success and 'TargetA' not in packet.payload
            success = success and 'CTEInfo' not in packet.payload
            success = success and 'ADI' in packet.payload
            success = success and 'AuxPtr' in packet.payload
            success = success and 'SyncInfo' not in packet.payload
            success = success and 'TxPower' not in packet.payload
            success = success and 'ACAD' not in packet.payload
            success = success and 'AD' not in packet.payload
            success = success and packet.phy == '1M'

            offset = packet.payload['AuxPtr'].auxOffset * (30 if packet.payload['AuxPtr'].offsetUnits == 0 else 300)
            # Packet air length is: pre-amble + AA + header + payload + CRC
            packetLength = 1 + 4 + 2 + len(packet) + 3
            packetAirtime = 8*packetLength
            success = success and offset >= 300 + packetAirtime

        # Test AUX_ADV_IND: AdvMode field set to 01b; The AdvA field shall contain the IUT’s Advertising Address; The IUT sends and receives data on the expected PHY selected in step 1
        for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
            success = success and packet.payload['AdvMode'] == 0b01
            success = success and packet.header.TxAdd == 0x00 # AdvA is public
            success = success and Address(SimpleAddressType.PUBLIC, packet.payload['AdvA']) == advA
            success = success and packet.phy == ('2M' if phy == PhysicalChannel.LE_2M else '1M')

        # Test AUX_CONNECT_RSP: RxAdd = 1 and valid TargetA address; The IUT responds to the AUX_CONNECT_REQ within the 2 μs range around T_IFS
        auxConnectReqEndTS = 0
        for packet in packets.fetch(packet_filter=('AUX_CONNECT_RSP', 'AUX_CONNECT_REQ')):
            if packet.type == PacketType.AUX_CONNECT_REQ:
                # Packet air length is: pre-amble + AA + header + payload + CRC
                packetLength = (2 if packet.phy == '2M' else 1) + 4 + 2 + len(packet) + 3
                auxConnectReqEndTS = packet.ts + 8*packetLength/(2 if packet.phy == '2M' else 1)
            else:
                success = success and packet.header.RxAdd == 0x01 # TargetA is random
                success = success and packet.payload['TargetA'] == lowerRandomA
                success = success and packet.header.TxAdd == 0x00 # AdvA is public
                success = success and Address(SimpleAddressType.PUBLIC, packet.payload['AdvA']) == advA
                airTimeDiff = packet.ts - auxConnectReqEndTS
                success = success and airTimeDiff >= 150 - 2 and airTimeDiff <= 150 + 2
                


        # Only 20 tries with AUX_CONNECT_REQ allowed, check that we didn't exceed that
        connectReqCount = 0
        for packet in packets.fetch(packet_filter=('AUX_CONNECT_REQ')):
            connectReqCount += 1
        success = success and connectReqCount <= 20
    
        # Expect to receive an HCI_LE_Enhanced_Connection_Complete event followed by a HCI_LE_Channel_Selection_Algorithm
        # It is also expected to receive an HCI_LE_Advertising_Set_Terminated event, but this is not to be tested
        connectionHandle = None
        eventsWaiting = has_event(transport, upperTester, 200)[1]
        success = success and eventsWaiting >= 2

        if not success:
            return False

        # Consume all outstanding HCI events
        for i in range(eventsWaiting):
            event = get_event(transport, upperTester, 200)
            if i == 0 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_ENH_CONN_COMPLETE:
                # Nothing further to check for this event
                connectionHandle = event.decode()[1]
            elif i == 1 and event.subEvent == MetaEvents.BT_HCI_EVT_LE_CHAN_SEL_ALGO:
                handle, algorithm = event.decode()
                success = success and algorithm == 0x01 # LE Channel Selection Algorithm #2
            elif i < 2:
                # Unexpected/wrong event received
                success = False

        # At least one LL data PDU should have been exchanged between the two devices
        iutLLData = None
        lowerTesterLLData = None
        for packet in packets.fetch(packet_filter=('LL_DATA_PDU')):
            if not lowerTesterLLData and packet.direction == 'Tx' and packet.idx == lowerTester:
                # SN and NESN should be 0
                success = success and packet.header.SN == 0 and packet.header.NESN == 0
                lowerTesterLLData = packet
            elif packet.direction == 'Tx' and packet.idx == upperTester:
                # LLID should be either 1 (for an empty packet) or 2 (for a non-empty packet)
                if packet.header.Length > 0:
                    success = success and packet.header.LLID == 0b10
                else:
                    success = success and packet.header.LLID == 0b01
                # Channel should match the lowerTesters packet
                success = success and packet.channel_num == lowerTesterLLData.channel_num
                # SN should be 0 and NESN 1
                success = success and packet.header.SN == 0 and packet.header.NESN == 1
                iutLLData = packet
            if iutLLData != None and lowerTesterLLData != None:
                break

        success = success and not (iutLLData == None or lowerTesterLLData == None)
        
        # Disconnect with "Remote User Terminated Connection"
        status = disconnect(transport, upperTester, connectionHandle, 0x13, 200)
        success = success and (status == 0)

        # Make sure the disconnect gets handled before starting next round
        transport.wait(500)

        # Flush any outstanding HCI events
        get_event(transport, upperTester, 200, True)
        get_event(transport, lowerTester, 200, True)

        # Flush packets
        packets.flush()

    return success

"""
    LL/CON/ADV/BV-14-C [Extended Advertising, Accepting Connections with Random address]; LE 1M PHY
"""
def ll_con_adv_bv_14_c(transport, upperTester, lowerTester, trace, packets):

    return do_ll_con_adv_bv_14_15_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_1M)

"""
    LL/CON/ADV/BV-15-C [Extended Advertising, Accepting Connections with Random address]; LE 2M PHY
"""
def ll_con_adv_bv_15_c(transport, upperTester, lowerTester, trace, packets):

    return do_ll_con_adv_bv_14_15_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_2M)

"""
    LL/CON/INI/BV-01-C [Connection Initiation rejects Address change]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen (NOTE: Timing for connection events not verified - see Air Trace)
"""
def ll_con_ini_bv_01_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = True;
    for channel in [ AdvertiseChannel.CHANNEL_37, AdvertiseChannel.CHANNEL_38, AdvertiseChannel.CHANNEL_39 ]:

        advertiser.channels = channel;

        for address in [ 0x456789ABCDEF, address_scramble_OUI(0x456789ABCDEF), address_scramble_LAP(0x456789ABCDEF), address_exchange_OUI_LAP(0x456789ABCDEF) ]:

            trace.trace(7, "\nUsing channel %s and Lower Tester address %s\n" % (str(channel), formatAddress(toArray(address, 6))));

            success = preamble_set_public_address(transport, lowerTester, address, trace) and success;
            initiator.peerAddress = Address( ExtendedAddressType.PUBLIC, address );

            success = initiator.preConnect() and success;

            randAddress = [ random.randint(0,255) for _ in range(6) ];
            randAddress[5] |= 0xC0;
            status = le_set_random_address(transport, upperTester, randAddress, 100);
            trace.trace(6, "LE Set Random Address Command returns status: 0x%02X" % status);
            success = success and (status == 0x0C);
            getCommandCompleteEvent(transport, upperTester, trace);

            success = advertiser.enable() and success;
            connected = initiator.postConnect();
            success = success and connected;

            if connected:
                transport.wait(2660);
                success = initiator.disconnect(0x13) and success;
            else:
                success = advertiser.disable() and success;

            if not success:
                break;

    return success;

"""
    LL/CON/INI/BV-02-C [Connecting to Advertiser using Directed Advertising Packets]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen (NOTE: Timing for connection events not verified - see Air Trace)
"""
def ll_con_ini_bv_02_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(2660);
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/INI/BV-06-C [Filtered Connection to Advertiser using Undirected Advertising Packets]

    Last modified: 09-12-2019
    Reviewed and verified: 09-12-2019 Henrik Eriksen
"""
def ll_con_ini_bv_06_c(transport, upperTester, lowerTester, trace):

    success = True;
    for j in range(2):
        if j == 0:
            advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                       AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.CHANNEL_38, InitiatorFilterPolicy.FILTER_ACCEPT_LIST_ONLY);
        else:
            advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                        ExtendedAddressType.RANDOM, ExtendedAddressType.RANDOM, AdvertisingFilterPolicy.FILTER_NONE, \
                                                        AdvertiseChannel.CHANNEL_38, InitiatorFilterPolicy.FILTER_ACCEPT_LIST_ONLY);
        """
            Place Public|Random address of lowerTester in the Filter Accept List
        """
        filterAcceptListAddress = publicIdentityAddress(lowerTester) if j == 0 else randomIdentityAddress(lowerTester);

        success = addAddressesToFilterAcceptList(transport, upperTester, [ filterAcceptListAddress ], trace) and success;
        addresses = [ Address( ExtendedAddressType.RANDOM if filterAcceptListAddress.type == SimpleAddressType.PUBLIC \
                                                          else ExtendedAddressType.PUBLIC, filterAcceptListAddress.address ),
                      Address( ExtendedAddressType.PUBLIC if filterAcceptListAddress.type == SimpleAddressType.PUBLIC \
                                                          else ExtendedAddressType.RANDOM, address_scramble_LAP(toNumber(filterAcceptListAddress.address)) ),
                      filterAcceptListAddress ];

        for i, advertiserAddress in enumerate( addresses ):

            advertiser.ownAddress = advertiserAddress;
            if advertiserAddress.type == ExtendedAddressType.RANDOM:
                success = preamble_set_random_address(transport, lowerTester, toNumber(advertiserAddress.address), trace) and success;
            else:
                success = preamble_set_public_address(transport, lowerTester, toNumber(advertiserAddress.address), trace) and success;

            success = advertiser.enable() and success;
            connected = initiator.connect();
            success = (connected if i == 2 else not connected) and success;

            if connected:
                success = initiator.disconnect(0x13) and success;
            else:
                """
                    Need to stop connection attempt - otherwise following commands will fail with not allowed...
                """
                success = initiator.cancelConnect() and success;
                success = advertiser.disable() and success;

            if not success:
                break

    return success;

"""
    LL/CON/INI/BV-07-C [Filtered Connection to Advertiser using Directed Advertising Packets]

    Last modified: 09-12-2019
    Reviewed and verified: 09-12-2019 Henrik Eriksen
"""
def ll_con_ini_bv_07_c(transport, upperTester, lowerTester, trace):

    success = True;
    for j in range(2):
        if j == 0:
            advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED, \
                                                       AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.CHANNEL_38, InitiatorFilterPolicy.FILTER_ACCEPT_LIST_ONLY);
        else:
            advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED, \
                                                        ExtendedAddressType.RANDOM, ExtendedAddressType.RANDOM, AdvertisingFilterPolicy.FILTER_NONE, \
                                                        AdvertiseChannel.CHANNEL_38, InitiatorFilterPolicy.FILTER_ACCEPT_LIST_ONLY);
        """
            Place Public|Random address of lowerTester in the Filter Accept List
        """
        filterAcceptListAddress = publicIdentityAddress(lowerTester) if j == 0 else randomIdentityAddress(lowerTester);

        success = addAddressesToFilterAcceptList(transport, upperTester, [ filterAcceptListAddress ], trace) and success;

        addresses = [ Address( ExtendedAddressType.RANDOM if filterAcceptListAddress.type == SimpleAddressType.PUBLIC \
                                                          else ExtendedAddressType.PUBLIC, filterAcceptListAddress.address ),
                      Address( ExtendedAddressType.PUBLIC if filterAcceptListAddress.type == SimpleAddressType.PUBLIC \
                                                          else ExtendedAddressType.RANDOM, address_scramble_LAP(toNumber(filterAcceptListAddress.address)) ),
                      filterAcceptListAddress ];

        for i, advertiserAddress in enumerate( addresses ):

            advertiser.ownAddress = advertiserAddress;
            if advertiserAddress.type == ExtendedAddressType.RANDOM:
                success = preamble_set_random_address(transport, lowerTester, toNumber(advertiserAddress.address), trace) and success;
            else:
                success = preamble_set_public_address(transport, lowerTester, toNumber(advertiserAddress.address), trace) and success;

            success = advertiser.enable() and success;
            connected = initiator.connect();
            success = (connected if i == 2 else not connected) and success;

            if connected:
                success = initiator.disconnect(0x13) and success;
            else:
                """
                    Need to stop connection attempt - otherwise following commands will fail with not allowed...
                """
                success = initiator.cancelConnect() and success;
                success = advertiser.disable() and success;

    return success;

"""
    LL/CON/INI/BV-08-C [Connecting to Connectable Undirected Advertiser with Network Privacy]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_con_ini_bv_08_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_NONE, AdvertiseChannel.CHANNEL_38);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace, upperIRK );
    success = RPA.clear();
    success = RPA.add( publicIdentityAddress(lowerTester) ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPA.timeout( 60 ) and success;
    success = RPA.enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = connected and success;

    if connected:
        transport.wait(2660);
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPA.disable() and success;

    return success;

"""
    LL/CON/INI/BV-09-C [Connecting to Connectable Undirected Advertiser with Network Privacy thru Resolving List]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_con_ini_bv_09_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Add Public address of lowerTester to the Resolving List
    """
    randIRK = [ random.randint(0,255) for _ in range(16) ];

    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, randIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = initiator.preConnect() and success;
    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and not connected;
    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[lowerTester].clear() and success;
    RPAs[lowerTester].localIRK = lowerIRK[ : ];
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;

    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and connected;

    if connected:
        transport.wait(2660);
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/CON/INI/BV-10-C [Connecting to Directed Advertiser with Network Privacy thru Resolving List]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen
"""
def ll_con_ini_bv_10_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);
    """
        Add Public address of lowerTester to the Resolving List
    """
    randIRK = [ random.randint(0,255) for _ in range(16) ];

    RPAs = [ ResolvableAddresses( transport, upperTester, trace ), ResolvableAddresses( transport, lowerTester, trace, randIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = initiator.preConnect() and success;
    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and not connected;
    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[lowerTester].clear() and success;
    RPAs[lowerTester].localIRK = lowerIRK[ : ];
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;

    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and connected;

    if connected:
        transport.wait(2660);
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/CON/INI/BV-11-C [Connecting to Directed Advertiser using  wrong address with Network Privacy thru Resolving List ]

    Last modified: 02-08-2019
    Reviewed and verified: 02-08-2019 Henrik Eriksen (NOTE: Cannot confirm that the InitA used in ADV_DIRECT_INT is different from the one used in CONNECT_REQ - see Air trace)
"""
def ll_con_ini_bv_11_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);
    """
        Add Public address of lowerTester to the Resolving List
    """
    randIRK = [ random.randint(0,255) for _ in range(16) ];

    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, randIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( three seconds and sixty seconds )
    """
    success = RPAs[upperTester].timeout( 3 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = initiator.preConnect() and success;
    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and not connected;
    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[lowerTester].clear() and success;
    RPAs[lowerTester].localIRK = lowerIRK[ : ];
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;

    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and connected;

    if connected:
        transport.wait(2660);
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/CON/INI/BV-12-C [Connecting to Directed Advertiser using Identity address with Network Privacy thru Resolving List]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Cannot confirm that the InitA used in ADV_DIRECT_INT is different from the one used in CONNECT_REQ - see Air trace)
"""
def ll_con_ini_bv_12_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;
    """
        Set resolvable private address timeout in seconds ( three seconds and sixty seconds )
    """
    success = RPAs[upperTester].timeout( 3 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = initiator.preConnect() and success;
    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and not connected;
    success = advertiser.disable() and success;

    success = RPAs[lowerTester].clear() and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;

    success = advertiser.enable() and success;
    connected = initiator.postConnect();
    success = success and connected;

    if connected:
        trace.trace(8, "Initiators local RPA: %s" % formatAddress(initiator.localRPA()));

        localRPAs = [ initiator.localRPA()[ : ], [ 0 for _ in range(6) ] ];

        transport.wait(2660);
        success = initiator.disconnect(0x13) and success;

        success = advertiser.enable() and success;
        connected = initiator.connect();
        success = success and connected;

        if connected:
            trace.trace(8, "Initiators local RPA: %s" % formatAddress(initiator.localRPA()));

            localRPAs[1] = initiator.localRPA()[ : ];

            success = initiator.disconnect(0x13) and success;
            """
                Verify that the Initiator Address (RPA) used in the CONNECT_IND has changed due to RPA timeout...
            """
            success = success and localRPAs[0] != localRPAs[1];
        else:
            success = advertiser.disable() and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/CON/INI/BV-16-C [Connecting to Advertiser with Channel Selection Algorithm #2]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing requirements cannot be verified - see Air trace)
"""
def ll_con_ini_bv_16_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
       Enable the LE Channel Selection Algorithm Event
    """
    events = [0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00];
    success = setLEEventMask(transport, upperTester, events, trace);

    success = advertiser.enable() and success;
    initiator.checkPrematureDisconnect = False;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Check for LE Channel Selection Algorithm Event in upper Tester...
        """
        ok, handle, chSelAlgorithm = hasChannelSelectionAlgorithmEvent(transport, upperTester, trace);
        success = success and ok and (chSelAlgorithm == 1);

        transport.wait(2840);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/INI/BV-17-C [Connecting to Directed Advertiser with Channel Selection Algorithm #2]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing requirements cannot be verified - see Air trace)
"""
def ll_con_ini_bv_17_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);
    """
       Enable the LE Channel Selection Algorithm Event
    """
    events = [0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00];
    success = setLEEventMask(transport, upperTester, events, trace);

    success = advertiser.enable() and success;
    initiator.checkPrematureDisconnect = False;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Check for LE Channel Selection Algorithm Event in upper Tester...
        """
        ok, handle, chSelAlgorithm = hasChannelSelectionAlgorithmEvent(transport, upperTester, trace);
        success = success and ok and (chSelAlgorithm == 1);

        transport.wait(2840);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/INI/BV-18-C [Don't connect to Advertiser using Identity address with Network Privacy thru Resolving List]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_ini_bv_18_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, ExtendedAddressType.PUBLIC);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace, upperIRK );
    success = RPA.clear();
    success = RPA.add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPA.timeout( 60 ) and success;
    success = RPA.enable() and success;

    success = success and advertiser.enable();
    connected = initiator.connect();
    success = success and not connected;

    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = initiator.cancelConnect();
        success = advertiser.disable() and success;

    success = RPA.disable() and success;

    return success;

"""
    LL/CON/INI/BV-19-C [Don't connect to Directed Advertiser using Identity address with Network Privacy thru Resolving List]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_ini_bv_19_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace );
    success = RPA.clear();
    success = RPA.add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPA.timeout( 60 ) and success;
    success = RPA.enable() and success;

    success = success and advertiser.enable();
    connected = initiator.connect();
    success = success and not connected;

    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = initiator.cancelConnect();
        success = advertiser.disable() and success;

    success = RPA.disable() and success;

    return success;

"""
    LL/CON/INI/BV-20-C [Connect to Advertiser using Identity address with Device Privacy thru Resolving List]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_ini_bv_20_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;
    """
        Set Privacy Mode
    """
    success = setPrivacyMode(transport, upperTester, publicIdentityAddress(lowerTester), PrivacyMode.DEVICE_PRIVACY, trace) and success;

    success = success and advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(2660);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/CON/INI/BV-21-C [Connect to Directed Advertiser using Identity address with Device Privacy thru Resolving List]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_ini_bv_21_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_LDC_DIRECTED);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace );
    success = RPA.clear();
    success = RPA.add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPA.timeout( 60 ) and success;
    success = RPA.enable() and success;
    """
        Set Privacy Mode
    """
    success = setPrivacyMode(transport, upperTester, publicIdentityAddress(lowerTester), PrivacyMode.DEVICE_PRIVACY, trace) and success;

    success = success and advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(2660);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPA.disable() and success;

    return success;

"""
    LL/CON/INI/BV-23-C [Network Privacy - Connection Establishment using filterallowlist and resolving list with address resolution disabled]

    Last modified: 17-12-2019
    Reviewed and verified: 17-12-2019 Henrik Eriksen
"""
def ll_con_ini_bv_23_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, ExtendedAddressType.PUBLIC,
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, AdvertisingFilterPolicy.FILTER_NONE,
                                                AdvertiseChannel.ALL_CHANNELS, InitiatorFilterPolicy.FILTER_ACCEPT_LIST_ONLY);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester) ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Add two more entries not equal to lower tester with different local IRK for each entry
    """
    extraAddressses = [ Address( SimpleAddressType.PUBLIC, address_scramble_OUI(toNumber(publicIdentityAddress(lowerTester).address)) ),
                        Address( SimpleAddressType.PUBLIC, address_scramble_LAP(toNumber(publicIdentityAddress(lowerTester).address)) ) ];
    RPAs[upperTester].localIRK = [ random.randint(0,255) for _ in range(16) ];
    success = RPAs[upperTester].add( extraAddressses[0] ) and success;
    RPAs[upperTester].localIRK = [ random.randint(0,255) for _ in range(16) ];
    success = RPAs[upperTester].add( extraAddressses[1] ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[lowerTester].enable() and success;
    """
        Add Lower tester identity address to plus two more to Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester), extraAddressses[0], extraAddressses[1] ], trace);

    success = success and advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(200);
        """
            Check that the InitA from the connect indication is a RPA
        """
        success = Address( None, initiator.localRPA() ).isResolvablePrivate() and success;
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/CON/INI/BV-24-C [Network Privacy - Connection Establishment using resolving list with address resolution disabled]

    Last modified: 17-12-2019
    Reviewed and verified: 17-12-2019 Henrik Eriksen
"""
def ll_con_ini_bv_24_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, ExtendedAddressType.PUBLIC);
    """
        Add Public address of lowerTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace, upperIRK );
    success = RPA.clear();
    success = RPA.add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPA.timeout( 60 ) and success;
    success = RPA.disable() and success;

    success = success and advertiser.enable();
    connected = initiator.connect();
    success = success and not connected;

    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = initiator.cancelConnect();
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-04-C [Connection where Peripheral sends data to Central]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_04_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, upperTester, trace);

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        txData = [0 for _ in range(10)];
        pbFlags = 1;
        """
            Sending Data Packets with a fixed length less than 27...
        """
        for count in [ 100, 100, 1, 99 ]:
            pbFlags ^= 1;
            for j in range(count):

                dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, lowerTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);

        if maxPacketLength > 27:
            """
                Sending Data Packets with a random length greater than 27...
            """
            pbFlags, count = 0, 0;

            while count < 1000:
                txData = [0 for _ in range(random.randint(28, maxPacketLength))];

                dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);

                count += len(txData);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-05-C [Connection where Peripheral receives data from Central]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_05_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        txData = [0 for _ in range(10)];
        pbFlags = 1;
        """
            Sending Data Packets with a fixed length less than 27...
        """
        for count in [ 100, 100, 1, 99 ]:
            pbFlags ^= 1;
            trace.trace(7, '-'*77);
            for j in range(count):
                dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        trace.trace(7, '-'*77);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-06-C [Connection where Peripheral sends and receives data to and from Central]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_06_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        txData = [0 for _ in range(10)];
        pbFlags = 0;
        """
            Sending Data Packets with a fixed length less than 27...
        """
        for j in range(100):
            """
                Upper Tester is sending Data...
            """
            pbFlags ^= 1;
            trace.trace(7, '-'*77);
            dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readData(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
            """
                Lower Tester is sending Data...
            """
            pbFlags ^= 1;
            dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readData(transport, upperTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        trace.trace(7, '-'*77);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-10-C [Peripheral accepting Connection Parameter Update from Central]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing cannot be verified - see Air trace)
"""
def ll_con_per_bv_10_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(100);

        for interval, timeout in zip([ 6, 3200, 6 ], [ 300, 3200, 300 ]):
            """
                Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            """
            success = initiator.update(interval, interval, initiator.latency, timeout) and success;
            """
                Accept the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP...
            """
            success = initiator.acceptUpdate() and success;
            """
                Both lower and upper Tester should receive a LE Connection Update Complete Event...
            """
            success = initiator.updated() and success;

            transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-11-C [Peripheral sending Termination to Central]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing cannot be verified - see Air trace)
"""
def ll_con_per_bv_11_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and (initiator.reasons[0] == 0x16) and (initiator.reasons[1] == 0x13) and success;
        initiator.resetRoles();
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-12-C [Peripheral accepting Termination from Central]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing cannot be verified - see Air trace)
"""
def ll_con_per_bv_12_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        success = initiator.disconnect(0x13) and (initiator.reasons[0] == 0x16) and (initiator.reasons[1] == 0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-13-C [Peripheral Terminating Connection on Supervision Timer]
"""
def ll_con_per_bv_13_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    initiator.supervisionTimer = 3200;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(3200);
        hasEvent = has_event(transport, upperTester, 3200)[0];
        success = success and hasEvent;
        if hasEvent:
            event = get_event(transport, upperTester, 100);
            trace.trace(7, str(event));
        else:
            success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-14-C [Peripheral performs Feature Setup procedure]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_14_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Send LL_FEATURE_REQ to IUT
        """
        success = readRemoteFeatures(transport, lowerTester, initiator.handles[0], trace) and success;
        """
            Verify if lower tester received LE Read Remote Features Complete Event
        """
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, lowerTester, trace);
        success = hasFeatures and success;
        if hasFeatures:
            showFeatures(features, trace);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-19-C [Peripheral requests Version Exchange procedure]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_19_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        success = readRemoteVersionInformation(transport, upperTester, initiator.handles[1], trace) and success;

        hasVersion, handle, version, manufacturer, subVersion = hasReadRemoteVersionInformationCompleteEvent(transport, upperTester, trace);
        success = success and hasVersion;
        if hasVersion:
            trace.trace(8, "     version: 0x%02x" % version);
            trace.trace(8, " sub-version: 0x%04x" % subVersion);
            trace.trace(8, "manufacturer: 0x%04x" % manufacturer);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-20-C [Peripheral responds to Version Exchange procedure]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_20_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        success = readRemoteVersionInformation(transport, lowerTester, initiator.handles[0], trace) and success;

        hasVersion, handle, version, manufacturer, subVersion = hasReadRemoteVersionInformationCompleteEvent(transport, lowerTester, trace);
        success = success and hasVersion;
        if hasVersion:
            trace.trace(8, "     version: 0x%02x" % version);
            trace.trace(8, " sub-version: 0x%04x" % subVersion);
            trace.trace(8, "manufacturer: 0x%04x" % manufacturer);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-22-C [Peripheral requests Feature Exchange procedure]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_22_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Upper Tester sends an HCI_LE_Read_Local_Supported_Features command...
        """
        success = readLocalFeatures(transport, upperTester, trace)[0] and success;
        """
            Upper Tester sends an HCI_LE_Read_Remote_Features command...
        """
        success = readRemoteFeatures(transport, upperTester, initiator.handles[0], trace) and success;
        """
            Upper tester expects LE Read Remote Features Complete event...
        """
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, upperTester, trace);
        success = hasFeatures and success;
        if hasFeatures:
            showFeatures(features, trace);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-24-C [Peripheral requests Connection Parameters - Central Accepts]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing not verified - see Air trace)
"""
def ll_con_per_bv_24_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        initiator.switchRoles();
        """
            The test consists of 3 cases for specific connection intervals and supervision timeouts
        """
        for interval, timeout in zip([ 6, 3200, 6 ], [ 300, 3200, 300 ]):
            """
                Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            """
            success = initiator.update(interval, interval, initiator.latency, timeout) and success;
            """
                Accept the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP...
            """
            success = initiator.acceptUpdate() and success;
            """
                Both lower and upper Tester should receive a LE Connection Update Complete Event...
            """
            success = initiator.updated() and success;

            transport.wait(int(4 * interval * 1.25));

        initiator.resetRoles();
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-25-C [Peripheral requests Connection Parameters - Central Rejects]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing not verified - see Air trace)
"""
def ll_con_per_bv_25_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        initiator.switchRoles();

        interval, timeout = 6, 300;
        """
            Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Reject the LE Remote Connection Parameter Request Event by issuing a LL_REJECTEXT_IND...
        """
        success = initiator.rejectUpdate(0x0C) and success;
        """
            Both lower and upper Tester should receive a LE Connection Update Complete Event... if request was accepted
        """
        success = not initiator.updated() and initiator.status == 0x0C and success;

        transport.wait(int(4 * interval * 1.25));

        initiator.resetRoles();
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-26-C [Peripheral requests Connection Parameters - same procedure collision]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing not verified - see Air trace)
"""
def ll_con_per_bv_26_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        initiator.switchRoles();

        interval, timeout, errCode = 6, 300, 0x23;
        """
            Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Reject the LE Remote Connection Parameter Request Event by issuing a LL_REJECTEXT_IND...
            NOTE: Not according to test specification, LL_CONNECTION_PARAM_REQ should be issued prior to LL_REJECTEXT_IND,
                  but Zephyr is preventing us from sending the the LL_CONNECTION_PARAM_REQ first, returning COMMAND DISALLOWED
        """
        success = initiator.rejectUpdate(errCode) and success;

        initiator.resetRoles();
        """
            Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        updInitiatorRequest = initiator.update(interval, interval, initiator.latency, timeout);
        updPeerRequest = initiator.updPeerRequest;
        success = success and updInitiatorRequest and updPeerRequest;

        initiator.switchRoles();
        """
            Both lower and upper Tester should receive a LE Connection Update Complete Event... if request was accepted
        """
        updated = initiator.updated();
        success = success and not updated and (initiator.status == errCode);

        initiator.resetRoles();
        """
            Accept the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP...
        """
        initiator.updInitiatorRequest, initiator.updPeerRequest = updInitiatorRequest, updPeerRequest;
        success = initiator.acceptUpdate() and success;
        """
            Both lower and upper Tester should receive a LE Connection Update Complete Event...
        """
        success = initiator.updated() and success;

        transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-27-C [Peripheral requests Connection Parameters - channel map update procedure collision]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing not verified - see Air trace)
"""
def ll_con_per_bv_27_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        initiator.switchRoles();

        interval, timeout, errCode, channelMap = 6, 300, 0x2A, 0x1555555555;
        """
            Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Request an update of used channels - sends an LL_CHANNEL_MAP_IND...
        """
        success = channelMapUpdate(transport, lowerTester, channelMap, trace) and success;
        """
            Reject the LE Remote Connection Parameter Request Event by issuing a LL_REJECTEXT_IND...
        """
        success = initiator.rejectUpdate(errCode) and success;
        """
            Both lower and upper Tester should receive a LE Connection Update Complete Event... if request was accepted
        """
        success = not initiator.updated() and (initiator.status == errCode) and success;

        initiator.resetRoles();

        transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-29-C [Peripheral responds to Connection Parameters - Central no Preferred Periodicity]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen (NOTE: Timing not verified - see Air trace)
"""
def ll_con_per_bv_29_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        for interval, timeout in zip([6, 3200, 6], [300, 3200, 200]):
            """
                Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            """
            success = initiator.update(interval, interval, initiator.latency, timeout) and success;
            """
                Accept the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP...
            """
            success = initiator.acceptUpdate() and success;
            """
                Both lower and upper Tester should receive a LE Connection Update Complete Event... if request was accepted
            """
            success = initiator.updated() and success;

            transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-33-C [Peripheral responds to Connection Parameters request - event masked]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_33_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;
    """
        Mask LE Remote Connection Parameter Request Event
    """
    events = [0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    success = setLEEventMask(transport, upperTester, events, trace) and success;

    if connected:
        interval, timeout, errCode = 6, 300, 0x1A;
        """
            Send LL_CONNECTION_PARAM_REQ to IUT...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and not initiator.updPeerRequest and success;
        """
            Verify that lower tester receives a LL_REJECT_EXT_IND... unfortunately we cannot verify that (but it appears in the Air trace)!
        """
        success = initiator.updated() and success;

        transport.wait(int(4 * interval * 1.25))

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-34-C [Peripheral responds to Connection Parameters request - Host rejects]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_34_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout, errCode = 6, 300, 0x3B;
        """
            Send LL_CONNECTION_PARAM_REQ to IUT...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Reject the LE Remote Connection Parameter Request Event by issuing a LL_REJECT_EXT_IND...
        """
        success = initiator.rejectUpdate(errCode) and success;
        """
            Both lower and upper Tester should receive a LE Connection Update Complete Event... if request was accepted
        """
        success = not initiator.updated() and (initiator.status == errCode) and success;

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-40-C [Peripheral requests PHY Update procedure]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_40_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    columns = defaultdict(list); # each value in each column is appended to a list

    with open('src/tests/params_con_per_bv_40.csv') as f:
        reader = csv.reader(f);
        next(reader);
        for row in reader:
            for (i,v) in enumerate(row):
                columns[i].append(int(v, 16));

    all_phys, tx_phys, rx_phys = columns[1], columns[2], columns[3];

    if connected:
        initiator.switchRoles();

        for i in range(0, len(columns[0])):
            if (tx_phys[i] == 0) or (tx_phys[i] > 3) or (rx_phys[i] == 0) or (rx_phys[i] > 3):
                continue

            trace.trace(7, "Execute PHY Update with the following parameters:\tALL_PHYS: %s\tTX: %s\tRX: %s" % (str(all_phys[i]), str(tx_phys[i]), str(rx_phys[i])));

            success = initiator.updatePhys(all_phys[i], tx_phys[i], rx_phys[i], 0) and success;

            trace.trace(4, "Updated PHYs:\tTX: %s\tRX: %s\n" % (str(initiator.txPhys), str(initiator.rxPhys)))

        transport.wait(int(4 * initiator.intervalMin * 1.25))

        initiator.resetRoles()

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-42-C [Peripheral responds to PHY Update procedure]

    Last modified: 05-08-2019
    Reviewed and verified: 05-08-2019 Henrik Eriksen
"""
def ll_con_per_bv_42_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        initiator.switchRoles();

        success = initiator.updatePhys( 3, 0, 0, 0 ) and success;

        initiator.resetRoles();

        tabel = list(zip( [2, 1, 2, 1, 3, 3, 1, 2, 3], [2, 2, 1, 1, 2, 1, 3, 3, 3], [2, 1, 2, 1, 2, 2, 1, 2, 2], [2, 2, 1, 1, 2, 1, 2, 2, 2] ));

        for i in range(2):
            for txPhys, rxPhys, expTxPhys, expRxPhys in tabel:
                success = initiator.updatePhys(0, txPhys, rxPhys, 0) and success;
                success = success and (initiator.txPhys == expTxPhys) and (initiator.rxPhys == expRxPhys);
            random.shuffle(tabel)

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-77-C [Peripheral Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 1M PHY]

    Last modified: 09-08-2019
    Reviewed and verified: 09-08-2019 Henrik Eriksen (Note: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_per_bv_77_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    maxPacketTime, cmaxTxOctets, cmaxTxTime = calcMaxPacketTime(maxPacketLength), 27, 328;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, lowerTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, lowerTester, trace);
        showLEFeatures(features, trace);

        """
            Exchange data...
        """
        lenValues  = list(range(28, maxPacketLength-1)) + list(range(maxPacketLength+1, 250));
        timeValues = list(range(329, maxPacketTime-1)) + list(range(maxPacketTime+1, 2119));

        for txOctets, txTime in zip( [ 27, 251, maxPacketLength, 27, 27, 27, 251, 251, 251, maxPacketLength, maxPacketLength, maxPacketLength, \
                                       random.choice(lenValues), random.choice(lenValues), random.choice(lenValues), random.choice(lenValues) ], \
                                     [ 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, \
                                       random.choice(timeValues), random.choice(timeValues), random.choice(timeValues), random.choice(timeValues) ] ):

            success = setDataLength(transport, upperTester, initiator.handles[1], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, upperTester, trace);
                success = success and gotEvent;
                gotEvent = hasDataLengthChangedEvent(transport, lowerTester, trace)[0];
                success = success and gotEvent;

            pbFlags = 0;
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)];
            dataSent = writeData(transport, lowerTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, upperTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)];
            for i in range(20):
                dataSent = writeData(transport, upperTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, lowerTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-78-C [Peripheral requests Packet Data Length Update procedure; LE 1M PHY]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_per_bv_78_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    cmaxTxOctets, cmaxTxTime, maxPacketTime = 27, 328, int(2120 * maxPacketLength / 251);

    trace.trace(8, "Max supported packet length: %d octets. Max supported transmit time: %d micro seconds" % (maxPacketLength, maxPacketTime));

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, lowerTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, lowerTester, trace);
        showLEFeatures(features, trace);

        for txOctets, txTime in zip([ maxPacketLength, 27, 251, maxPacketLength, 27, 251, maxPacketLength, 27, 251, maxPacketLength, 27, 251 ], \
                                    [ maxPacketTime, maxPacketTime, maxPacketTime, 328, 328, 328, 2120, 2120, 2120, 2120, 2120, 2120 ]):

            success = setDataLength(transport, upperTester, initiator.handles[1], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, upperTester, trace);
                if not gotEvent:
                    trace.trace(7, "Missing Data Length Changed event from upperTester!");
                success = success and gotEvent;
                gotEvent = hasDataLengthChangedEvent(transport, lowerTester, trace)[0];
                if not gotEvent:
                    trace.trace(7, "Missing Data Length Changed event from lowerTester!");
                success = success and gotEvent;

            pbFlags = 0;
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)];
            dataSent = writeData(transport, upperTester, initiator.handles[1], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData));
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)];
            for i in range(20):
                dataSent = writeData(transport, lowerTester, initiator.handles[0], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData));
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-80-C [Peripheral Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 2M PHY]

    Last modified: 09-08-2019
    Reviewed and verified: 09-08-2019 Henrik Eriksen (Note: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_per_bv_80_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    maxPacketTime, cmaxTxOctets, cmaxTxTime = calcMaxPacketTime(maxPacketLength), 27, 328;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, lowerTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, lowerTester, trace);
        showLEFeatures(features, trace);

        """
            Switch to LE 2M PHY channel...
        """
        allPhys, txPhys, rxPhys, optionPhys = 0, 2, 2, 0;

        success = initiator.updatePhys(allPhys, txPhys, rxPhys, optionPhys) and success;
        success = (initiator.txPhys == txPhys) and (initiator.rxPhys == rxPhys) and success
        """
            Exchange data...
        """
        lenValues  = list(range(28, maxPacketLength-1)) + list(range(maxPacketLength+1, 250));
        timeValues = list(range(329, maxPacketTime-1)) + list(range(maxPacketTime+1, 2119));

        for txOctets, txTime in zip( [ 27, 251, maxPacketLength, 27, 27, 27, 251, 251, 251, maxPacketLength, maxPacketLength, maxPacketLength, \
                                       random.choice(lenValues), random.choice(lenValues), random.choice(lenValues), random.choice(lenValues) ], \
                                     [ 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, \
                                       random.choice(timeValues), random.choice(timeValues), random.choice(timeValues), random.choice(timeValues) ] ):

            success = setDataLength(transport, upperTester, initiator.handles[1], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, upperTester, trace);
                success = success and gotEvent;
                gotEvent = hasDataLengthChangedEvent(transport, lowerTester, trace)[0];
                success = success and gotEvent;

            pbFlags = 0;
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)];
            dataSent = writeData(transport, lowerTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, upperTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)];
            for i in range(20):
                dataSent = writeData(transport, upperTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, lowerTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BV-81-C [Peripheral requests Packet Data Length Update procedure; LE 2M PHY]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_per_bv_81_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    cmaxTxOctets, cmaxTxTime, maxPacketTime = 27, 328, int(2120 * maxPacketLength / 251);

    trace.trace(8, "Max supported packet length: %d octets. Max supported transmit time: %d micro seconds" % (maxPacketLength, maxPacketTime));

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, lowerTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, lowerTester, trace);
        showLEFeatures(features, trace);

        initiator.switchRoles();
        """
            Switch to the 2M PHY channel
        """
        txPhys, rxPhys, allPhys, optionPhys = 2, 2, 0, 0;
        success = initiator.updatePhys(allPhys, txPhys, rxPhys, optionPhys) and success;
        success = success and (initiator.txPhys == txPhys) and (initiator.rxPhys == rxPhys);

        initiator.resetRoles();

        for txOctets, txTime in zip([ maxPacketLength, 27, 251, maxPacketLength, 27, 251, maxPacketLength, 27, 251, maxPacketLength, 27, 251 ], \
                                    [ maxPacketTime, maxPacketTime, maxPacketTime, 328, 328, 328, 2120, 2120, 2120, 2120, 2120, 2120 ]):

            success = setDataLength(transport, upperTester, initiator.handles[1], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, upperTester, trace);
                success = success and gotEvent;
                gotEvent = hasDataLengthChangedEvent(transport, lowerTester, trace)[0];
                success = success and gotEvent;

            pbFlags = 0;
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)];
            dataSent = writeData(transport, upperTester, initiator.handles[1], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData));
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)];
            for i in range(20):
                dataSent = writeData(transport, lowerTester, initiator.handles[0], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData));
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/PER/BI-08-C [Peripheral responds to Connection Parameters request - Illegal Parameters]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_per_bi_08_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, errCode = 4, 0x1E;
        """
            Send LL_CONNECTION_PARAM_REQ to IUT...
        """
        success = initiator.update(interval, interval, initiator.latency, 300) and success;
        """
            Verify that lower tester receives a CONNECTION UPDATE COMPLETE Event...
        """
        success = not initiator.updated() and (initiator.status == errCode) and success;

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-03-C [Central sending Data packets to Peripheral]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_03_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
       Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        txData = [0 for _ in range(10)];
        pbFlags = 1;
        """
            Sending Data Packets with a fixed length less than 27...
        """
        for count in [ 100, 100, 1, 99 ]:
            pbFlags ^= 1;
            for j in range(count):
                dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, lowerTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);

        if maxPacketLength > 27:
            """
                Sending Data Packets with a random length greater than 27...
            """
            pbFlags, count = 0, 0;

            while count < 1000:
                txData = [0 for _ in range(random.randint(28, maxPacketLength))];

                dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
                success = success and dataSent;
                count += len(txData);
                if dataSent:
                    dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
                else:
                    break;

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-04-C [Central receiving Data packets from Peripheral]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_04_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        txData = [0 for _ in range(10)];
        pbFlags = 1;
        """
            Sending Data Packets with a fixed length less than 27...
        """
        for count in [ 100, 100, 1, 99 ]:
            pbFlags ^= 1;
            trace.trace(7, '-'*77);
            for j in range(count):
                dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        trace.trace(7, '-'*77);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-05-C [Central sending and receiving Data packets to and form Peripheral]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_05_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        pbFlags = 0;
        """
            Sending Data Packets with a fixed length less than 27...
        """
        for j in range(100):
            """
                Upper Tester is sending Data...
            """
            trace.trace(7, '-'*77);
            txData = [0x00 for _ in range(10)];
            dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readData(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
            """
                Lower Tester is sending Data...
            """
            txData = [0xFF for _ in range(10)];
            dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readData(transport, upperTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);

            if j == 0:
                pbFlags = 1;
        trace.trace(7, '-'*77);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-07-C [Central requests Connection Parameter Update]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: connection event where change take place cannot be verified - see Air trace)
"""
def ll_con_cen_bv_07_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout = 64, 3200;
        """
            Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Accept the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP...
        """
        success = initiator.acceptUpdate() and success;
        """
            Both lower and upper Tester should receive a LE Connection Update Complete Event...
        """
        success = initiator.updated() and success;
        """
            Wait for change to take place...
        """
        transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success

"""
    LL/CON/CEN/BV-08-C [Central Terminating Connection]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Not verified that IUT stops sending empty data packets - see Air trace)
"""
def ll_con_cen_bv_08_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        success = initiator.disconnect(0x13) and (initiator.reasons[0] == 0x16) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-09-C [Central accepting Connection Termination]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Not verified that IUT stops sending empty data packets - see Air trace)
"""
def ll_con_cen_bv_09_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and (initiator.reasons[1] == 0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-13-C [Central requests Feature Setup procedure]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_13_c(transport, upperTester, lowerTester, trace):

    LL_FEAT_BIT_MASK_VALID = 0x1CF2F # Bitmask for features not impacting feature masking (ll_feat.h)

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Read local features from lower- and upperTester to establish expected remote read
            result
        """
        hasFeatures, expectedFeatures = readLocalFeatures(transport, lowerTester, trace)
        hasFeatures, upperFeatures    = readLocalFeatures(transport, upperTester, trace)
        upperLocalFeatures = toNumber(upperFeatures)
        """
            Keep octets 1-7, and do the logical and on octet 0; also ignore the non-valid bits
            See BT Core spec V5.2, Vol 6. Part B chapter 4.6
        """
        expectedMaskedFeatures = toNumber([upperFeatures[0] & expectedFeatures[0]] + list(expectedFeatures[1:7]))
        expectedMaskedFeatures = expectedMaskedFeatures & LL_FEAT_BIT_MASK_VALID
        """
            Issue the LE Read Remote Features Command, verify the reception of a Command Status Event
        """
        success = readRemoteFeatures(transport, upperTester, initiator.handles[0], trace) and success;
        """
            Await the reception of a LE Read Remote Features Command Complete Event
        """
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, upperTester, trace);
        success = success and hasFeatures;
        if hasFeatures:
            showLEFeatures(features, trace)
            receivedMaskedFeatures = toNumber(features) & LL_FEAT_BIT_MASK_VALID
            success = (receivedMaskedFeatures == expectedMaskedFeatures) and success

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-20-C [Central requests Version Exchange procedure]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_20_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Issue the Read Remote Version Information Command, verify the reception of a Command Status Event
        """
        success = readRemoteVersionInformation(transport, upperTester, initiator.handles[0], trace) and success;
        """
            Await the reception of a Read Remote Version Information Complete Event
        """
        hasVersion, handle, version, manufacturer, subVersion = hasReadRemoteVersionInformationCompleteEvent(transport, upperTester, trace);
        success = success and hasVersion;
        if hasVersion:
            trace.trace(8, "     version: 0x%02x" % version);
            trace.trace(8, " sub-version: 0x%04x" % subVersion);
            trace.trace(8, "manufacturer: 0x%04x" % manufacturer);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-21-C [Central responds to Version Exchange procedure]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_21_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Issue the Read Remote Version Information Command, verify the reception of a Command Status Event
        """
        success = readRemoteVersionInformation(transport, lowerTester, initiator.handles[1], trace) and success;
        """
            Await the reception of a Read Remote Version Information Complete Event
        """
        hasVersion, handle, version, manufacturer, subVersion = hasReadRemoteVersionInformationCompleteEvent(transport, lowerTester, trace);
        success = success and hasVersion;
        if hasVersion:
            trace.trace(8, "     version: 0x%02x" % version);
            trace.trace(8, " sub-version: 0x%04x" % subVersion);
            trace.trace(8, "manufacturer: 0x%04x" % manufacturer);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-23-C [Central responds to Feature Exchange procedure]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_23_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Issue the LE Read Remote Features Command, verify the reception of a Command Status Event
        """
        success = success and readRemoteFeatures(transport, lowerTester, initiator.handles[1], trace);
        """
            Await the reception of a LE Read Remote Features Command Complete Event
        """
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, lowerTester, trace);
        success = success and hasFeatures;
        if hasFeatures:
            showLEFeatures(features, trace);
            # Bit 27 is "Masked to Peer" an must be cleared
            success = ((toNumber(features) & (1 << 27)) == 0) and success;

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-24-C [Central requests Connection Parameters - Peripheral Accepts]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Actual effect of change cannot be verified - see Air trace)
"""
def ll_con_cen_bv_24_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        for interval, timeout in zip([ 6, 3200, 6 ], [ 300, 3200, 300 ]):
            """
                Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            """
            success = initiator.update(interval, interval, initiator.latency, timeout) and success;
            """
                Accept the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP...
            """
            success = initiator.acceptUpdate() and success;
            """
                Both lower and upper Tester should receive a LE Connection Update Complete Event...
            """
            success = initiator.updated() and success;
            """
                Wait for change to take place...
            """
            transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-25-C [Central requests Connection Parameters - Peripheral Rejects]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Actual effect of change cannot be verified - see Air trace)
"""
def ll_con_cen_bv_25_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout = 6, 300;
        for reject in [ True, False ]:
            """
                Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            """
            success = initiator.update(interval, interval, initiator.latency, timeout) and success;
            """
                Accept or Reject the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP or a LL_REJECT_EXT_IND...
            """
            success = (initiator.rejectUpdate(0x3B) if reject else initiator.acceptUpdate()) and success;
            """
                Both lower and upper Tester should receive a LE Connection Update Complete Event... if request was accepted
            """
            if reject:
                success = not initiator.updated() and (initiator.status == 0x3B) and success;
            else:
                success = initiator.updated() and success;
            """
                Wait for optional change to take place...
            """
            transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-26-C [Central requests Connection Parameters - same procedure collision]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Test not according to specs - not possible!)
"""
def ll_con_cen_bv_26_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout = 6, 300;
        """
            Request an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        updRequested = initiator.update(interval, interval, initiator.latency, timeout);
        success = success and updRequested;
        """
            Verify that the lower tester receives a LE Remote Connection Parameter Request Event...
        """
        updPeerInvolved = initiator.updPeerRequest;
        success = success and updPeerInvolved;
        """
            Send a LL_CONNECTION_PARAM_REQ as a reaction to the LE Remote Connection Parameter Request Event...
            NOTE: We use a little nasty trick here. Swap the roles of initiator and peer and swap assigned handles...
        """
        initiator.switchRoles();
        """
            Update request will be rejected with an error code 0x0C - command disallowed...
        """
        success = success and not initiator.update(interval, interval, initiator.latency, timeout) and initiator.status == 0x0C;
        """
            Get back to original roles of initiator and peer...
        """
        initiator.resetRoles();
        """
            Send a LL_CONNECTION_PARAM_RSP as a reaction to the original LE Remote Connection Parameter Request Event...
        """
        initiator.updInitiatorRequest, initiator.updPeerRequest = updRequested, updPeerInvolved;
        success = success and initiator.acceptUpdate();
        """
            Both lower and upper Tester should receive a LE Connection Update Complete Event...
        """
        success = success and initiator.updated();
        """
            Wait for change to take place...
        """
        transport.wait(int(4 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-27-C [Central requests Connection Parameters - Channel Map Update procedure collision]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Switch to only even channels cannot be verified - see Air trace)
"""
def ll_con_cen_bv_27_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout = 6, 300;
        """
            Use only even channels...
        """
        success = channelMapUpdate(transport, upperTester, 0x1555555555, trace) and success;
        """
            Lower tester requests an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            NOTE: We use a little nasty trick here. Swap the roles of initiator and peer and swap assigned handles...
        """
        initiator.switchRoles();
        transport.wait(20); # FIXME: Avoid test failure due to Zephyr controller occasionally generating Different Transaction Collision
        """
            Lower tester requests an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Reject the LE Remote Connection Parameter Request Event by issuing a LL_REJECT_EXT_IND...
        """
        success = initiator.rejectUpdate(0x2A) and success;
        """
            Verify that the update was rejected with error code 0x2A
        """
        success = not initiator.updated() and (initiator.status == 0x2A) and success;
        """
            Get back to original roles of initiator and peer...
        """
        initiator.resetRoles();
        initiator.pre_updated = True;
        interval = 24;
        """
            Wait for change to take place...
        """
        transport.wait(int(8 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-29-C [Central requests Connection Parameters - Peripheral unsupported]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Change to connection interval cannot be verified - see Air trace)
"""
def ll_con_cen_bv_29_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout = 6, 300;
        """
            Upper tester requests an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
        """
        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Reject the LE Remote Connection Parameter Request Event by issuing a LL_REJECT_EXT_IND... (update will take place)
        """
        success = initiator.rejectUpdate(0x1A) and success;
        """
            Verify that the update was accepted
        """
        success = initiator.updated() and success;
        """
            Wait for change to take place...
        """
        transport.wait(int(8 * interval * 1.25));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-30-C [Central responds to Connection Parameters request - no Preferred_Periodicity]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (NOTE: Change to connection interval cannot be verified - see Air trace)
"""
def ll_con_cen_bv_30_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        for interval, timeout in zip([ 6, 3200, 6 ], [ 300, 3200, 300 ]):
            """
                Lower tester requests an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
                NOTE: We use a little nasty trick here. Swap the roles of initiator and peer and swap assigned handles...
            """
            initiator.switchRoles();

            success = initiator.update(interval, interval, initiator.latency, timeout) and success;
            """
                Accept the LE Remote Connection Parameter Request Event by issuing a LL_CONNECTION_PARAM_RSP...
            """
            success = initiator.acceptUpdate() and success;
            """
                Both lower and upper Tester should receive a LE Connection Update Complete Event...
            """
            success = initiator.updated() and success;
            """
                Wait for change to take place...
            """
            transport.wait(int(4 * interval * 1.25));

            initiator.resetRoles();

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-34-C [Central responds to Connection Parameters request - event masked]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_34_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;
    """
        Disable the LE Remote Connection Parameter Request event (Bit 5)
    """
    events = [0xDF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00];
    success = setLEEventMask(transport, upperTester, events, trace) and success;

    if connected:
        interval, timeout = 6, 300;
        """
            Lower tester requests an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            NOTE: We use a little nasty trick here. Swap the roles of initiator and peer and swap assigned handles...
        """
        initiator.switchRoles();

        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Update request should be rejected with a LL_REJECT_EXT_IND...
        """
        success = not initiator.updated() and (initiator.status == 0x1A) and success;

        initiator.resetRoles();

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-35-C [Central responds to Connection Parameters request - Host rejects]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_35_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout = 6, 300;
        """
            Lower tester requests an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            NOTE: We use a little nasty trick here. Swap the roles of initiator and peer and swap assigned handles...
        """
        initiator.switchRoles();

        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Reject the LE Remote Connection Parameter Request Event by issuing a LL_REJECT_EXT_IND...
        """
        success = initiator.rejectUpdate(0x3B) and success;
        """
            Verify that the update was rejected...
        """
        success = not initiator.updated() and success;

        initiator.resetRoles();

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-41-C [Central requests PHY Update procedure]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_41_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        optionPhys = 0;

        table = list(zip( [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3], \
                     [2, 1, 2, 1, 3, 3, 1, 2, 3, 0, 2, 0], \
                     [2, 2, 1, 1, 2, 1, 3, 3, 3, 2, 0, 0], \
                     [2, 1, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2], \
                     [2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 2, 2] ));

        for i in range(2):
            for allPhys, txPhys, rxPhys, expTxPhys, expRxPhys in table:
                success = initiator.updatePhys(allPhys, txPhys, rxPhys, optionPhys) and success;
                success = success and (initiator.txPhys == expTxPhys) and (initiator.rxPhys == expRxPhys);
            random.shuffle(table);

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-43-C [Central responds to PHY Update procedure]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bv_43_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        allPhys, optionPhys, expTxPhys, expRxPhys = 3, 0, 2, 2;

        success = initiator.updatePhys(allPhys, 1, 1, optionPhys) and success;
        success = success and (initiator.txPhys == expTxPhys) and (initiator.rxPhys == expRxPhys);

        table = list(zip( [2, 1, 2, 1, 3, 3, 1, 2, 3], \
                     [2, 2, 1, 1, 2, 1, 3, 3, 3], \
                     [2, 1, 2, 1, 2, 2, 1, 2, 2], \
                     [2, 2, 1, 1, 2, 1, 2, 2, 2] ));
        allPhys = 0;

        initiator.switchRoles();

        for i in range(2):
            for txPhys, rxPhys, expTxPhys, expRxPhys in table:
                success = initiator.updatePhys(allPhys, txPhys, rxPhys, optionPhys) and success;
                success = success and (initiator.txPhys == expTxPhys) and (initiator.rxPhys == expRxPhys);
            random.shuffle(table);

        initiator.resetRoles();

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-73-C [Central Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 1M PHY]

    Last modified: 08-08-2019
    Reviewed and verified: 08-08-2019 Henrik Eriksen (Note: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_cen_bv_73_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    maxPacketTime, cmaxTxOctets, cmaxTxTime = calcMaxPacketTime(maxPacketLength), 27, 328;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, upperTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, upperTester, trace);
        showLEFeatures(features, trace);

        """
            Exchange data...
        """
        lenValues  = list(range(28, maxPacketLength-1)) + list(range(maxPacketLength+1, 250));
        timeValues = list(range(329, maxPacketTime-1)) + list(range(maxPacketTime+1, 2119));

        for txOctets, txTime in zip( [ 27, 251, maxPacketLength, 27, 27, 27, 251, 251, 251, maxPacketLength, maxPacketLength, maxPacketLength, \
                                       random.choice(lenValues), random.choice(lenValues), random.choice(lenValues), random.choice(lenValues) ], \
                                     [ 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, \
                                       random.choice(timeValues), random.choice(timeValues), random.choice(timeValues), random.choice(timeValues) ] ):

            success = setDataLength(transport, lowerTester, initiator.handles[1], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, lowerTester, trace);
                success = success and gotEvent;
                if not gotEvent:
                    trace.trace(6, "Error: Missed Data Length Changed Event from lowerTester");
                gotEvent = hasDataLengthChangedEvent(transport, upperTester, trace)[0];
                success = success and gotEvent;
                if not gotEvent:
                    trace.trace(6, "Error: Missed Data Length Changed Event from upperTester");

            pbFlags = 0;
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)];
            dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)];
            for i in range(20):
                dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-74-C [Central Packet Data Length Update - Initiating Packet Data Length Update Procedure; LE 1M PHY]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (Note: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_cen_bv_74_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    maxPacketTime, cmaxTxOctets, cmaxTxTime = calcMaxPacketTime(maxPacketLength), 27, 328;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, upperTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, upperTester, trace);
        showLEFeatures(features, trace);

        for txOctets, txTime in zip( [ maxPacketLength, 27, 251, maxPacketLength, 27, 251, maxPacketLength, 27, 251, 60, 27, 251 ], \
                                     [ maxPacketTime, maxPacketTime, maxPacketTime, 328, 328, 328, 2120, 2120, 2120, 2120, 2120, 2120 ] ):

            success = setDataLength(transport, upperTester, initiator.handles[0], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, upperTester, trace);
                success = success and gotEvent;
                gotEvent = hasDataLengthChangedEvent(transport, lowerTester, trace)[0];
                success = success and gotEvent;

            pbFlags = 0;
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)];
            dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)];
            for i in range(20):
                dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-76-C [Central Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 2M PHY]

    Last modified: 08-08-2019
    Reviewed and verified: 08-08-2019 Henrik Eriksen (Note: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_cen_bv_76_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    maxPacketTime, cmaxTxOctets, cmaxTxTime = calcMaxPacketTime(maxPacketLength), 27, 328;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, upperTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, upperTester, trace);
        showLEFeatures(features, trace);

        """
            Switch to LE 2M PHY channel...
        """
        allPhys, txPhys, rxPhys, optionPhys = 0, 2, 2, 0;

        success = initiator.updatePhys(allPhys, txPhys, rxPhys, optionPhys) and success;
        success = (initiator.txPhys == txPhys) and (initiator.rxPhys == rxPhys) and success
        """
            Exchange data...
        """
        lenValues  = list(range(28, maxPacketLength-1)) + list(range(maxPacketLength+1, 250));
        timeValues = list(range(329, maxPacketTime-1)) + list(range(maxPacketTime+1, 2119));

        for txOctets, txTime in zip( [ 27, 251, maxPacketLength, 27, 27, 27, 251, 251, 251, maxPacketLength, maxPacketLength, maxPacketLength, \
                                       random.choice(lenValues), random.choice(lenValues), random.choice(lenValues), random.choice(lenValues) ], \
                                     [ 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, 328, 2120, maxPacketTime, \
                                       random.choice(timeValues), random.choice(timeValues), random.choice(timeValues), random.choice(timeValues) ] ):

            success = setDataLength(transport, lowerTester, initiator.handles[1], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, lowerTester, trace);
                success = success and gotEvent;
                gotEvent = hasDataLengthChangedEvent(transport, upperTester, trace)[0];
                success = success and gotEvent;

            pbFlags = 0;
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)];
            dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)];
            for i in range(20):
                dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData)) and (rxData == txData);
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BV-77-C [Central Packet Data Length Update - Initiating Packet Data Length Update Procedure; LE 2M PHY]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen (Note: Requires that CONFIG_BT_CTLR_DATA_LENGTH_MAX=60 is set in the prj.conf file for the ptt_app.)
"""
def ll_con_cen_bv_77_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);
    """
        Obtain maximum Data Packet size and maximum number of Data Packets
    """
    success, maxPacketLength, maxPacketNumbers = readBufferSize(transport, lowerTester, trace);
    maxPacketTime, cmaxTxOctets, cmaxTxTime = calcMaxPacketTime(maxPacketLength), 27, 328;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Feature exchange as specified in LL.TS.5.1.1, chapter 4.1.5
        """
        success = readRemoteFeatures(transport, upperTester, initiator.handles[0], trace) and success;
        hasFeatures, handle, features = hasReadRemoteFeaturesCompleteEvent(transport, upperTester, trace);
        showLEFeatures(features, trace);

        allPhys, txPhys, rxPhys, optionPhys = 0, 2, 2, 0;

        success = initiator.updatePhys(allPhys, txPhys, rxPhys, optionPhys) and success;
        success = (initiator.txPhys == txPhys) and (initiator.rxPhys == rxPhys) and success

        for txOctets, txTime in zip( [ maxPacketLength, 27, 251, maxPacketLength, 27, 251, maxPacketLength, 27, 251, maxPacketLength, 27, 251 ], \
                                     [ maxPacketTime, maxPacketTime, maxPacketTime, 328, 328, 328, 2120, 2120, 2120, 2120, 2120, 2120 ] ):

            success = setDataLength(transport, upperTester, initiator.handles[0], txOctets, txTime, trace) and success;
            requested = success;
            success = success or (not success and ((txOctets > maxPacketLength) or (txTime > maxPacketTime)));

            changed = not ((cmaxTxOctets == min(txOctets, maxPacketLength)) and ((cmaxTxTime == max(txTime, 328))));

            if requested and changed:
                gotEvent, handle, cmaxTxOctets, cmaxTxTime, maxRxOctets, maxRxTime = hasDataLengthChangedEvent(transport, upperTester, trace);
                if not gotEvent:
                    trace.trace(7, "Missing Data Length Changed Event from upperTester!");
                success = success and gotEvent;
                gotEvent = hasDataLengthChangedEvent(transport, lowerTester, trace)[0];
                if not gotEvent:
                    trace.trace(7, "Missing Data Length Changed Event from lowerTester!");
                success = success and gotEvent;

            pbFlags = 0
            """
                Upper Tester is sending Data...
            """
            txData = [_ for _ in range(maxPacketLength)]
            dataSent = writeData(transport, upperTester, initiator.handles[0], pbFlags, txData, trace);
            success = success and dataSent;
            if dataSent:
                dataReceived, rxData = readDataFragments(transport, lowerTester, trace);
                success = success and dataReceived and (len(rxData) == len(txData));
            """
                Lower Tester is sending Data...
            """
            txData = [_ for _ in range(27)]
            for i in range(20):
                dataSent = writeData(transport, lowerTester, initiator.handles[1], pbFlags, txData, trace);
                success = success and dataSent;
                if dataSent:
                    dataReceived, rxData = readData(transport, upperTester, trace);
                    success = success and dataReceived and (len(rxData) == len(txData));
        """
            Note: Disconnect can generate another LE Data Length Change event...
        """
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/CON/CEN/BI-06-C [Central responds to Connection Parameter Request - illegal parameters]

    Last modified: 06-08-2019
    Reviewed and verified: 06-08-2019 Henrik Eriksen
"""
def ll_con_cen_bi_06_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPublicInitiator(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED);

    success = advertiser.enable();
    connected = initiator.connect();
    success = success and connected;

    if connected:
        interval, timeout = 4, 300;
        """
            Lower tester requests an update of the connection parameters - sends an LL_CONNECTION_PARAM_REQ...
            NOTE: We use a little nasty trick here. Swap the roles of initiator and peer and swap assigned handles...
        """
        initiator.switchRoles();

        success = initiator.update(interval, interval, initiator.latency, timeout) and success;
        """
            Verify that the update was rejected...
        """
        success = not initiator.updated() and (initiator.status == 0x1E) and success;

        initiator.resetRoles();

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-01-C [Changing Static Address while Advertising]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen (NOTE: Test fails - test specification is omitting Filter Accept List addition!)
"""
def ll_sec_adv_bv_01_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 30, 5, \
                                                   ExtendedAddressType.RANDOM, ExtendedAddressType.RANDOM, AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT' );
    """
        Adding lowerTester address to the Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ randomIdentityAddress(lowerTester) ], trace);

    success = advertiser.enable();
    success = scanner.enable() and success;
    scanner.monitor();
    """
        Attempt to change advertiser (upperTester) address...
    """
    status = le_set_random_address(transport, upperTester, toArray(address_scramble_OUI( toNumber(tests.test_utils.upperRandomAddress) ), 6), 100);
    trace.trace(6, "LE Set Random Address Command returns status: 0x%02X" % status);
    success = getCommandCompleteEvent(transport, upperTester, trace) and (status == 0x0C) and success;

    success = scanner.disable() and success;
    success = success and scanner.qualifyReports( 5 );
    success = success and scanner.qualifyResponses( 5, advertiser.responseData);

    success = advertiser.disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-02-C [Non Connectable Undirected Advertising with non-resolvable private address]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_02_c(transport, upperTester, lowerTester, trace):

    """
        Make sure that random address for upperTester is a non-resolvable private addresses
    """
    setNonResolvableRandomAddress(transport, upperTester, trace);

    advertiser, scanner = setPrivatePassiveScanning(transport, lowerTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 100, \
                                                    ExtendedAddressType.RESOLVABLE_OR_RANDOM, ExtendedAddressType.PUBLIC);
    """
        Add Random address of upperTester to the Resolving List
    """
    RPA = ResolvableAddresses( transport, upperTester, trace );
    success = RPA.add( publicIdentityAddress( lowerTester ) );
    """
        Enable Private Address Resolution
     """
    success = RPA.timeout( 60 ) and success;
    success = RPA.enable() and success;

    """
        Start NON_CONNECTABLE_ADVERTISING using non-resolvable private adddress
    """
    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor()
    success = scanner.disable() and success;
    success = success and scanner.qualifyReports( 100, randomIdentityAddress(upperTester) );

    success = advertiser.disable() and success;
    success = RPA.disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-03-C [Non Connectable Undirected Advertising with resolvable private address]

    Last modified: 21-08-2019
    Reviewed and verified: 21-08-2019 Henrik Eriksen
    Change: ReadLocalResolvableAddress() -> ReadPeerResolvableAddress()
"""
def ll_sec_adv_bv_03_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivatePassiveScanning(transport, lowerTester, trace, Advertising.NON_CONNECTABLE_UNDIRECTED, 20);
    """
        Add Public address of lowerTester to the Resolving List with the upperIRK
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester) ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( two and sixty seconds )
    """
    success = RPAs[upperTester].timeout( 2 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    resolvableAddresses = [ 0, 0 ];
    success = advertiser.enable() and success;

    for n in range(2):
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = success and scanner.qualifyReports( 20, resolvablePublicAddress(upperTester) );
        """
            Read local address in resolving list.
        """
        addressRead, resolvableAddresses[n] = readPeerResolvableAddress(transport, lowerTester, publicIdentityAddress(upperTester), trace);
        trace.trace(6, "Local Resolvable Address: %s" % formatAddress(resolvableAddresses[n]));

        if n == 0:
            transport.wait(2000); # Wait for RPA timeout to expire

    success = advertiser.disable() and success;
    success = success and toNumber(resolvableAddresses[0]) != toNumber(resolvableAddresses[1]);
    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-04-C [Scannable Undirected Advertising with non-resolvable private address]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_04_c(transport, upperTester, lowerTester, trace):

    """
        Make sure that random addresses for lower- and upper-Tester are non-resolvable private addresses
    """
    setNonResolvableRandomAddress(transport, lowerTester, trace);
    setNonResolvableRandomAddress(transport, upperTester, trace);

    advertiser, scanner = setPrivateActiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 100, 5, \
                                                   ExtendedAddressType.RANDOM, ExtendedAddressType.RANDOM);

    success = advertiser.enable();
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = scanner.qualifyReports( 100, randomIdentityAddress(upperTester) ) and success;
    success = scanner.qualifyResponses( 5, advertiser.responseData) and success;

    return success;

"""
    LL/SEC/ADV/BV-05-C [Scannable Undirected Advertising with resolvable private address]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_05_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20, 1, \
                                                   ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                   AdvertisingFilterPolicy.FILTER_SCAN_REQUESTS);

    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Adding lowerTester address to the Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( two and sixty seconds )
    """
    success = RPAs[upperTester].timeout( 2 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = success and advertiser.enable();

    resolvableAddresses = [ 0, 0 ];
    for n in range(2):
        success = scanner.enable() and success;
        scanner.monitor();
        success = scanner.disable() and success;
        success = scanner.qualifyReports( 20, resolvablePublicAddress(upperTester) ) and success;
        success = scanner.qualifyResponses( 1, advertiser.responseData ) and success;

        addressRead, resolvableAddresses[n] = readLocalResolvableAddress(transport, upperTester, publicIdentityAddress(lowerTester), trace);
        trace.trace(6, "AdvA: %s" % formatAddress(resolvableAddresses[n]));
        if n == 0:
            transport.wait(2000); # Wait for RPA timeout

    success = advertiser.disable() and success;
    success = success and toNumber(resolvableAddresses[0]) != toNumber(resolvableAddresses[1]);
    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-06-C [Connecting with Undirected Connectable Advertiser using non-resolvable private address]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_06_c(transport, upperTester, lowerTester, trace):

    """
        Make sure that random address for upperTester is a non-resolvable private addresses
    """
    setNonResolvableRandomAddress(transport, upperTester, trace);

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_RANDOM, ExtendedAddressType.PUBLIC);

    lowerAddresses = [ publicIdentityAddress(lowerTester), \
                       Address( ExtendedAddressType.PUBLIC, toNumber(tests.test_utils.lowerRandomAddress) | 0xC00000000000 ), \
                       Address( ExtendedAddressType.PUBLIC, toNumber(tests.test_utils.lowerRandomAddress) & 0x3FFFFFFFFFFF ) ];

    success = True;
    for lowerAddress in lowerAddresses:
        advertiser.peerAddress = lowerAddress;
        initiator.initiatorAddress = lowerAddress;
        initiator.peerAddress = randomIdentityAddress(upperTester);

        if lowerAddress.type == ExtendedAddressType.PUBLIC:
            success = preamble_set_public_address(transport, lowerTester, toNumber(lowerAddress.address), trace) and success;
        else:
            success = preamble_set_random_address(transport, lowerTester, toNumber(lowerAddress.address), trace) and success;

        success = advertiser.enable() and success;
        connected = initiator.connect();
        success = connected and success;
        if connected:
            success = initiator.disconnect(0x13) and success;
        else:
            success = advertiser.disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-07-C [Connecting with Undirected Connectable Advertiser with Local IRK but no Peer IRK]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen (NOTE: Test fails - filtering doesn't work!)
"""
def ll_sec_adv_bv_07_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_CONNECTION_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester) ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Adding lowerTester address to the Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Upper tester terminates the connection
        """
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and success;
        initiator.resetRoles();
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-08-C [Connecting with Undirected Connectable Advertiser with both Local and Peer IRK]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_08_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Adding lowerTester address to the Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Upper tester (PERIPHERAL) terminates the connection
        """
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and success;
        initiator.resetRoles();
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-09-C [Connecting with Undirected Connectable Advertiser with no Local IRK but peer IRK]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_09_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;
    """
        Adding lowerTester address to the Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Upper tester (PERIPHERAL) terminates the connection
        """
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and success;
        initiator.resetRoles();
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success

    return success;

"""
    LL/SEC/ADV/BV-10-C [Connecting with Undirected Connectable Advertiser where no match for Peer Device Identity]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_10_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Configure RPAs to use the IRKs for address resolutions
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    """
        Add Identity Addresses to Resolving Lists
    """
    bogusIRK = [ random.randint(0,255) for _ in range(16) ];
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), bogusIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Add Identity Address of lower Tester to Filter Accept List to enable responding to Scan Requests
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;

    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;

    for n in range(10):
        connected = initiator.connect();
        success = success and not connected;
        if connected:
            success = initiator.disconnect(0x13) and success;
            break;

    success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-11-C [Connecting with Directed Connectable Advertiser using local and remote IRK]

    Last modified: 17-12-2019
    Reviewed and verified: 17-12-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_11_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Verify that connection was established with resolvable private addresses
        """
        isRPA = Address( None, initiator.localRPA() ).isResolvablePrivate();
        isRPA = Address( None, initiator.peerRPA() ).isResolvablePrivate() and isRPA;
        success = isRPA and success;
        if not isRPA:
            trace.trace(6, "Wrong RPAs - local RPA: %s peer RPA: %s" %(Address( None, initiator.localRPA() ), Address( None, initiator.peerRPA() )));
        """
            Upper tester (PERIPHERAL) terminates the connection
        """
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and success;
        initiator.resetRoles();
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and success;

    advertiserTimeout = False;
    success = advertiser.enable() and success;
    initiator.checkPrematureDisconnect = False;
    """
        Retry connection 20 times.
    """
    for i in range(20):
        connected = initiator.connect();
        success = success and not connected;
        if connected:
            success = initiator.disconnect(0x13) and success;
            break;
        else:
            advertiserTimeout, waitTime = False, 0;
            while not advertiserTimeout:
                flush_events(transport, lowerTester, 100);
                advertiserTimeout = advertiser.timeout();
                waitTime += 100;
                if waitTime >= 1300:
                    break;
            if advertiserTimeout:
                trace.trace(7, "Advertising done!");
                success = advertiser.enable(True) and success;
            else:
                break;

    if not advertiserTimeout:
        success = advertiser.disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-12-C [Connecting with Directed Connectable Advertising with local IRK but without remote IRK]

    Last modified: 21-08-2019
    Reviewed and verified: 21-08-2019 Henrik Eriksen
    Change: ReadLocalResolvableAddress() -> ReadPeerResolvableAddress()
"""
def ll_sec_adv_bv_12_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester) ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( two seconds )
    """
    success = RPAs[upperTester].timeout( 2 ) and RPAs[lowerTester].timeout( 2 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    privateAddresses = [ 0, 0 ];

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Read the resolvable address used in the AdvA field
        """
        addressRead, privateAddresses[0] = readPeerResolvableAddress(transport, lowerTester, publicIdentityAddress(upperTester), trace);
        trace.trace(6, "AdvA Address: %s" % formatAddress(privateAddresses[0]));
        """
            Upper tester (PERIPHERAL) terminates the connection
        """
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and success;
        initiator.resetRoles();

        transport.wait( 2000 ); # wait for RPA to timeout
        """
            Extra connect step is necassary in order to the read the
        """
        success = advertiser.enable() and success;
        connected = initiator.connect();
        success = success and connected;

        if connected:
            """
                Read the resolvable address used in the AdvA field
            """
            addressRead, privateAddresses[1] = readPeerResolvableAddress(transport, lowerTester, publicIdentityAddress(upperTester), trace);
            trace.trace(6, "AdvA Address: %s" % formatAddress(privateAddresses[1]));

            success = initiator.disconnect(0x13) and success;
        else:
            success = advertiser.disable() and success;
    else:
        success = advertiser.disable() and success;

    success = success and (privateAddresses[0] != privateAddresses[1]);
    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-13-C [Directed Connectable Advertising without local IRK but with remote IRK]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen (NOTE: Test fails!)
"""
def ll_sec_adv_bv_13_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester) ) and success;
    """
        Set resolvable private address timeout in seconds ( two seconds )
    """
    success = RPAs[upperTester].timeout( 2 ) and RPAs[lowerTester].timeout( 2 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    privateAddresses = [ 0, 0 ];

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        """
            Read the resolvable address used in the AdvA field
        """
        addressRead, privateAddresses[0] = readLocalResolvableAddress(transport, lowerTester, publicIdentityAddress(upperTester), trace);
        trace.trace(6, "InitA Address: %s" % formatAddress(privateAddresses[0]));
        """
            Upper tester (PERIPHERAL) terminates the connection
        """
        initiator.switchRoles();
        success = initiator.disconnect(0x13) and success;
        initiator.resetRoles();

        transport.wait( 2000 ); # wait for RPA to timeout

        success = advertiser.enable() and success;
        connected = initiator.connect();
        success = success and connected;
        """
            Read the resolvable address used in the AdvA field
        """
        addressRead, privateAddresses[1] = readLocalResolvableAddress(transport, lowerTester, publicIdentityAddress(upperTester), trace);
        trace.trace(6, "InitA Address: %s" % formatAddress(privateAddresses[1]));

        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = success and (privateAddresses[0] != privateAddresses[1]);
    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-14-C [Directed Connectable Advertising using Resolving List and Peer Device Identity not in the List]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_14_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.RESOLVABLE_OR_PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Configure RPAs to use the IRKs for address resolutions
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    """
        Add Identity Addresses to Resolving Lists
    """
    bogusIRK = [ random.randint(0,255) for _ in range(16) ];
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), bogusIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Add Identity Address of lower Tester to Filter Accept List to enable responding to Scan Requests
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;

    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and not connected;

    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        """
            Need to stop connection attempt - otherwies Resolvable List disable will fail with command not allowed...
        """
        success = initiator.cancelConnect() and success;
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-15-C [Scannable Advertising with resolvable private address, no Scan Response to Identity Address]

    Last modified: 07-08-2019
    Reviewed and verified: 07-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_15_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20, 1, \
                                                   ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC, AdvertisingFilterPolicy.FILTER_SCAN_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Adding lowerTester address to the Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;

    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.qualifyReports( 20, resolvablePublicAddress(upperTester) ) and success;
    success = not scanner.qualifyResponses( 1 ) and success;
    success = scanner.disable() and success;
    success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-16-C [Undirected Connectable Advertising with resolvable private address; no Connection to Identity Address]

    Last modified: 10-09-2019
    Reviewed and verified: 10-09-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_16_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Adding lowerTester address to the Filter Accept List
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and not connected;
    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-17-C [Directed Connectable Advertising using local and remote IRK, Ignore Identity Address]

    Last modified: 08-08-2019
    Reviewed and verified: 08-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_17_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Add Identity addresses of upperTester and lowerTester to respective Resolving Lists with the distributed IRKs
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and not connected;
    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success

"""
    LL/SEC/ADV/BV-18-C [Scannable Advertising with resolvable private address, accept Identity Address]

    Last modified: 08-08-2019
    Reviewed and verified: 08-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_18_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, lowerTester, trace, Advertising.SCANNABLE_UNDIRECTED, 20, 1, \
                                                   ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC);
    """
        Configure RPAs to use the IRKs for address resolutions
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set Device Privacy
    """
    success = setPrivacyMode(transport, upperTester, publicIdentityAddress(lowerTester), PrivacyMode.DEVICE_PRIVACY, trace) and success;
    """
        Add Identity Address of lower Tester to Filter Accept List to enable responding to Scan Requests
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    success = scanner.enable() and success;
    scanner.monitor();
    success = scanner.disable() and success;
    success = advertiser.disable() and success;
    success = scanner.qualifyReports( 20, resolvablePublicAddress(upperTester) ) and success;
    success = scanner.qualifyResponses( 1, advertiser.responseData ) and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-19-C [Undirected Connectable Advertising with Local IRK and Peer IRK, accept Identity Address]

    Last modified: 08-08-2019
    Reviewed and verified: 08-08-2019 Henrik Eriksen (NOTE: Test fails!)
"""
def ll_sec_adv_bv_19_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_UNDIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Configure RPAs to use the IRKs for address resolutions
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set Device Privacy
    """
    success = setPrivacyMode(transport, upperTester, publicIdentityAddress(lowerTester), PrivacyMode.DEVICE_PRIVACY, trace) and success;
    """
        Add Identity Address of lower Tester to Filter Accept List to enable responding to Scan Requests
    """
    success = addAddressesToFilterAcceptList(transport, upperTester, [ publicIdentityAddress(lowerTester) ], trace) and success;
    """
        Set resolvable private address timeout in seconds ( two and sixty seconds )
    """
    success = RPAs[upperTester].timeout( 2 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        transport.wait(2100); # Wait for address renewal
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success;

"""
    LL/SEC/ADV/BV-20-C [Directed Connectable Advertising with resolvable private address; Connect to Identity Address]

    Last modified: 08-08-2019
    Reviewed and verified: 08-08-2019 Henrik Eriksen
"""
def ll_sec_adv_bv_20_c(transport, upperTester, lowerTester, trace):

    advertiser, initiator = setPrivateInitiator(transport, lowerTester, trace, Advertising.CONNECTABLE_HDC_DIRECTED, \
                                                ExtendedAddressType.RESOLVABLE_OR_PUBLIC, ExtendedAddressType.PUBLIC, \
                                                AdvertisingFilterPolicy.FILTER_BOTH_REQUESTS);
    """
        Configure RPAs to use the IRKs for address resolutions
    """
    RPAs = [ ResolvableAddresses( transport, upperTester, trace, upperIRK ), ResolvableAddresses( transport, lowerTester, trace, lowerIRK ) ];
    success = RPAs[upperTester].clear() and RPAs[lowerTester].clear();
    success = RPAs[upperTester].add( publicIdentityAddress(lowerTester), lowerIRK ) and success;
    success = RPAs[lowerTester].add( publicIdentityAddress(upperTester), upperIRK ) and success;
    """
        Set Device Privacy
    """
    success = setPrivacyMode(transport, upperTester, publicIdentityAddress(lowerTester), PrivacyMode.DEVICE_PRIVACY, trace) and success;
    """
        Set resolvable private address timeout in seconds ( sixty seconds )
    """
    success = RPAs[upperTester].timeout( 60 ) and RPAs[lowerTester].timeout( 60 ) and success;
    success = RPAs[upperTester].enable() and RPAs[lowerTester].enable() and success;

    success = advertiser.enable() and success;
    connected = initiator.connect();
    success = success and connected;

    if connected:
        success = initiator.disconnect(0x13) and success;
    else:
        success = advertiser.disable() and success;

    success = RPAs[upperTester].disable() and RPAs[lowerTester].disable() and success;

    return success

"""
    LL/SEC/SCN/BV-01-C [Changing Static Address while Scanning]

    Last modified: 08-08-2019
    Reviewed and verified: 08-08-2019 Henrik Eriksen
"""
def ll_sec_scn_bv_01_c(transport, upperTester, lowerTester, trace):

    advertiser, scanner = setPrivateActiveScanning(transport, upperTester, trace, Advertising.CONNECTABLE_UNDIRECTED, 20, 1, \
                                                   ExtendedAddressType.RANDOM, ExtendedAddressType.RANDOM);
    adData = ADData();
    advertiser.responseData = adData.encode( ADType.COMPLETE_LOCAL_NAME, 'IUT' );

    success = advertiser.enable();
    success = scanner.enable() and success;
    scanner.monitor();
    """
        Attempt to change scanner (upperTester) address...
    """
    status = le_set_random_address(transport, upperTester, toArray(address_scramble_OUI( toNumber(tests.test_utils.upperRandomAddress) ), 6), 100);
    trace.trace(6, "LE Set Random Address Command returns status: 0x%02X" % status);
    """
        Event queue may hold several Advertising Events...
    """
    while not getCommandCompleteEvent(transport, upperTester, trace):
        pass;
    success = (status == 0x0C) and success;

    success = scanner.disable() and success;
    success = scanner.qualifyReports( 20, randomIdentityAddress(lowerTester) ) and success;
    success = scanner.qualifyResponses( 5, advertiser.responseData) and success;

    success = advertiser.disable() and success;

    address = toNumber( randomIdentityAddress(lowerTester).address );
    randAddr = (address >> 24) & 0xFFFFFF;
    hashAddr = address & 0xFFFFFF;

    trace.trace(8, "Address parts: rand: 0x%06X hash: 0x%06X" % (randAddr, hashAddr));
    ok, localHash = encrypt(transport, upperTester, lowerIRK, toArray(randAddr, 16), trace);
    success = success and ok and (toNumber(localHash) & 0xFFFFFF == hashAddr);
    trace.trace(8, "Regenerated: hash: 0x%06X" % (toNumber(localHash) & 0xFFFFFF));

    return success;


def cis_setup_response_procedure_peripheral(transport, upper_tester, lower_tester, trace, params):
    """
    [CIS Setup Response Procedure, Peripheral]
    """
    success, initiator, _, (cisConnectionHandle,) = \
        state_connected_isochronous_stream(transport, upper_tester, lower_tester, trace, params)

    # 10. The Lower Tester sends data packets to the IUT.
    # 11. The Upper Tester IUT sends an ISO data packet to the Upper Tester.
    def lt_send_data_packet(pkt_seq_num):
        return iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, cisConnectionHandle,
                                    params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, pkt_seq_num)

    # 12. Perform either Alternative 12A or 12B depending on whether P_To_C Payload (PDU) in Table 4.146 is 0:
    #     Alternative 12A (P_To_C Payload (PDU) is not equal to 0):
    #       12A.1. TODO: The IUT sends an Ack to the Lower Tester.
    #     Alternative 12B (P_To_C Payload (PDU) is equal to 0):
    #       12B.1. TODO: The IUT sends a CIS Null PDU to the Lower Tester.

    # 13. Repeat steps 10-12 a total of 50 times.
    for j in range(50):
        success = lt_send_data_packet(j) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-01-C [CIS Setup Response Procedure, Peripheral]
"""
def ll_cis_per_bv_01_c(transport, upperTester, lowerTester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 7500, # 7.5 ms
        SDU_Interval_P_To_C     = 7500, # 7.5 ms
        ISO_Interval            = int(7.5 // 1.25), # 7.5
        NSE                     = 2,
        Max_PDU_C_To_P          = 60, # TODO: Supposed to be 160
        Max_PDU_P_To_C          = 60, # TODO: Supposed to be 160
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 1,
    )

    return cis_setup_response_procedure_peripheral(transport, upperTester, lowerTester, trace, params)


def cis_setup_peripheral_rejected(transport, peripheral, central, trace):
    success = True

    status = le_set_host_feature(transport, central, FeatureSupport.ISOCHRONOUS_CHANNELS, 1, 100)
    success = getCommandCompleteEvent(transport, central, trace) and (status == 0x00) and success

    status = le_set_host_feature(transport, peripheral, FeatureSupport.ISOCHRONOUS_CHANNELS, 1, 100)
    success = getCommandCompleteEvent(transport, peripheral, trace) and (status == 0x00) and success

    advertiser, initiator = setPublicInitiator(transport, central, trace, Advertising.CONNECTABLE_UNDIRECTED)
    success = advertiser.enable() and success
    connected = initiator.connect()
    success = success and connected

    if not connected:
        success = advertiser.disable() and success
        return success

    # 1. The Upper Tester sends an HCI_LE_Set_Event_Mask command with all events enabled,
    #    including the HCI_LE_CIS_Request event. The IUT sends a successful
    #    HCI_Command_Complete in response.
    #
    # NOTE: This is already performed during the preamble step

    # 2. The Lower Tester sends an LL_CIS_REQ PDU with valid data as specified for the Set CIG
    #    Parameters command in Section 4.10.1.3 Default Values for Set CIG Parameters Commands to
    #    the IUT.
    # NOTE: CIG_ID is hardcoded to 0
    params = SetCIGParameters()

    status, cigId, cisCount, cis_handle_central = \
    le_set_cig_parameters_test(transport, central, 0, *params.get_cig_parameters_test(), 100)
    success = getCommandCompleteEvent(transport, central, trace) and (status == 0x00) and success

    status = le_create_cis(transport, central, 1, cis_handle_central, [initiator.handles[0]], 100)
    success = verifyAndShowEvent(transport, central, Events.BT_HCI_EVT_CMD_STATUS, trace) and (status == 0) and success

    # 3. The Upper Tester receives an HCI_LE_CIS_Request event from the IUT.
    s, event = verifyAndFetchMetaEvent(transport, peripheral, MetaEvents.BT_HCI_EVT_LE_CIS_REQUEST, trace)
    success = s and success
    aclConnectionHandle, cis_handle_peripheral, cigId, cisId = event.decode()

    # 4. The Upper Tester sends an HCI_LE_Reject_CIS_Request command to the IUT with a valid
    #    reason code and receives a successful return status.
    status, _ = le_reject_cis_request(transport, peripheral, cis_handle_peripheral, 0x0D, 100)

    # 5. The Upper Tester receives an HCI_Command_Complete event from the IUT.
    success = getCommandCompleteEvent(transport, peripheral, trace) and (status == 0x00) and success

    # 6. The Lower Tester receives an LL_REJECT_EXT_IND from the IUT with a valid reason code.
    s, event = verifyAndFetchMetaEvent(transport, central, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace)
    success = s and (event.decode()[0] == 0x0D) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_per_bv_02_c(transport, upperTester, lowerTester, trace):
    """LL/CIS/PER/BV-02-C [CIS Setup Response Procedure, Peripheral, Reject Response]"""
    return cis_setup_peripheral_rejected(transport, upperTester, lowerTester, trace)


def test_cis_map_update(transport, peripheral, central, trace, bn_c_to_p, nse, sdu_interval_c_to_p):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = sdu_interval_c_to_p,
        ISO_Interval            = int(100 // 1.25),  # 100 ms
        NSE                     = nse,
        Max_PDU_P_To_C          = 0,
        BN_C_To_P               = bn_c_to_p,
        BN_P_To_C               = 0,
    )

    success, initiator, _, (cis_conn_handle,) = \
        state_connected_isochronous_stream(transport, peripheral, central, trace, params)
    if not initiator:
        return success

    acl_handle = initiator.handles[1]
    channel_map_new = 0x1249249249
    success = channelMapUpdate(transport, central, channel_map_new, trace) and success

    instant_to = initiator.prevInterval * 10  # TODO: calculate based on LL_CHANNEL_MAP_IND PDU instant
    transport.wait(instant_to)
    status, handle, channel_map = le_read_channel_map(transport, peripheral, acl_handle, 100)
    success = getCommandCompleteEvent(transport, peripheral, trace) and status == 0x00 and handle == acl_handle and success

    success = channel_map == channel_map_new and success

    # 4. The Lower Tester sends data packets to the IUT.
    for pkt_seq_num in range(50):
        success = iso_send_payload_pdu(transport, central, peripheral, trace, cis_conn_handle,
                                       params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, pkt_seq_num) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-03-C [CIS Map Update]
"""
def ll_cis_per_bv_03_c(transport, upper_tester, lower_tester, trace):
    return test_cis_map_update(transport, upper_tester, lower_tester, trace, 0x01, 0x01, 100000)


"""
    LL/CIS/PER/BV-37-C [CIS Map Update]
"""
def ll_cis_per_bv_37_c(transport, upper_tester, lower_tester, trace):
    return test_cis_map_update(transport, upper_tester, lower_tester, trace, 0x02, 0x02, 50000)


"""
    LL/CIS/PER/BV-05-C [Receiving data in Unidirectional CIS]
"""
def ll_cis_per_bv_05_c(transport, upperTester, lowerTester, trace):
    # Establish Initial Condition
    #
    # Connected in the relevant role as defined in the following initial states:
    #
    # Note: “default” refers to values specified in Section 4.10.1.3 Default Values for Set CIG Parameters
    # Commands.

    # Table 4.126: State Variable Values
    # NOTE: As the IUT is the Peripheral, the CIG Parameters are those of the Central
    max_cis_nse = get_ixit_value(transport, upperTester, IXITS["TSPX_max_cis_nse"], 100)

    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 50000,  # 50 ms
        SDU_Interval_P_To_C     = 50000,  # 50 ms
        ISO_Interval            = int(100 // 1.25), # 100 ms
        NSE                     = min(max_cis_nse, 4),  # Note 1: TSPX_max_cis_nse or 0x04, whichever is less
        # Max_PDU_P_To_C          = 0,  # TODO: Supposed to be 0
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        BN_C_To_P               = 2,
        BN_P_To_C               = 1, # TODO: Supposed to be 0
    )

    success, initiator, _, (cisConnectionHandle,) = \
        state_connected_isochronous_stream(transport, upperTester, lowerTester, trace, params)
    if not initiator:
        return success

    for pkt_seq_num in range(3):
        success = iso_send_payload_pdu(transport, lowerTester, upperTester, trace, cisConnectionHandle,
                                       params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, pkt_seq_num) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def sending_and_receiving_data_complete(transport, central, peripheral, trace, params, cis_handle_pairs, packets_sent):
    # Fetch all the EDTT Write ISO Data command responses
    success = le_iso_data_write_complete(transport, peripheral, trace, len(packets_sent[peripheral]), 100)
    success = le_iso_data_write_complete(transport, central, trace, len(packets_sent[central]), 100) and success

    # Fetch all the HCI Number of Completed Packets events
    _, conn_handles_p, num_packets_p = \
        fetch_number_of_completed_packets(transport, peripheral, trace, len(packets_sent[peripheral]),
                                          params.SDU_Interval_P_To_C)
    success = len(packets_sent[peripheral]) == sum(num_packets_p) and success

    _, conn_handles_c, num_packets_c = \
        fetch_number_of_completed_packets(transport, central, trace, len(packets_sent[central]),
                                          params.SDU_Interval_C_To_P)
    success = len(packets_sent[central]) == sum(num_packets_c) and success

    def cis_handle_central(cis_handle_peripheral):
        for handle_p, handle_c in cis_handle_pairs:
            if handle_p == cis_handle_peripheral:
                return handle_c
        return -1

    def cis_handle_peripheral(cis_handle_central):
        for handle_p, handle_c in cis_handle_pairs:
            if handle_c == cis_handle_central:
                return handle_p
        return -1

    # Fetch and verify the payloads received
    for _ in range(len(packets_sent[peripheral])):
        s, cis_handle_c, payload = iso_receive_sdu(transport, central, trace, params.SDU_Interval_P_To_C)
        cis_handle_p = cis_handle_peripheral(cis_handle_c)
        if (cis_handle_p, payload) in packets_sent[peripheral]:
            packets_sent[peripheral].remove((cis_handle_p, payload))
            success = success and s
        else:
            success = False

    for _ in range(len(packets_sent[central])):
        s, cis_handle_p, payload = iso_receive_sdu(transport, peripheral, trace, params.SDU_Interval_C_To_P)
        cis_handle_c = cis_handle_central(cis_handle_p)
        if (cis_handle_c, payload) in packets_sent[central]:
            packets_sent[central].remove((cis_handle_c, payload))
            success = success and s
        else:
            success = False

    return success


def test_sending_and_receiving_data_in_multiple_cises(transport, central, peripheral, trace, params,
                                                      num_iso_data_packets_per_cis, send_delay_c=0,
                                                      adjust_conn_interval=False):
    success, initiator, peripheral_cis_handles, central_cis_handles = \
        state_connected_isochronous_stream(transport, peripheral, central, trace, params,
                                           adjust_conn_interval=adjust_conn_interval)
    if not initiator:
        return success

    cis_handle_pairs = tuple(zip(peripheral_cis_handles, central_cis_handles))

    s, _, _, peripheral_iso_buffer_len, _ = readBufferSizeV2(transport, peripheral, trace)
    success = s and success
    s, _, _, central_iso_buffer_len, _ = readBufferSizeV2(transport, central, trace)
    success = s and success

    # Repeat all steps 3 times
    for round_num in range(3):
        packets_sent = {
            peripheral: [],
            central: [],
        }

        for i in range(num_iso_data_packets_per_cis):
            pkt_seq_num = round_num * num_iso_data_packets_per_cis + i
            for j in range(len(peripheral_cis_handles)):
                s, sdu = le_iso_data_write_nbytes(transport, peripheral, trace, peripheral_cis_handles[j],
                                                  params.Max_SDU_P_To_C[j], pkt_seq_num, peripheral_iso_buffer_len)
                success = s and success
                packets_sent[peripheral].append((peripheral_cis_handles[j], sdu))

            if send_delay_c:
                # wait some time so that ISO event begins with central's Null PDU
                transport.wait(send_delay_c)

            for j in range(len(central_cis_handles)):
                s, sdu = le_iso_data_write_nbytes(transport, central, trace, central_cis_handles[j],
                                                  params.Max_SDU_C_To_P[j], pkt_seq_num, central_iso_buffer_len)
                success = s and success
                packets_sent[central].append((central_cis_handles[j], sdu))

        success = sending_and_receiving_data_complete(transport, central, peripheral, trace, params, cis_handle_pairs,
                                                      packets_sent) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def test_sending_and_receiving_data_in_bidirectional_cis(transport, central, peripheral, trace, enc_keys=None):
    # Establish Initial Condition
    #
    # Connected in the relevant role as defined in the following initial states.
    # Note 2: TSPX_max_cis_bn, or 0x03, whichever is less.
    cis_nse = min(0x06, get_ixit_value(transport, peripheral, IXITS["TSPX_max_cis_nse"], 100))
    cis_bn = min(0x03, get_ixit_value(transport, peripheral, IXITS["TSPX_max_cis_bn"], 100))

    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 100000,  # 100 ms
        SDU_Interval_P_To_C     = 100000,  # 100 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(300 // 1.25),  # 300 ms
        NSE                     = cis_nse,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = cis_bn,
        BN_P_To_C               = cis_bn,
    )

    success, initiator, (peripheral_cis_handle,), (central_cis_handle,) = \
        state_connected_isochronous_stream(transport, peripheral, central, trace, params, enc_keys=enc_keys)
    if not initiator:
        return success

    cis_handle_pairs = ((peripheral_cis_handle, central_cis_handle),)

    s, _, _, peripheral_iso_buffer_len, _ = readBufferSizeV2(transport, peripheral, trace)
    success = s and success
    s, _, _, central_iso_buffer_len, _ = readBufferSizeV2(transport, central, trace)
    success = s and success

    for round_num in range(cis_bn):
        if not success:
            break

        packets_sent = {
            peripheral: [],
            central: [],
        }

        success = True
        s, sdu = le_iso_data_write_nbytes(transport, central, trace, central_cis_handle, params.Max_SDU_C_To_P[0],
                                          round_num, central_iso_buffer_len)
        success = s and success
        packets_sent[central].append((central_cis_handle, sdu))

        s, sdu = le_iso_data_write_nbytes(transport, peripheral, trace, peripheral_cis_handle, params.Max_SDU_P_To_C[0],
                                          round_num, peripheral_iso_buffer_len)
        success = s and success
        packets_sent[peripheral].append((peripheral_cis_handle, sdu))

        success = sending_and_receiving_data_complete(transport, central, peripheral, trace, params, cis_handle_pairs,
                                                      packets_sent) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-06-C [Sending and Receiving Data in Bidirectional CIS]
"""
def ll_cis_per_bv_06_c(transport, upper_tester, lower_tester, trace):
    return test_sending_and_receiving_data_in_bidirectional_cis(transport, lower_tester, upper_tester, trace)


"""
    LL/CIS/PER/BV-27-C [Sending and Receiving Data in Bidirectional CIS]
"""
def ll_cis_per_bv_27_c(transport, upper_tester, lower_tester, trace):
    return test_sending_and_receiving_data_in_bidirectional_cis(transport, lower_tester, upper_tester, trace, ENC_KEYS)


"""
    LL/CIS/PER/BV-07-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG,
                        Peripheral]
"""
def ll_cis_per_bv_07_c(transport, upper_tester, lower_tester, trace):
    # Establish Initial Condition
    #
    # State: Connected Isochronous Stream, Peripheral
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)

    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 50000,  # 50 ms
        SDU_Interval_P_To_C     = 50000,  # 50 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(100 // 1.25),  # 100 ms
        Packing                 = 1,
        CIS_Count               = 2,
        NSE                     = min(max_cis_nse, 4),  # Note 1: TSPX_max_cis_nse or 0x04, whichever is less
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 2,
        BN_P_To_C               = 2,
    )

    # The Lower Tester sends Null PDU to the IUT on CISes first, so lets wait a specific time prior sending ISO Data PDU
    lower_tester_send_delay = int(params.ISO_Interval / (params.NSE[0] + params.NSE[1])) + 1

    success = test_sending_and_receiving_data_in_multiple_cises(transport, lower_tester, upper_tester, trace, params, 2,
                                                                lower_tester_send_delay)

    return success


def cis_updating_peer_clock_accuracy(transport, upper_tester, lower_tester, trace, role):
    assert role == "Peripheral" or role == "Central"
    # Initial Condition

    # Connected in the relevant role.
    # An ACL connection has been established between the IUT and Lower Tester with a valid Connection Handle.
    if role == "Peripheral":
        success, advertiser, initiator = establish_acl_connection(transport, lower_tester, upper_tester, trace)
        acl_conn_handle = initiator.handles[1]
    else:
        success, advertiser, initiator = establish_acl_connection(transport, upper_tester, lower_tester, trace)
        acl_conn_handle = initiator.handles[0]

    # 1. The Upper Tester sends an HCI_LE_Request_Peer_SCA command to the IUT with a valid Connection_Handle.
    # 2. The Upper Tester receives the Command Status event.
    status = le_request_peer_sca(transport, upper_tester, acl_conn_handle, 100)
    s = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace)
    success = s and status == 0x00 and success

    # 3. The Lower Tester receives an LL_CLOCK_ACCURACY_REQ PDU from the IUT and sends back an LL_CLOCK_ACCURACY_RSP
    #    PDU with a valid Peer_Clock_Accuracy value.
    # 4. The Upper Tester receives an HCI_LE_Request_Peer_SCA_Complete event with the Connection_Handle of the ACL and
    #    the Peer_Clock_Accuracy parameter when the HCI_LE_Request_Peer_SCA command completes.
    s, event = verifyAndFetchMetaEvent(transport, upper_tester, MetaEvents.BT_HCI_EVT_LE_REQUEST_PEER_SCA_COMPLETE,
                                       trace, 1000)
    status, conn_handle, peer_clock_accuracy = event.decode()
    success = s and status == 0x00 and conn_handle == acl_conn_handle and success

    # 5. The Upper Tester sends an HCI_LE_Request_Peer_SCA command to the IUT with a valid, but
    # non-existent Connection_Handle and receives the error code Unknown Connection Identifier
    # (0x02).
    acl_conn_handle_non_existent = (acl_conn_handle + 1) % 0x0EFF
    status = le_request_peer_sca(transport, upper_tester, acl_conn_handle_non_existent, 100)
    s = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace)
    success = s and status == 0x02 and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_per_bv_18_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/PER/BV-18-C [CIS Updating Peer Clock Accuracy]"""
    return cis_updating_peer_clock_accuracy(transport, upper_tester, lower_tester, trace, "Peripheral")


"""
    LL/CIS/PER/BV-08-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Sequential,
                        Peripheral]
"""
def ll_cis_per_bv_08_c(transport, upper_tester, lower_tester, trace):
    # Establish Initial Condition
    #
    # State: Connected Isochronous Stream, Peripheral
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)

    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 100000,  # 100 ms
        SDU_Interval_P_To_C     = 50000,  # 50 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(100 // 1.25),  # 100 ms
        Packing                 = 0,  # Sequential
        CIS_Count               = 2,
        NSE                     = min(max_cis_nse, 4),  # Note 1: TSPX_max_cis_nse or 0x04, whichever is less
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 2,
    )

    # The Lower Tester sends Null PDU to the IUT on CISes first, so lets wait a specific time prior sending ISO Data PDU
    lower_tester_send_delay = int(params.ISO_Interval / (params.NSE[0] + params.NSE[1])) + 1

    success = test_sending_and_receiving_data_in_multiple_cises(transport, lower_tester, upper_tester, trace, params, 2,
                                                                lower_tester_send_delay)

    return success


"""
    LL/CIS/PER/BV-19-C [CIS Setup Response Procedure, Peripheral]
"""
def ll_cis_per_bv_19_c(transport, upperTester, lowerTester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 11250, # 11.25 ms
        SDU_Interval_P_To_C     = 11250, # 11.25 ms
        ISO_Interval            = int(11.25 // 1.25), # 11.25 ms
        NSE                     = 4,
        Max_PDU_C_To_P          = 200,
        Max_PDU_P_To_C          = 200,
        PHY_C_To_P              = 2,
        PHY_P_To_C              = 2,
        FT_C_To_P               = 3,
        FT_P_To_C               = 2,
        BN_C_To_P               = 3,
        BN_P_To_C               = 1,
    )

    return cis_setup_response_procedure_peripheral(transport, upperTester, lowerTester, trace, params)


"""
    LL/CIS/PER/BV-22-C [CIS Request Event Not Set]
"""
def ll_cis_per_bv_22_c(transport, upper_tester, lower_tester, trace):
    # Initial Condition
    #
    # The Isochronous Channels (Host Support) FeatureSet bit is clear.
    success = set_isochronous_channels_host_support(transport, upper_tester, trace, 0)
    success = set_isochronous_channels_host_support(transport, lower_tester, trace, 1) and success

    # An ACL connection has been established between the IUT and Lower Tester with the IUT acting as the Peripheral.
    s, advertiser, initiator = establish_acl_connection(transport, lower_tester, upper_tester, trace)
    success = s and success
    if not initiator:
        return success

    # 1. The Lower Tester sends an LL_CIS_REQ to the IUT with the contents specified per Section 4.10.1.3 Default Values
    #    for Set CIG Parameters Commands.
    params = SetCIGParameters()

    status, cig_id, cis_count, cis_conn_handle = \
        le_set_cig_parameters_test(transport, lower_tester, 0, *params.get_cig_parameters_test(), 100)
    success = getCommandCompleteEvent(transport, lower_tester, trace) and status == 0x00 and success

    def lt_send_ll_cis_req(acl_conn_handle):
        status = le_create_cis(transport, lower_tester, cis_count, cis_conn_handle, [acl_conn_handle] * cis_count, 100)
        return verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and status == 0 and success

    success = lt_send_ll_cis_req(initiator.handles[0]) and success

    # 2. The IUT responds to the Lower Tester with an LL_REJECT_EXT_IND with error code Unsupported Remote Feature
    #    (0x1A).
    s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace)
    success = s and (event.decode()[0] == 0x1A) and success

    # 3. The IUT disconnects the ACL connection from the Lower Tester.
    # TSE ID: 17099: Core does not mandate IUT to disconnect ACL when CIS Request has been rejected.
    #                The TS shall be clear that, it is Upper Tester initiated operation, not autonomous IUT operation.
    status = disconnect(transport, upper_tester, initiator.handles[1], 0x13, 200)
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and (status == 0) \
              and success

    s, event = verifyAndFetchEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and (status == 0x00) and handle == initiator.handles[0] and success

    s, event = verifyAndFetchEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and (status == 0x00) and handle == initiator.handles[1] and success

    # 4. The Upper Tester sends an HCI_LE_Set_Host_Feature command to the IUT with the Bit_Number set to 32 (Isochronous
    #    Channels) and the Bit_Value set to 0b1. The Upper Tester receives an HCI_Command_Complete event from the IUT.
    success = set_isochronous_channels_host_support(transport, upper_tester, trace, 1) and success

    # 5. The IUT establishes an ACL connection with the Lower Tester as Peripheral.
    s, advertiser, initiator = establish_acl_connection(transport, lower_tester, upper_tester, trace)
    success = s and success
    if not initiator:
        return success

    # 6. The Upper Tester sends an HCI_LE_Set_Event_Mask command with all events enabled except the HCI_LE_CIS_Request
    #    event. The IUT sends a successful HCI_Command_Complete in response.
    def ut_set_event_mask(event_mask):
        status = le_set_event_mask(transport, upper_tester, event_mask, 100)
        return getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x00

    success = ut_set_event_mask([0xFF, 0xFF, 0xFF, 0xFD, 0x07, 0x00, 0x00, 0x00]) and success

    # 7. Repeat step 1.
    success = lt_send_ll_cis_req(initiator.handles[0]) and success

    # 8. The IUT responds to the Lower Tester with an LL_REJECT_EXT_IND with a valid reason code.
    s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace)
    success = s and (event.decode()[0] != 0x00) and success

    # 9. The Upper Tester does not receive an HCI_LE_CIS_Request event. Confirm for at least 5 seconds.
    s, _, _, _, _ = hasLeCisRequestMetaEvent(transport, upper_tester, trace, 5000)
    success = not s and success

    # 10. The Upper Tester sends an HCI_LE_Set_Event_Mask command with all events enabled including the
    #     HCI_LE_CIS_Request event. The IUT sends a successful HCI_Command_Complete in response.
    success = ut_set_event_mask([0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00]) and success

    # 11. Repeat step 1.
    success = lt_send_ll_cis_req(initiator.handles[0]) and success

    # 12. The Upper Tester receives an HCI_LE_CIS_Request event.
    s, _, _, _, _ = hasLeCisRequestMetaEvent(transport, upper_tester, trace, 5000)
    success = s and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-23-C [CIS Setup Response Procedure, Peripheral]
"""
def ll_cis_per_bv_23_c(transport, upper_tester, lower_tester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 11250,  # 11.25 ms
        SDU_Interval_P_To_C     = 11250,  # 11.25 ms
        ISO_Interval            = int(11.25 // 1.25),  # 11.25 ms
        NSE                     = 4,
        Max_PDU_C_To_P          = 100,
        Max_PDU_P_To_C          = 100,
        PHY_C_To_P              = 2,
        PHY_P_To_C              = 1,
        FT_C_To_P               = 3,
        FT_P_To_C               = 2,
        BN_C_To_P               = 3,
        BN_P_To_C               = 2,
    )

    return cis_setup_response_procedure_peripheral(transport, upper_tester, lower_tester, trace, params)


"""
    LL/CIS/PER/BV-29-C [CIS Setup Response Procedure, Peripheral]
"""
def ll_cis_per_bv_29_c(transport, upper_tester, lower_tester, trace):
    params = SetCIGParameters(
        Max_PDU_C_To_P          = 128,
        Max_PDU_P_To_C          = 128,
        PHY_C_To_P              = 2,
        PHY_P_To_C              = 2,
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 1,
    )

    return cis_setup_response_procedure_peripheral(transport, upper_tester, lower_tester, trace, params)


"""
    LL/CIS/PER/BV-31-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG,
                        Peripheral, NSE=2]
"""
def ll_cis_per_bv_31_c(transport, upper_tester, lower_tester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 50000,  # 50 ms
        SDU_Interval_P_To_C     = 50000,  # 50 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(100 // 1.25),  # 100 ms
        Packing                 = 1,
        CIS_Count               = 2,
        NSE                     = 2,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 2,
        BN_P_To_C               = 2,
    )

    success = test_sending_and_receiving_data_in_multiple_cises(transport, lower_tester, upper_tester, trace, params, 2)

    return success


"""
    LL/CIS/PER/BV-32-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Peripheral, BN=1]
"""
def ll_cis_per_bv_32_c(transport, upper_tester, lower_tester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 50000,  # 50 ms
        SDU_Interval_P_To_C     = 50000,  # 50 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(50 // 1.25),  # 50 ms
        Packing                 = 1,  # Interleaved
        CIS_Count               = 2,
        NSE                     = 1,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 1,
    )

    success = test_sending_and_receiving_data_in_multiple_cises(transport, lower_tester, upper_tester, trace, params, 1)

    return success


def test_sending_and_receiving_data_in_bidirectional_cis_bn_1(transport, central, peripheral, trace, enc_keys=None):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 100000,  # 100 ms
        SDU_Interval_P_To_C     = 100000,  # 100 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(100 // 1.25),  # 100 ms
        NSE                     = 1,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 1,
    )

    success, initiator, (peripheral_cis_handle,), (central_cis_handle,) = \
        state_connected_isochronous_stream(transport, peripheral, central, trace, params, enc_keys=enc_keys)
    if not initiator:
        return success

    # The SDU is equal for both devices
    assert params.Max_SDU_C_To_P[0] == params.Max_SDU_P_To_C[0]

    iso_data_sdu = tuple([0xD7] * params.Max_SDU_C_To_P[0])

    success = iso_send_payload_pdu(transport, peripheral, central, trace, peripheral_cis_handle,
                                   params.Max_SDU_P_To_C[0], params.SDU_Interval_P_To_C, 0, iso_data_sdu) and success

    success = iso_send_payload_pdu(transport, central, peripheral, trace, central_cis_handle,
                                   params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, 0, iso_data_sdu) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-35-C [Sending and Receiving Data in Bidirectional CIS, BN = 1]
"""
def ll_cis_per_bv_35_c(transport, upper_tester, lower_tester, trace):
    return test_sending_and_receiving_data_in_bidirectional_cis_bn_1(transport, lower_tester, upper_tester, trace)


"""
    LL/CIS/PER/BV-36-C [Sending and Receiving Data in Bidirectional CIS, BN = 1, Encrypted]
"""
def ll_cis_per_bv_36_c(transport, upper_tester, lower_tester, trace):
    return test_sending_and_receiving_data_in_bidirectional_cis_bn_1(transport, lower_tester, upper_tester, trace,
                                                                     ENC_KEYS)


"""
    LL/CIS/PER/BV-33-C [Sending Data in Unidirectional CIS, BN = 1, Peripheral]
"""
def ll_cis_per_bv_33_c(transport, upper_tester, lower_tester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 100000,  # 100 ms
        SDU_Interval_P_To_C     = 100000,  # 100 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(100 // 1.25),  # 100 ms
        CIS_Count               = 1,
        NSE                     = 1,
        Max_PDU_C_To_P          = 0,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 0,
        BN_P_To_C               = 1,
    )

    success, initiator, (cis_conn_handle,), _ = \
        state_connected_isochronous_stream(transport, upper_tester, lower_tester, trace, params)
    if not initiator:
        return success

    success = iso_send_payload_pdu(transport, upper_tester, lower_tester, trace, cis_conn_handle,
                                   params.Max_SDU_P_To_C[0], params.SDU_Interval_P_To_C, 0) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-34-C [Receiving Data in Unidirectional CIS, BN = 1, Peripheral]
"""
def ll_cis_per_bv_34_c(transport, upper_tester, lower_tester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 100000,  # 100 ms
        SDU_Interval_P_To_C     = 100000,  # 100 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = int(100 // 1.25),  # 100 ms
        CIS_Count               = 1,
        NSE                     = 1,
        Max_PDU_P_To_C          = 0,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 0,
    )

    success, initiator, _, (cis_conn_handle,) = \
        state_connected_isochronous_stream(transport, upper_tester, lower_tester, trace, params)
    if not initiator:
        return success

    success = iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, cis_conn_handle,
                                   params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, 0) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-39-C [CIS Peripheral Accepts All Supported NSE Values]
"""
def ll_cis_per_bv_39_c(transport, upper_tester, lower_tester, trace):
    # Initial Condition
    #
    # The Isochronous Channels (Host Support) FeatureSet bit is set.
    success = set_isochronous_channels_host_support(transport, upper_tester, trace, 1)
    success = set_isochronous_channels_host_support(transport, lower_tester, trace, 1) and success

    # An ACL connection has been established between the IUT and the Lower Tester with a valid Connection Handle.
    s, advertiser, initiator = establish_acl_connection(transport, lower_tester, upper_tester, trace)
    success = s and success
    if not initiator:
        return success

    max_sdu_length = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_sdu_length"], 100)
    max_cis_bn = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_bn"], 100)

    # 1. Test_BN represents the BN and NSE values for each round, and its initial value is 1.
    test_bn = 1
    while True:
        # 2. The Lower Tester sends an LL_CIS_REQ to the IUT with BN_C_To_P, BN_P_To_C, and NSE set to Test_BN.
        #    FT_C_To_P and FT_P_To_C are 1. ISO_Interval is a valid value and at least 200 ms. All other values are
        #    valid values, and Max_SDU_C_To_P and Max_SDU_P_To_C are no greater than TSPX_max_sdu_length.
        #    The parameters are configured such that each PDU contains a single SDU.
        max_pdu = min(64, max_sdu_length)

        # BN = ceil(Max_SDU / Max_PDU) * (ISO_Interval / SDU_Interval), where Max_SDU == Max_PDU, thus
        iso_interval = 200
        r = iso_interval % test_bn
        if r != 0:
            iso_interval += (test_bn - r)

        sdu_interval = int(iso_interval / test_bn)

        params = SetCIGParameters(
            SDU_Interval_C_To_P = sdu_interval * 1000,
            SDU_Interval_P_To_C = sdu_interval * 1000,
            ISO_Interval        = int(iso_interval // 1.25),
            NSE                 = test_bn,  # NSE set to Test_BN
            Max_PDU_C_To_P      = max_pdu,
            Max_PDU_P_To_C      = max_pdu,
            FT_C_To_P           = 1,
            FT_P_To_C           = 1,
            BN_C_To_P           = test_bn,
            BN_P_To_C           = test_bn,
            Max_SDU_Supported   = max_pdu,  # Each PDU contains a single SDU
        )

        # 3-10.
        s, (lower_tester_cis_handle,), (upper_tester_cis_handle,) = \
            establish_cis_connection(transport, lower_tester, upper_tester, trace, params, initiator.handles[0])
        success = s and success
        if not initiator:
            return success

        def iso_send_and_receive_payload_pdu(pkt_seq_num):
            # 11. The Lower Tester sends data packets to the IUT. All data packets contain data , and there are no
            #     zero length data packets.
            # 12. The Upper Tester receives ISO data from the IUT.
            success = iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, lower_tester_cis_handle,
                                           params.Max_PDU_C_To_P[0], params.SDU_Interval_C_To_P, pkt_seq_num)

            # 13. The Upper Tester sends ISO data to the IUT sufficient to ensure that all data PDUs contain data
            #     and there are no zero length PDUs.
            # 14. The Lower Tester receives ISO data from the IUT.
            return iso_send_payload_pdu(transport, upper_tester, lower_tester, trace, upper_tester_cis_handle,
                                        params.Max_PDU_P_To_C[0], params.SDU_Interval_P_To_C, pkt_seq_num) and success

        # 15. Repeat steps 11–15 for 20 ISO intervals.
        for pkt_seq_num in range(20):
            success = iso_send_and_receive_payload_pdu(pkt_seq_num) and success
            if not success:
                break

        # 16. The Lower Tester sends an LL_CIS_TERMINATE_IND PDU to the IUT and receives an Ack from
        # the IUT.
        # LT - Initiate CIS Disconnection and verify command status
        status = disconnect(transport, lower_tester, lower_tester_cis_handle, 0x13, 200)
        success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and (status == 0) \
                  and success

        # LT - Verify HCI Disconnection Complete event parameters
        s, event = verifyAndFetchEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
        status, handle, reason = event.decode()
        success = s and (status == 0x00) and handle == lower_tester_cis_handle and success

        # 17. The Upper Tester receives an HCI_Disconnection_Complete event from the IUT.
        s, event = verifyAndFetchEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
        status, handle, reason = event.decode()
        success = s and (status == 0x00) and handle == upper_tester_cis_handle and success

        # Remove the CIG, since we are going to create the CIG with different parameters
        status, _ = le_remove_cig(transport, lower_tester, 0, 100)
        success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_COMPLETE, trace) and status == 0 \
                  and success

        # 18. Test_BN is incremented by 1. If Test_BN exceeds TSPX_max_cis_bn, then the test is complete.
        #     If not, go to step 2 to execute the next round.
        test_bn += 1
        if test_bn > max_cis_bn or not success:
            break

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


"""
    LL/CIS/PER/BV-40-C [CIS Setup Response Procedure, Peripheral]
"""
def ll_cis_per_bv_40_c(transport, upper_tester, lower_tester, trace):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 7500,  # 7.5 ms
        SDU_Interval_P_To_C     = 7500,  # 7.5 ms
        ISO_Interval            = int(7.5 // 1.25),  # 7.5
        NSE                     = 2,
        Max_PDU_C_To_P          = 160,
        Max_PDU_P_To_C          = 0,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 0,
    )

    return cis_setup_response_procedure_peripheral(transport, upper_tester, lower_tester, trace, params)


def cis_terminate_procedure_initiated(transport, upper_tester, lower_tester, trace, role, packets):
    assert role == "Peripheral" or role == "Central"
    params = SetCIGParameters(
            SDU_Interval_C_To_P     = 20000,
            SDU_Interval_P_To_C     = 20000,
            ISO_Interval            = int(20 // 1.25),
            NSE                     = 1,
            Max_SDU_C_To_P          = 130,
            Max_SDU_P_To_C          = 130,
            Max_PDU_C_To_P          = 130,
            Max_PDU_P_To_C          = 130,
            PHY_C_To_P              = 1,
            PHY_P_To_C              = 1,
            FT_C_To_P               = 1,
            FT_P_To_C               = 1,
            BN_C_To_P               = 1,
            BN_P_To_C               = 1,
    )

    if role == "Peripheral":
        success, initiator, (upper_tester_cis_handle,), (lower_tester_cis_handle,) = \
            state_connected_isochronous_stream(transport, upper_tester, lower_tester, trace, params)
    else:
        success, initiator, (lower_tester_cis_handle,), (upper_tester_cis_handle,) = \
            state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params)

    if not initiator:
        return success

    # Test procedure
    # 1. A payload PDU and Ack is sent between the IUT and Lower Tester
    success = iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, lower_tester_cis_handle,
                                   params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, 0) and success

    # 2. If IUT is Peripheral, skip to step 5.
    if role == "Central":
        # 3. The Upper Tester sends an HCI_LE_Remove_CIG command with the CIG_ID value obtained when the CIG was
        #       established.
        status, _ = le_remove_cig(transport, upper_tester, 0, 100)  # CIG_ID is hardcoded to 0
        # 4. The Upper Tester receives an HCI_Command_Complete event with error code Command Disallowed (0x0C).
        success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_COMPLETE, trace) and success
        success = status == 0x0C and success

    # 5. The Upper Tester sends an HCI_Disconnect to the IUT and receives HCI_Command_status IUT.
    reason_code = 0x13
    status = disconnect(transport, upper_tester, upper_tester_cis_handle, reason_code, 200)
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and (status == 0) and success

    # 6. The Lower Tester receives an LL_CIS_TERMINATE_IND PDU from the IUT and the ErrorCode
    #        field in the CtrData field matches the Reason code value the Upper Tester sent in step 45.
    def check_ll_cis_terminate_ind():
        packet = packets.find('LL_CIS_TERMINATE_IND')
        return packet and packet.payload.CtrData.ErrorCode == reason_code
    # 7. The Lower Tester sends an Ack to the IUT.

    # 8. The Upper Tester receives an HCI_Disconnection_Complete event from the IUT.
    s, event = verifyAndFetchEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and (status == 0x00) and handle == upper_tester_cis_handle and success

    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace) and success

    # 9. If IUT is Central, proceed to step 10. If IUT is Peripheral, test is complete.
    if role == "Central":
        # 10. The Upper Tester sends an HCI_LE_Create_CIS command to the IUT with the CIS Connection_Handle in step 5
        #       and receives a successful HCI_Command_Status event from the IUT in response.
        success = le_create_cis(transport, upper_tester, 1, [upper_tester_cis_handle], [initiator.handles[0]], 100) == 0
        success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success
        # 11. The IUT sends an LL_CIS_REQ PDU to the Lower Tester with all fields set to valid values.
        # 12. The Lower Tester sends an LL_CIS_RSP PDU to the IUT.
        # 13. The IUT sends an LL_CIS_IND PDU to the Lower Tester.
        # 14. The IUT sends a Null ISO Data packet to the Lower Tester.
        # 15. The Lower Tester sends an LL ACK to the IUT.
        # LT: Wait for HCI_EVT_LE_CIS_REQUEST
        s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_REQUEST, trace)
        success = s and success
        lower_tester_cis_handle = event.decode()[1]

        # LT: Accept CIS Request
        success = le_accept_cis_request(transport, lower_tester, lower_tester_cis_handle, 100) == 0 and success
        success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

        # 16. The IUT sends an HCI_LE_CIS_Established event to the Upper Tester with the CIS_Connection_Handle set to
        #       the value in step 10.
        s, event = verifyAndFetchMetaEvent(transport, upper_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace, 2000)
        success = s and (event.decode()[0] == 0x00) and (event.decode()[1] == upper_tester_cis_handle) and success

        # LT: Wait for HCI_EVT_LE_CIS_ESTABLISHED
        s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace)
        success = s and (event.decode()[0] == 0x00) and success

        # 17. The Upper Tester begins providing data to the IUT to send to the Lower Tester.
        # 18. The IUT sends the data to the Lower Tester.
        # 19. The Lower Tester sends isochronous data to the IUT.
        # 20. The IUT provides the Lower Tester’s isochronous data to the Upper Tester.
        success = iso_send_payload_pdu_parallel(transport, upper_tester, lower_tester, trace, upper_tester_cis_handle,
                                                lower_tester_cis_handle, params.Max_SDU_C_To_P[0],
                                                params.SDU_Interval_C_To_P, 0) and success

    ### LL VERIFICATION ###
    success = check_ll_cis_terminate_ind() and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_per_bv_12_c(transport, upper_tester, lower_tester, trace, packets):
    """LL/CIS/PER/BV-12-C [Cis Terminate Procedure, Initiated - Peripheral]"""
    return cis_terminate_procedure_initiated(transport, upper_tester, lower_tester, trace, "Peripheral", packets)


def cis_terminate_procedure_accepting(transport, upper_tester, lower_tester, trace, role):
    assert role == "Peripheral" or role == "Central"
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 20000,  # 20 ms
        SDU_Interval_P_To_C     = 20000,  # 20 ms
        ISO_Interval            = int(20 // 1.25),  # 20 ms
        NSE                     = 1,
        Max_PDU_C_To_P          = 130,
        Max_PDU_P_To_C          = 130,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 1,
    )

    if role == "Peripheral":
        success, initiator, (upper_tester_cis_handle,), (lower_tester_cis_handle,) = \
            state_connected_isochronous_stream(transport, upper_tester, lower_tester, trace, params)
    else:
        success, initiator, (lower_tester_cis_handle,), (upper_tester_cis_handle,) = \
            state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params)

    if not initiator:
        return success

    # Test Procedure
    # 1. A payload PDU and Ack is sent between the IUT and Lower Tester.
    success = iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, lower_tester_cis_handle,
                                   params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, 0) and success

    # 2. The Lower Tester sends an LL_CIS_TERMINATE_IND PDU to the IUT and receives an Ack from the IUT.
    # LT - Initiate CIS Disconnection and verify command status
    status = disconnect(transport, lower_tester, lower_tester_cis_handle, 0x13, 200)
    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and (status == 0) \
              and success

    # LT - Verify HCI Disconnection Complete event parameters
    s, event = verifyAndFetchEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and (status == 0x00) and handle == lower_tester_cis_handle and success

    # 3. The Upper Tester receives an HCI_Disconnection_Complete event from the IUT.
    s, event = verifyAndFetchEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and (status == 0x00) and handle == upper_tester_cis_handle and success

    # Pass verdict:
    # - In step 2, the IUT sends an Ack.
    # TODO: Verify Ack

    # - In step 3, the IUT sends an HCI_Disconnection_Complete event to the Upper Tester.
    # PASS

    # - The Lower Tester does not receive any payload PDUs from the IUT after step 3.
    success = not le_iso_data_ready(transport, lower_tester, 100) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_per_bv_13_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/PER/BV-13-C [CIS Terminate Procedure, Accepting, Peripheral]"""
    return cis_terminate_procedure_accepting(transport, upper_tester, lower_tester, trace, "Peripheral")


def iso_test_mode_common_procedure(transport, transmitter, receiver, trace, transmitter_cis_handle, receiver_cis_handle,
                                   payload_type):
    # 1. The Transmitter sends the HCI_LE_ISO_Transmit_Test command with PayloadType as specified in Table and
    # receives a successful HCI_Command_Complete event from the IUT in response.
    status, conn_handle = hci_le_iso_transmit_test(transport, transmitter, transmitter_cis_handle, payload_type, 100)
    success = (conn_handle == transmitter_cis_handle)

    success = getCommandCompleteEvent(transport, transmitter, trace) and success and status == 0x00

    # 1.X The Receiver sends the HCI_LE_ISO_Receive_Test command to the IUT with Payload_Type as specified in Table
    # and receives a successful HCI_Command_Complete event from the IUT in response.
    status, conn_handle = hci_le_iso_receive_test(transport, receiver, receiver_cis_handle, payload_type, 100)
    success = (conn_handle == receiver_cis_handle) and success

    success = getCommandCompleteEvent(transport, receiver, trace) and success and status == 0x00

    # 2. The IUT sends isochronous data PDUs with Payload.
    # The controller generates and sends test payloads

    # 3. Repeat step 2 for a total of 5 rounds of payloads.
    received_sdu_count = 0
    missed_sdu_count = 0
    failed_sdu_count = 0
    while ((received_sdu_count + missed_sdu_count + failed_sdu_count) < 5) and success:
        # The Receiver sends the HCI_LE_ISO_Read_Test_Counters command to the IUT.
        status, connection_handle, received_sdu_count, missed_sdu_count, failed_sdu_count = \
            hci_le_iso_read_test_counters_test(transport, receiver, receiver_cis_handle, 100)
        success = getCommandCompleteEvent(transport, receiver, trace) and success and status == 0x00

        # Core Version Sydney r11 | | Vol 6, Part B
        # Because the transmitter and receiver do not enter test mode simultaneously, it is not possible to
        # determine whether the first test SDU received was the first one sent. As a consequence, at the moment the
        # first valid test SDU is received (indicated by either Received_SDU_Count or Failed_SDU_Count being
        # incremented), the value of Missed_SDU_Count is unpredictable. Once a valid test SDU has been received,
        # any further changes in Missed_SDU_Count will be correct.
        if received_sdu_count == 0 and failed_sdu_count == 0:
            missed_sdu_count = 0

    success = received_sdu_count >= 5 and missed_sdu_count == 0 and failed_sdu_count == 0 and success

    # 4. The Receiver sends the HCI_LE_ISO_Test_End command to the IUT and receives an HCI_Command_Status event
    # from the IUT with the Status field set to Success.
    status, connection_handle, _, _, _ = hci_le_iso_test_end(transport, receiver, receiver_cis_handle, 100)
    success = getCommandCompleteEvent(transport, receiver, trace) and success and status == 0x00

    # 4.X The Transmitter sends the HCI_LE_ISO_Test_End command to the IUT and receives an HCI_Command_Status event
    # from the IUT with the Status field set to Success.
    status, connection_handle, _, _, _ = hci_le_iso_test_end(transport, transmitter, transmitter_cis_handle, 100)
    success = getCommandCompleteEvent(transport, transmitter, trace) and success and status == 0x00

    return success, received_sdu_count, missed_sdu_count, failed_sdu_count


def iso_transmit_test_mode_cis(transport, upper_tester, lower_tester, trace, is_upper_tester_central):
    params = SetCIGParameters()

    if is_upper_tester_central:
        success, initiator, (lower_tester_cis_handle,), (upper_tester_cis_handle,) = \
            state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params,
                                               setup_iso_data_path=False)
    else:
        success, initiator, (upper_tester_cis_handle,), (lower_tester_cis_handle,) = \
            state_connected_isochronous_stream(transport, upper_tester, lower_tester, trace, params,
                                               setup_iso_data_path=False)
    if not initiator:
        return success

    testData = namedtuple('testData', 'Name, Payload_Type')
    rounds = [
        testData("Zero length payload", 0x00),
        testData("Variable length payload", 0x01),
        testData("Maximum length payload", 0x02),
    ]
    for name, payload_type in rounds:
        success, received_sdu_count, missed_sdu_count, failed_sdu_count = \
            iso_test_mode_common_procedure(transport, upper_tester, lower_tester, trace, upper_tester_cis_handle,
                                           lower_tester_cis_handle, payload_type)

        trace.trace(5, "%s done, received_sdu_count=%d missed_sdu_count=%d failed_sdu_count=%d" %
                    (name, received_sdu_count, missed_sdu_count, failed_sdu_count))

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_ist_per_bv_01_c(transport, upper_tester, lower_tester, trace):
    """LL/IST/PER/BV-01-C [ISO Transmit Test Mode, CIS]"""
    return iso_transmit_test_mode_cis(transport, upper_tester, lower_tester, trace, False)


def iso_receive_test_mode_cis(transport, upper_tester, lower_tester, trace, is_upper_tester_central):
    def establish_acl_and_cis(framing):
        params = SetCIGParameters(
            Framing                 = framing,
            SDU_Interval_C_To_P     = 400000,  # 400 ms
            SDU_Interval_P_To_C     = 400000,  # 400 ms
            ISO_Interval            = int(400 // 1.25),  # 400 ms
        )

        if is_upper_tester_central:
            success, initiator, (lower_tester_cis_handle,), (upper_tester_cis_handle,) = \
                state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params,
                                                   setup_iso_data_path=False)
        else:
            success, initiator, (upper_tester_cis_handle,), (lower_tester_cis_handle,) = \
                state_connected_isochronous_stream(transport, upper_tester, lower_tester, trace, params,
                                                   setup_iso_data_path=False)

        return success and initiator, initiator, upper_tester_cis_handle, lower_tester_cis_handle

    testData = namedtuple('testData', 'Name, Framing, Payload_Type')
    rounds = [
        testData("Zero length payload unframed", 0, 0x00),
        testData("Variable length payload unframed", 0, 0x01),
        testData("Maximum length payload unframed", 0, 0x02),
        testData("Maximum length payload framed", 1, 0x02),
        testData("Variable length payload framed", 1, 0x01),
    ]
    previous = rounds[0]
    success, initiator, upper_tester_cis_handle, lower_tester_cis_handle = establish_acl_and_cis(previous.Framing)
    for current in rounds:
        # When Framing is changing between rounds, Isochronous link needs to be terminated and re-established with
        # correct Framing, as framing cannot be changed after creation.
        if current.Framing != previous.Framing:
            success = initiator.disconnect(0x13) and success

            if is_upper_tester_central:
                status, _ = le_remove_cig(transport, upper_tester, 0, 100)
                success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x00 and success
            else:
                status, _ = le_remove_cig(transport, lower_tester, 0, 100)
                success = getCommandCompleteEvent(transport, lower_tester, trace) and status == 0x00 and success

            s, initiator, upper_tester_cis_handle, lower_tester_cis_handle = establish_acl_and_cis(current.Framing)
            success = s and success

        s, received_sdu_count, missed_sdu_count, failed_sdu_count = \
            iso_test_mode_common_procedure(transport, lower_tester, upper_tester, trace, lower_tester_cis_handle,
                                           upper_tester_cis_handle, current.Payload_Type)
        success = s and success

        previous = current

        trace.trace(5, "%s done, received_sdu_count=%d missed_sdu_count=%d failed_sdu_count=%d" %
                    (current.Name, received_sdu_count, missed_sdu_count, failed_sdu_count))

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_ist_per_bv_03_c(transport, upper_tester, lower_tester, trace):
    """LL/IST/PER/BV-03-C [ISO Receive Test Mode, CIS]"""
    return iso_receive_test_mode_cis(transport, upper_tester, lower_tester, trace, False)


def cis_setup_procedure_central_initiated(transport, upper_tester, lower_tester, trace, params):
    success, initiator, _, (cis_handle,) = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params)

    def lt_send_data_packet(pkt_seq_num):
        return iso_send_payload_pdu(transport, upper_tester, lower_tester, trace, cis_handle,
                                    params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, pkt_seq_num)

    for j in range(50 // params.BN_C_To_P[0]):
        success = lt_send_data_packet(j) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_cen_bv_01_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-01-C [CIS Setup Procedure, Central Initiated]"""
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)
    params = SetCIGParameters(
        PHY_C_To_P              = 1,
        BN_C_To_P               = 2,
        FT_C_To_P               = 1,
        Max_PDU_C_To_P          = 130,
        PHY_P_To_C              = 1,
        BN_P_To_C               = 2,
        FT_P_To_C               = 1,
        Max_PDU_P_To_C          = 130,
        NSE                     = min(4, max_cis_nse),
        SDU_Interval_C_To_P     = 0x2710,  # 10 ms
        SDU_Interval_P_To_C     = 0x2710,  # 10 ms
        ISO_Interval            = 0x10,  # 20 ms
    )

    return cis_setup_procedure_central_initiated(transport, upper_tester, lower_tester, trace, params)


def ll_cis_cen_bv_02_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-02-C [CIS Setup Procedure, Central Initiated]"""
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)
    params = SetCIGParameters(
        PHY_C_To_P              = 2,
        BN_C_To_P               = 2,
        FT_C_To_P               = 2,
        Max_PDU_C_To_P          = 251,
        PHY_P_To_C              = 2,
        BN_P_To_C               = 2,
        FT_P_To_C               = 1,
        Max_PDU_P_To_C          = 251,
        NSE                     = min(4, max_cis_nse),
        SDU_Interval_C_To_P     = 0x4E20,  # 20 ms
        SDU_Interval_P_To_C     = 0x2710,  # 10 ms
        ISO_Interval            = 0x10,  # 20 ms
    )

    return cis_setup_procedure_central_initiated(transport, upper_tester, lower_tester, trace, params)


def ll_cis_cen_bv_31_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-31-C [CIS Setup Procedure, Central Initiated]"""
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)
    params = SetCIGParameters(
        PHY_C_To_P              = 2,
        BN_C_To_P               = 2,
        FT_C_To_P               = 1,
        Max_PDU_C_To_P          = 130,
        PHY_P_To_C              = 1,
        BN_P_To_C               = 2,
        FT_P_To_C               = 1,
        Max_PDU_P_To_C          = 130,
        NSE                     = min(4, max_cis_nse),
        SDU_Interval_C_To_P     = 0x2710,  # 10 ms
        SDU_Interval_P_To_C     = 0x2710,  # 10 ms
        ISO_Interval            = 0x10,  # 20 ms
    )

    return cis_setup_procedure_central_initiated(transport, upper_tester, lower_tester, trace, params)


def ll_cis_cen_bv_39_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-39-C [CIS Setup Procedure, Central Initiated]"""
    params = SetCIGParameters(
        PHY_C_To_P              = 2,
        BN_C_To_P               = 1,
        FT_C_To_P               = 1,
        Max_PDU_C_To_P          = 130,
        PHY_P_To_C              = 2,
        BN_P_To_C               = 1,
        FT_P_To_C               = 1,
        Max_PDU_P_To_C          = 50,
        NSE                     = 1,
        SDU_Interval_C_To_P     = 0x4E20,  # 20 ms
        SDU_Interval_P_To_C     = 0x4E20,  # 20 ms
        ISO_Interval            = 0x10,  # 20 ms
    )

    return cis_setup_procedure_central_initiated(transport, upper_tester, lower_tester, trace, params)


def ll_cis_cen_bv_03_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-03-C [CIS Setup Procedure, Central Initiated, Rejected]"""
    return cis_setup_peripheral_rejected(transport, lower_tester, upper_tester, trace)


def ll_cis_cen_bv_04_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-04-C [New Channel Map]"""
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)
    return test_cis_map_update(transport, lower_tester, upper_tester, trace, 2, min(3, max_cis_nse), 100000)


def ll_cis_cen_bv_40_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-40-C [New Channel Map]"""
    return test_cis_map_update(transport, lower_tester, upper_tester, trace, 1, 1, 200000)


def ll_cis_cen_bv_07_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-07-C [Sending and Receiving Data in Bidirectional CIS]"""
    return test_sending_and_receiving_data_in_bidirectional_cis(transport, upper_tester, lower_tester, trace)


def ll_cis_cen_bv_35_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-35-C [Sending and Receiving Data in Bidirectional CIS, Encryption]"""
    return test_sending_and_receiving_data_in_bidirectional_cis(transport, upper_tester, lower_tester, trace, ENC_KEYS)


def ll_cis_cen_bv_47_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-47-C [Sending and Receiving Data in Bidirectional CIS, BN = 1]"""
    return test_sending_and_receiving_data_in_bidirectional_cis_bn_1(transport, upper_tester, lower_tester, trace)


def ll_cis_cen_bv_48_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-48-C [Sending and Receiving Data in Bidirectional CIS, BN = 1, Encryption]"""
    return test_sending_and_receiving_data_in_bidirectional_cis_bn_1(transport, upper_tester, lower_tester, trace,
                                                                     ENC_KEYS)


def ll_cis_cen_bv_45_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-45-C [Sending Data in Unidirectional CIS, BN = 1]"""
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 0x186A0,  # 100 ms
        SDU_Interval_P_To_C     = 0x186A0,  # 100 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = 0x50,  # 100 ms
        CIS_Count               = 1,
        NSE                     = 1,
        Max_PDU_C_To_P          = 251,
        Max_PDU_P_To_C          = 0,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 0,
    )

    success, initiator, _, (cis_conn_handle,) = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params)
    if not initiator:
        return success

    success = iso_send_payload_pdu(transport, upper_tester, lower_tester, trace, cis_conn_handle,
                                   params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, 0) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_cen_bv_06_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-06-C [Receiving Data in Unidirectional CIS]"""
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 0xC350,  # 50 ms
        SDU_Interval_P_To_C     = 0xC350,  # 50 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = 0x50,  # 100 ms
        CIS_Count               = 1,
        NSE                     = min(4, max_cis_nse),
        Max_PDU_C_To_P          = 0,
        # Max_PDU_P_To_C          = default,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 0,
        BN_P_To_C               = 2,
    )

    success, initiator, (cis_conn_handle,), _ = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params)
    if not initiator:
        return success

    for seq_num in range(2):
        success = iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, cis_conn_handle,
                                       params.Max_SDU_P_To_C[0], params.SDU_Interval_P_To_C, seq_num) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_cen_bv_46_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-46-C [Receiving Data in Unidirectional CIS, BN = 1]"""
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 0x186A0,  # 100 ms
        SDU_Interval_P_To_C     = 0x186A0,  # 100 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = 0x50,  # 100 ms
        CIS_Count               = 1,
        NSE                     = 1,
        Max_PDU_C_To_P          = 0,
        Max_PDU_P_To_C          = 251,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 0,
        BN_P_To_C               = 1,
    )

    success, initiator, (cis_conn_handle,), _ = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params)
    if not initiator:
        return success

    success = iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, cis_conn_handle,
                                   params.Max_SDU_P_To_C[0], params.SDU_Interval_P_To_C, 0) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def central_send_and_receive_data_in_multi_cises_single_cig_single_conn_interleaved(transport, central, peripheral,
                                                                                    trace, bn, sdu_interval, nse,
                                                                                    adjust_conn_interval=False):
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = sdu_interval,
        SDU_Interval_P_To_C     = sdu_interval,
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = 0x50,  # 100 ms
        Packing                 = 1,  # Interleaved
        CIS_Count               = 2,
        NSE                     = nse,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = bn,
        BN_P_To_C               = bn,
    )

    return test_sending_and_receiving_data_in_multiple_cises(transport, central, peripheral, trace, params, 1,
                                                             adjust_conn_interval=adjust_conn_interval)


def ll_cis_cen_bv_08_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-08-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG,
     Central]"""
    return central_send_and_receive_data_in_multi_cises_single_cig_single_conn_interleaved(
        transport, upper_tester, lower_tester, trace, 2, 0xC350, 4, adjust_conn_interval=True)


def ll_cis_cen_bv_43_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-43-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG,
     Central]"""
    return central_send_and_receive_data_in_multi_cises_single_cig_single_conn_interleaved(
        transport, upper_tester, lower_tester, trace, 1, 0x186A0, 1)


def ll_cis_cen_bv_44_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-44-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG,
     Central, BN > 1, NSE = 2]"""
    return central_send_and_receive_data_in_multi_cises_single_cig_single_conn_interleaved(
        transport, upper_tester, lower_tester, trace, 2, 0x0C350, 2)


def ll_cis_cen_bv_09_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-09-C [Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Sequential,
     Central]"""
    max_cis_nse = get_ixit_value(transport, upper_tester, IXITS["TSPX_max_cis_nse"], 100)
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 0x0C350,  # 50 ms
        SDU_Interval_P_To_C     = 0x186A0,  # 100 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = 0x50,  # 100 ms
        Packing                 = 0,  # Sequential
        CIS_Count               = 2,
        NSE                     = min(max_cis_nse, 4),
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 2,
        BN_P_To_C               = 1,
    )

    return test_sending_and_receiving_data_in_multiple_cises(transport, upper_tester, lower_tester, trace, params, 2,
                                                             adjust_conn_interval=True)


def ll_cis_cen_bv_24_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-24-C [CIS Updating Peer Clock Accuracy]"""
    return cis_updating_peer_clock_accuracy(transport, upper_tester, lower_tester, trace, "Central")


def ll_cis_cen_bv_15_c(transport, upper_tester, lower_tester, trace, packets):
    """LL/CIS/CEN/BV-15-C [CIS Terminate Procedure, Initiated]"""
    return cis_terminate_procedure_initiated(transport, upper_tester, lower_tester, trace, "Central", packets)


def ll_cis_cen_bv_16_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-16-C [CIS Terminate Procedure, Accepting]"""
    return cis_terminate_procedure_accepting(transport, upper_tester, lower_tester, trace, "Central")


def ll_cis_cen_bv_30_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-30-C [Isochronous Channels Host Support Feature Bit]"""
    params = SetCIGParameters()
    success = set_isochronous_channels_host_support(transport, lower_tester, trace, 0b1)

    # 1. The Upper Tester sends an HCI_LE_Set_Host_Feature with Bit_Number set to 30 (not a host controlled feature bit)
    #       and Bit_Value set to 0b1. The Upper Tester receives an HCI_Command_Complete response with an error code
    #       Unsupported Feature or Parameter Value (0x11).
    status = le_set_host_feature(transport, upper_tester, FeatureSupport.ISOCHRONOUS_BROADCASTER, 0b1, 100)
    success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x11 and success

    # 2. The IUT establishes an ACL connection with the Lower Tester as Peripheral.
    s, _, initiator = establish_acl_connection(transport, upper_tester, lower_tester, trace)
    success = s and success
    if not initiator:
        return False

    # 3. The Upper Tester sends an HCI_LE_Set_Host_Feature with Bit_Number set to 32 (Isochronous Channels feature bit)
    #       and Bit_Value set to 0b1. The Upper Tester receives an HCI_Command_Complete response with an error code
    #       Command Disallowed (0x0C).
    status = le_set_host_feature(transport, upper_tester, FeatureSupport.ISOCHRONOUS_CHANNELS, 0b1, 100)
    success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x0C and success

    # 4. The IUT disconnects the ACL connection from the Lower Tester.
    success = initiator.disconnect(0x13) and success

    testData = namedtuple('testData', 'Set_Feature_Enable, Bit_Value, CIS_Created')
    table = [
        testData(False, None, False),
        testData(True, 0b1, True),
        testData(True, 0b0, False),
    ]

    # 5. Repeat steps 6–13 for each round specified in Table
    for set_feature_enable, bit_value, cis_created in table:
        # 6. If the table indicates “Set Feature Enable” is true for this round, then the Upper Tester sends an
        #       HCI_LE_Set_Host_Feature command to the IUT with the Bit_Number set to 32 (Isochronous Channels) and the
        #       Bit_Value set to the value specified in the table. The Upper Tester receives an HCI_Command_Complete
        #       event from the IUT.
        if set_feature_enable:
            status = le_set_host_feature(transport, upper_tester, FeatureSupport.ISOCHRONOUS_CHANNELS, bit_value, 100)
            success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x00 and success

        # 7. The IUT establishes an ACL connection with the Lower Tester as Peripheral.
        s, _, initiator = establish_acl_connection(transport, upper_tester, lower_tester, trace)
        success = s and success
        if not initiator:
            return False

        # 8. The Upper Tester sends an HCI_LE_Set_CIG_Parameters command to the IUT using the default parameters
        #       specified in Section 4.10.1.3 Default Values for Set CIG Parameters Commands and receives a successful
        #       HCI_Command_Complete event.
        status, cig_id, cis_count, cis_handles = le_set_cig_parameters_test(transport, upper_tester, 0,
                                                                            *params.get_cig_parameters_test(), 100)
        success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x00 and success

        # 9. The Upper Tester sends an HCI_LE_Create_CIS command with the ACL_Connection_Handle of the established ACL
        #       and valid Connection_Handle
        status = le_create_cis(transport, upper_tester, cis_count, cis_handles, cis_count * [initiator.handles[0]], 100)
        success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

        if cis_created:
           # 10. The Upper Tester receives a successful HCI_Command_Status event.
           success = status == 0x00 and success

           # 11. The IUT establishes a CIS with the Lower Tester.
           s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_REQUEST, trace)
           success = s and success

           status = le_accept_cis_request(transport, lower_tester, event.decode()[1], 100)
           s = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace)
           success = status == 0 and s and success

           s, event = verifyAndFetchMetaEvent(transport, upper_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace,
                                              2000)
           success = s and (event.decode()[0] == 0x00) and success

           s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace)
           success = s and (event.decode()[0] == 0x00) and success
        else:
            # 10. The Upper Tester receives an HCI_Command_Status event with the status set to error code Command
            #       Disallowed (0x0C).
            success = status == 0x0C and success

        # 12. The IUT disconnects the ACL connection from the Lower Tester.
        success = initiator.disconnect(0x13) and success

        # 13. If the table indicates that a CIS is not created this round, then the Upper Tester sends an
        #       HCI_LE_Remove_CIG command to the IUT with the CIG_ID set to the CIG_ID in step 8 and receives
        #       a successful HCI_Command_Complete event.
        if not cis_created:
            status, _ = le_remove_cig(transport, upper_tester, 0, 100)
            success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x00 and success

    return success


def ll_cis_cen_bv_20_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-20-C [Set Encryption After CIS Established]"""
    params = SetCIGParameters(
        SDU_Interval_C_To_P     = 0x4E20,  # 20 ms
        SDU_Interval_P_To_C     = 0x4E20,  # 20 ms
        FT_C_To_P               = 1,
        FT_P_To_C               = 1,
        ISO_Interval            = 0x10,  # 20 ms
        CIS_Count               = 1,
        NSE                     = 1,
        Max_PDU_C_To_P          = 130,
        Max_PDU_P_To_C          = 130,
        PHY_C_To_P              = 1,
        PHY_P_To_C              = 1,
        BN_C_To_P               = 1,
        BN_P_To_C               = 1,
    )

    success, initiator, _, _ = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params,
                                           setup_iso_data_path=False, enc_keys=None)
    if not initiator:
        return False

    rand, ediv, ltk = ENC_KEYS
    status = le_start_encryption(transport, upper_tester, initiator.handles[0], rand, ediv, ltk, 100)
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace, 1000) and status == 0x0C

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def ll_cis_cen_bv_51_c(transport, upper_tester, lower_tester, trace):
    """LL/CIS/CEN/BV-51-C [CIS Setup Procedure, Central Initiated, CIG ID Reuse]"""
    params = SetCIGParameters(
        CIS_Count               = 2,
    )

    success, initiator, _, (connection_handle_1, connection_handle_2) = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params)

    if not initiator:
        return False

    # 2. The Upper Tester sends an HCI_Disconnect command to the IUT with Connection_Handle_1 and Reason Code set to
    #       any valid value.
    # 3. The IUT sends a successful HCI_Command_Status event to the Upper Tester.
    # 4. The IUT sends an LL_CIS_TERMINATE_IND PDU to the Lower Tester with the ErrorCode field in the CtrData field
    #       set to match the Reason Code value in step 2.
    # 5. The Lower Tester sends an Ack to the IUT.
    success = disconnect(transport, upper_tester, connection_handle_1, 0x13, 200) == 0 and success
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

    # 6. The IUT sends a successful HCI_Disconnection_Complete event to the Upper Tester with Connection_Handle_1
    #       and Reason Code set to Connection Terminated by Local Host (0x16).
    s, event = verifyAndFetchEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and status == 0x00 and handle == connection_handle_1 and success and reason == 0x16

    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace) and success

    # 8. The IUT sends an LL_CIS_REQ PDU to the Lower Tester with CIG_ID set to CIG_ID_1 and CIS_ID set to CIS_ID_1.
    #       The re-created CIS may be different from the CIS disconnected previously using Connection_Handle_1,
    #       but it still complies with the Default Values for Set CIG Parameters Commands.
    status = le_create_cis(transport, lower_tester, 1, [connection_handle_1], [initiator.handles[0]], 100)
    success = status == 0 and success
    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

    # 9. The Lower Tester sends an LL_CIS_RSP PDU to the IUT.
    # 10. The IUT sends an LL_CIS_IND PDU to the Lower Tester. The Access Address provided in CtrData is different from
    #       AA_1. The new Access Address is identified as AA_1new.
    # 11. The IUT sends an ISO Data Packet to the Lower Tester.
    # 12. The Lower Tester sends an LL Ack to the IUT.
    # 13. The IUT sends an HCI_LE_CIS_Established event to the Upper Tester with Status set to 0x00 and
    #       Connection_Handle set to Connection_Handle_1.
    s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_REQUEST, trace)
    success = s and success

    status = le_accept_cis_request(transport, lower_tester, event.decode()[1], 100)
    s = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace)
    success = status == 0 and s and success

    s, event = verifyAndFetchMetaEvent(transport, upper_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace,
                                       2000)
    success = s and (event.decode()[0] == 0x00) and success

    s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace)
    success = s and (event.decode()[0] == 0x00) and success

    # 14. The Upper Tester sends an HCI_Disconnect command to the IUT with Connection_Handle_1 and Reason Code set to
    #       any valid value.
    # 15. The IUT sends a successful HCI_Command_Status event to the Upper Tester.
    success = disconnect(transport, upper_tester, connection_handle_1, 0x13, 200) == 0 and success
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

    # 16. The IUT sends an LL_CIS_TERMINATE_IND PDU to the Lower Tester with the ErrorCode field in the CtrData field
    #       set to match the Reason Code value in step 14.
    # 17. The Lower Tester sends an Ack to the IUT.
    # 18. The IUT sends a successful HCI_Disconnection_Complete event to the Upper Tester with Connection_Handle_1 and
    #       Reason Code set to Connection Terminated by Local Host (0x16).
    s, event = verifyAndFetchEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and status == 0x00 and handle == connection_handle_1 and reason == 0x16 and success

    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace) and success

    # 19. The Upper Tester sends an HCI_Disconnect command to the IUT with Connection_Handle_2 and Reason Code set to
    #       any valid value.
    # 20. The IUT sends a successful HCI_Command_Status event to the Upper Tester.
    success = disconnect(transport, upper_tester, connection_handle_2, 0x13, 200) == 0 and success
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

    # 21. The IUT sends an LL_CIS_TERMINATE_IND PDU to the Lower Tester with the ErrorCode field in the CtrData field
    #       set to match the Reason code value the Upper Tester sent in step 19.
    # 22. The Lower Tester sends an Ack to the IUT.
    # 23. The IUT sends a successful HCI_Disconnection_Complete event to the Upper Tester with Connection_Handle_2 and
    #       Reason Code set to Connection Terminated by Local Host (0x16).
    s, event = verifyAndFetchEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace)
    status, handle, reason = event.decode()
    success = s and status == 0x00 and handle == connection_handle_2 and success and reason == 0x16

    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace) and success

    # 24. The Upper Tester sends an HCI_LE_Remove_CIG command to the IUT with CIG_ID set to the value of CIG_ID_1
    #       and receives a successful HCI_Command_Complete event from the IUT in response.
    status, _ = le_remove_cig(transport, upper_tester, 0, 100)
    success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x00 and success

    # 25. The Upper Tester sends an HCI_LE_Set_CIG_Parameters command to the IUT with values as specified in Section 4.10.1.3, Default Values for Set CIG Parameters Commands. The CIG_ID is
    # to be set to CIG_ID_1 in step 1, CIS_ID is set to CIS_ID_1.
    params = SetCIGParameters()
    status, cig_id, cis_count, (connection_handle_3,) = \
        le_set_cig_parameters_test(transport, lower_tester, 0, *params.get_cig_parameters_test(), 100)

    # 26. The IUT sends a successful HCI_Command_Complete event to the Upper Tester with CIG_ID set to CIG_ID_1,
    #       CIS_Count set to 1, and the connection handle for CIS_ID_1, which is saved and referenced as
    #       Connection_Handle_3 in the following steps.
    success = cig_id == 0 and cis_count == 1 and success
    success = getCommandCompleteEvent(transport, lower_tester, trace) and status == 0x00 and success

    # 27. The Upper Tester sends an HCI_LE_Create_CIS command to the IUT with CIS_Count set to 1 and Connection_Handle
    #       set to Connection_Handle_3 from step 26, and it receives a successful HCI_Command_Status in response.
    status = le_create_cis(transport, lower_tester, 1, [connection_handle_3], [initiator.handles[0]], 100)
    success = status == 0 and success
    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

    # 28. The IUT sends an LL_CIS_REQ PDU to the Lower Tester with CIG_ID set to CIG_ID_1 and CIS_ID set to CIS_ID_3.
    # 29. The Lower Tester sends an LL_CIS_RSP PDU to the IUT.
    # 30. The IUT sends an LL_CIS_IND PDU to the Lower Tester. The Access Address provided in CtrData is different from
    #       AA_1, AA_1new, and AA_2.
    # 31. The IUT sends an ISO Data Packet to the Lower Tester.
    # 32. The Lower Tester sends an LL Ack to the IUT.
    # 33. The IUT sends an HCI_LE_CIS_Established event to the Upper Tester with Status set to 0x00 and
    #       Connection_Handle set to Connection_Handle_3.
    s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_REQUEST, trace)
    success = s and success

    status = le_accept_cis_request(transport, lower_tester, event.decode()[1], 100)
    s = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace)
    success = status == 0 and s and success

    s, event = verifyAndFetchMetaEvent(transport, upper_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace,
                                       2000)
    success = s and (event.decode()[0] == 0x00) and success

    s, event = verifyAndFetchMetaEvent(transport, lower_tester, MetaEvents.BT_HCI_EVT_LE_CIS_ESTABLISHED, trace)
    success = s and (event.decode()[0] == 0x00) and success

    if connection_handle_3 == connection_handle_1:
        # Alternative 34A (The value of Connection_Handle_3 equals the value of Connection_Handle_1):
        # 34A.1. The Upper Tester sends an HCI_LE_Create_CIS command to the IUT with CIS_Count set to 1,
        #       ACL_Connection_Handle set to the current ACL Connection Handle value, and CIS_Connection_Handle set to
        #       Connection_Handle_2.
        status = le_create_cis(transport, lower_tester, 1, [connection_handle_2], [initiator.handles[0]], 100)
        success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

        # 34A.2. The IUT sends an HCI_Command_Status event to the Upper Tester with Status set to Unknown Connection
        #       Identifier (0x02).
        success = status == 0x02 and success
    elif connection_handle_3 == connection_handle_2:
        # Alternative 34B (The value of Connection_Handle_3 equals the value of Connection_Handle_2):
        # 34B.1. The Upper Tester sends an HCI_LE_Create_CIS command to the IUT with CIS_Count set to 1,
        #       ACL_Connection_Handle set to the current ACL Connection Handle value, and CIS_Connection_Handle set to
        #       the value of Connection_Handle_1.
        status = le_create_cis(transport, lower_tester, 1, [connection_handle_1], [initiator.handles[0]], 100)
        success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success

        # 34B.2 The IUT sends an HCI_Command_Status event to the Upper Tester with Status set to Unknown Connection
        #       Identifier (0x02).
        success = status == 0x02 and success
    else:
        # Alternative 34C (The value of Connection_Handle is different than Connection_Handle_1 or Connection_Handle_2):
        # 34C.1 The test ends with a Pass verdict.
        pass

    # TODO: Verify CIS Access Addresses

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    return success


def connected_isochronous_stream_using_non_test_command(transport, upper_tester, lower_tester, trace, packets, phy,
                                                        max_transport_latency):
    params = SetCIGParameters(
        Framing                         = 1,
        PHY_C_To_P                      = phy,
        PHY_P_To_C                      = phy,
        Max_Transport_Latency_C_To_P    = max_transport_latency,
        Max_Transport_Latency_P_To_C    = max_transport_latency,
        Max_SDU_C_To_P                  = 16,
        Max_SDU_P_To_C                  = 16,
    )

    start_time = transport.get_time()

    success, initiator, _, (cis_handle_upper_tester,) = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params, use_test_cmd=False)
    if not initiator:
        return False

    for seq_num in range(3):
        success = iso_send_payload_pdu(transport, upper_tester, lower_tester, trace, cis_handle_upper_tester,
                                       params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, seq_num) and success

    success = disconnect(transport, upper_tester, cis_handle_upper_tester, 0x13, 200) == 0 and success
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_CMD_STATUS, trace) and success
    success = verifyAndShowEvent(transport, upper_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace) and success
    success = verifyAndShowEvent(transport, lower_tester, Events.BT_HCI_EVT_DISCONN_COMPLETE, trace) and success

    status, cig_id = le_remove_cig(transport, upper_tester, 0, 100)
    success = getCommandCompleteEvent(transport, upper_tester, trace) and status == 0x00 and cig_id == 0 and success

    params = SetCIGParameters(
        Framing                         = 1,
        PHY_C_To_P                      = phy,
        PHY_P_To_C                      = phy,
        Max_Transport_Latency_C_To_P    = max_transport_latency,
        Max_Transport_Latency_P_To_C    = max_transport_latency,
        Max_SDU_C_To_P                  = 0,
        Max_SDU_P_To_C                  = 16,
    )

    s, _, (cis_handle_lower_tester,) = establish_cis_connection(transport, upper_tester, lower_tester, trace, params,
                                                                initiator.handles[0], use_test_cmd=False)
    success = s and success

    for seq_num in range(50):
        success = iso_send_payload_pdu(transport, lower_tester, upper_tester, trace, cis_handle_lower_tester,
                                       params.Max_SDU_P_To_C[0], params.SDU_Interval_P_To_C, seq_num) and success


    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    trace.trace(4, "Host verification " + ("PASS" if success else "FAIL"))

    ### LL VERIFICATION ###
    isoc_framed_pdu_num = 0
    for packet in packets.fetch(packet_filter=('LL_CIS_REQ', 'ISOC_FRAMED_PDU')):
        if packet.ts/1000 < start_time:
            trace.trace(4, "Drop %s" % str(packet))
        elif packet.type.name == 'LL_CIS_REQ':
            success = packet.payload.CtrData.Framed == 1 and success
        elif packet.payload.SegmentationHeader.CMPLT == 1:
            isoc_framed_pdu_num += 1

    success = isoc_framed_pdu_num == 53 and success

    trace.trace(4, "LL verification " + ("PASS" if success else "FAIL"))

    return success


def ll_cis_cen_bv_26_c(transport, upper_tester, lower_tester, trace, packets):
    """LL/CIS/CEN/BV-26-C [Connected Isochronous Stream Using Non-Test Command, Central Initiated]"""
    return connected_isochronous_stream_using_non_test_command(transport, upper_tester, lower_tester, trace, packets,
                                                               1, 60)


def ll_cis_cen_bv_27_c(transport, upper_tester, lower_tester, trace, packets):
    """LL/CIS/CEN/BV-27-C [Connected Isochronous Stream Using Non-Test Command, Central Initiated]"""
    return connected_isochronous_stream_using_non_test_command(transport, upper_tester, lower_tester, trace, packets,
                                                               2, 60)


def connected_isochronous_stream_using_non_test_command_force_framed_pdus(transport, upper_tester, lower_tester, trace,
                                                                          packets, phy):
    params = SetCIGParameters(
        SDU_Interval_C_To_P             = 10884,  # 10.884 ms
        SDU_Interval_P_To_C             = 10884,  # 10.884 ms
        Max_SDU_C_To_P                  = 0x20,
        Max_SDU_P_To_C                  = 0x20,
        RTN_C_To_P                      = 0x03,
        RTN_P_To_C                      = 0x03,
        Max_Transport_Latency_C_To_P    = 40,
        Max_Transport_Latency_P_To_C    = 40,
        Framing                         = 0,  # Unframed
        PHY_C_To_P                      = phy,
        PHY_P_To_C                      = phy,
    )

    start_time = transport.get_time()

    success, initiator, _, (cis_handle_upper_tester,) = \
        state_connected_isochronous_stream(transport, lower_tester, upper_tester, trace, params, use_test_cmd=False)
    if not initiator:
        return False

    for seq_num in range(3):
        success = iso_send_payload_pdu(transport, upper_tester, lower_tester, trace, cis_handle_upper_tester,
                                       params.Max_SDU_C_To_P[0], params.SDU_Interval_C_To_P, seq_num) and success

    ### TERMINATION ###
    success = initiator.disconnect(0x13) and success

    trace.trace(4, "Host verification " + ("PASS" if success else "FAIL"))

    ### LL VERIFICATION ###
    isoc_framed_pdu_num = 0
    for packet in packets.fetch(packet_filter=('LL_CIS_REQ', 'ISOC_FRAMED_PDU')):
        if packet.ts/1000 < start_time:
            trace.trace(4, "Drop %s" % str(packet))
        elif packet.type.name == 'LL_CIS_REQ':
            success = packet.payload.CtrData.Framed == 1 and success
        elif packet.payload.SegmentationHeader.CMPLT == 1:
            isoc_framed_pdu_num += 1

    success = isoc_framed_pdu_num == 3 and success

    trace.trace(4, "LL verification " + ("PASS" if success else "FAIL"))

    return success


def ll_cis_cen_bv_36_c(transport, upper_tester, lower_tester, trace, packets):
    """LL/CIS/CEN/BV-36-C [Connected Isochronous Stream Using Non-Test Command, Force Framed PDUs]"""
    return connected_isochronous_stream_using_non_test_command_force_framed_pdus(transport, upper_tester, lower_tester,
                                                                                 trace, packets, 1)


def ll_cis_cen_bv_37_c(transport, upper_tester, lower_tester, trace, packets):
    """LL/CIS/CEN/BV-37-C [Connected Isochronous Stream Using Non-Test Command, Force Framed PDUs]"""
    return connected_isochronous_stream_using_non_test_command_force_framed_pdus(transport, upper_tester, lower_tester,
                                                                                 trace, packets, 2)


def ll_ist_cen_bv_01_c(transport, upper_tester, lower_tester, trace):
    """LL/IST/CEN/BV-01-C [ISO Transmit Test Mode, CIS]"""
    return iso_transmit_test_mode_cis(transport, upper_tester, lower_tester, trace, True)


def ll_ist_cen_bv_03_c(transport, upper_tester, lower_tester, trace):
    """LL/IST/CEN/BV-03-C [ISO Receive Test Mode, CIS]"""
    return iso_receive_test_mode_cis(transport, upper_tester, lower_tester, trace, True)

# Implements LL/TIM/ADV/BV-03-C, LL/TIM/ADV/BV-04-C, LL/TIM/ADV/BV-05-C and LL/TIM/ADV/BV-07-C
# (only difference is PHY and the timing of the AUX_SCAN_REQ)
def do_ll_tim_adv_bv_03_04_05_07_c(transport, upperTester, lowerTester, trace, packets, phy, timing_offset):

    advInterval = 0x20 # 32 x 0.625 ms = 20.00 ms
    Handle          = 0
    Properties      = 0x0002
    PrimMinInterval = toArray(advInterval, 3)
    PrimMaxInterval = toArray(advInterval, 3)
    PrimChannelMap  = 0x07  # Advertise on all three channels (#37, #38 and #39)
    OwnAddrType     = SimpleAddressType.PUBLIC
    PeerAddrType    = SimpleAddressType.PUBLIC
    PeerAddress     = toArray(0x456789ABCDEF, 6)
    FilterPolicy    = AdvertisingFilterPolicy.FILTER_NONE
    TxPower         = 0
    PrimAdvPhy      = PhysicalChannel.LE_1M
    SecAdvMaxSkip   = 0
    SecAdvPhy       = phy
    Sid             = 0
    ScanReqNotifyEnable = 0

    success = preamble_ext_advertising_parameters_set(transport, upperTester, Handle, Properties, PrimMinInterval, PrimMaxInterval,
                                                      PrimChannelMap, OwnAddrType, PeerAddrType, PeerAddress, FilterPolicy, TxPower,
                                                      PrimAdvPhy, SecAdvMaxSkip, SecAdvPhy, Sid, ScanReqNotifyEnable, trace)
    if not success:
        return success

    advData = [ 0xAA ]
    success = success and preamble_ext_scan_response_data_set(transport, upperTester, Handle, FragmentOperation.COMPLETE_FRAGMENT, 0, advData, trace)
    if not success:
        return False

    success = success and preamble_ext_advertise_enable(transport, upperTester, Advertise.ENABLE, [Handle], [0], [0], trace)
    if not success:
        return False

    responses = 0

    for i in range(100):
        auxAdvIndPacket = wait_for_AUX_ADV_IND_end(transport, packets)

        # Transmit a AUX_SCAN_REQ
        packetData = (0b0011 + (12 << 8)).to_bytes(2, 'little', signed=False) # header - PDU Type 0b0101, ChSel, TxAdd and RxAdd all 0, length 12
        packetData = b''.join([packetData, 0x456789ABCDEF.to_bytes(6, 'little', signed=False)]) # ScanA
        packetData = b''.join([packetData, 0x123456789ABC.to_bytes(6, 'little', signed=False)]) # AdvA
        CRC = calcBLECRC(0x555555, packetData)
        packetData = b''.join([packetData, CRC.to_bytes(3, 'little', signed=False)])

        # Calculate transmit timestamp (T_IFS + timing_offset from end of AUX_ADV_IND)
        transmitTime = auxAdvIndPacket.ts + get_packet_air_time(auxAdvIndPacket) + 150 + timing_offset

        transport.low_level_device.tx(channel_num_to_index(auxAdvIndPacket.channel_num), auxAdvIndPacket.phy, auxAdvIndPacket.aa, transmitTime, packetData)

        # Wait a ms for a response
        transport.wait(1)

        lastAuxScanResp = packets.findLast(packet_filter=('AUX_SCAN_RSP'))

        if lastAuxScanResp:
            # Check timing - should be T_IFS (plus or minus 2 μsecs) after the AUX_SCAN_REQ
            reqAirTime = math.ceil(((2 if lastAuxScanResp.phy == '2M' else 1) + 4 + 2 + 12 + 3)*8/(2 if lastAuxScanResp.phy == '2M' else 1))
            targetTime = transmitTime + reqAirTime + 150
            if lastAuxScanResp.ts <= targetTime + 2 and lastAuxScanResp.ts >= targetTime - 2:
                responses += 1

    # Check: The time between a PDU containing an AuxPtr field and the PDU to which it refers shall be greater than or equal to T_MAFS
    for packet in packets.fetch(packet_filter=('AUX_ADV_IND')):
        for superiorPacket in packet.payload['SuperiorPackets']:
            # Packet air length is: pre-amble + AA + header + payload + CRC
            packetLength = (2 if superiorPacket.phy == '2M' else 1) + 4 + 2 + len(superiorPacket) + 3
            packetAirtime = math.ceil(8*packetLength/(2 if superiorPacket.phy == '2M' else 1))
            success = success and packet.ts >= superiorPacket.ts + packetAirtime + 300

    # The IUT responds to at least 95 percent of the AUX_SCAN_REQ packets sent by the Lower Tester
    success = success and responses >= 95

    return success

"""
    LL/TIM/ADV/BV-03-C [Extended Advertising, Secondary Channel, Earliest Transmission to Advertiser - LE 1M PHY]
"""
def ll_tim_adv_bv_03_c(transport, upperTester, lowerTester, trace, packets):
    # Note: BabbleSim only supports whole microsecond timings, so using -2 instead of -1.5
    # It should not affect the test, since 1.5 us is only used to account for lower tester timing inaccuracies anyway
    return do_ll_tim_adv_bv_03_04_05_07_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_1M, -2)

"""
    LL/TIM/ADV/BV-04-C [Extended Advertising, Secondary Channel, Latest Transmission to Advertiser - LE 1M PHY]
"""
def ll_tim_adv_bv_04_c(transport, upperTester, lowerTester, trace, packets):
    # Note: BabbleSim only supports whole microsecond timings, so using 2 instead of 1.5
    # It should not affect the test, since 1.5 us is only used to account for lower tester timing inaccuracies anyway
    return do_ll_tim_adv_bv_03_04_05_07_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_1M, 2)

"""
    LL/TIM/ADV/BV-05-C [Extended Advertising, Secondary Channel, Earliest Transmission to Advertiser - LE 2M PHY]
"""
def ll_tim_adv_bv_05_c(transport, upperTester, lowerTester, trace, packets):
    # Note: BabbleSim only supports whole microsecond timings, so using -2 instead of -1.5
    # It should not affect the test, since 1.5 us is only used to account for lower tester timing inaccuracies anyway
    return do_ll_tim_adv_bv_03_04_05_07_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_2M, -2)

"""
    LL/TIM/ADV/BV-07-C [Extended Advertising, Secondary Channel, Latest Transmission to Advertiser - LE 2M PHY]
"""
def ll_tim_adv_bv_07_c(transport, upperTester, lowerTester, trace, packets):
    # Note: BabbleSim only supports whole microsecond timings, so using 2 instead of 1.5
    # It should not affect the test, since 1.5 us is only used to account for lower tester timing inaccuracies anyway
    return do_ll_tim_adv_bv_03_04_05_07_c(transport, upperTester, lowerTester, trace, packets, PhysicalChannel.LE_2M, 2)

LowLevelDeviceRequired = "LowLevelDeviceRequired"
__tests__ = {
    "LL/CON/ADV/BV-01-C": [ ll_con_adv_bv_01_c, "Accepting Connection Request" ],
    "LL/CON/ADV/BV-04-C": [ ll_con_adv_bv_04_c, "Accepting Connection Request after Directed Advertising" ],
    "LL/CON/ADV/BV-05-C": [ ll_con_adv_bv_05_c, "Extended Advertising, Accepting Connections; LE 1M PHY", LowLevelDeviceRequired ],
    "LL/CON/ADV/BV-06-C": [ ll_con_adv_bv_06_c, "Extended Advertising, Legacy PDUs, Accepting Connections", LowLevelDeviceRequired ],
    "LL/CON/ADV/BV-09-C": [ ll_con_adv_bv_09_c, "Accepting Connection Request using Channel Selection Algorithm #2" ],
    "LL/CON/ADV/BV-10-C": [ ll_con_adv_bv_10_c, "Accepting Connection Request after Directed Advertising using Channel Selection Algorithm #2" ],
    "LL/CON/ADV/BV-12-C": [ ll_con_adv_bv_12_c, "Extended Advertising, Accepting Connections; LE 2M PHY", LowLevelDeviceRequired ],
    "LL/CON/ADV/BV-14-C": [ ll_con_adv_bv_14_c, "Extended Advertising, Accepting Connections with Random address; LE 1M PHY" ],
    "LL/CON/ADV/BV-15-C": [ ll_con_adv_bv_15_c, "Extended Advertising, Accepting Connections with Random address; LE 2M PHY" ],
    "LL/CON/INI/BV-01-C": [ ll_con_ini_bv_01_c, "Connection Initiation rejects Address change" ],
    "LL/CON/INI/BV-02-C": [ ll_con_ini_bv_02_c, "Connecting to Advertiser using Directed Advertising Packets" ],
    "LL/CON/INI/BV-06-C": [ ll_con_ini_bv_06_c, "Filtered Connection to Advertiser using Undirected Advertising Packets" ],
    "LL/CON/INI/BV-07-C": [ ll_con_ini_bv_07_c, "Filtered Connection to Advertiser using Directed Advertising Packets" ],
    "LL/CON/INI/BV-08-C": [ ll_con_ini_bv_08_c, "Connecting to Connectable Undirected Advertiser with Network Privacy" ],
    "LL/CON/INI/BV-09-C": [ ll_con_ini_bv_09_c, "Connecting to Connectable Undirected Advertiser with Network Privacy thru Resolving List" ],
    "LL/CON/INI/BV-10-C": [ ll_con_ini_bv_10_c, "Connecting to Directed Advertiser with Network Privacy thru Resolving List" ],
    "LL/CON/INI/BV-11-C": [ ll_con_ini_bv_11_c, "Connecting to Directed Advertiser using  wrong address with Network Privacy thru Resolving List " ],
    "LL/CON/INI/BV-12-C": [ ll_con_ini_bv_12_c, "Connecting to Directed Advertiser using Identity address with Network Privacy thru Resolving List" ],
    "LL/CON/INI/BV-16-C": [ ll_con_ini_bv_16_c, "Connecting to Advertiser with Channel Selection Algorithm #2" ],
    "LL/CON/INI/BV-17-C": [ ll_con_ini_bv_17_c, "Connecting to Directed Advertiser with Channel Selection Algorithm #2" ],
    "LL/CON/INI/BV-18-C": [ ll_con_ini_bv_18_c, "Don't connect to Advertiser using Identity address with Network Privacy thru Resolving List" ],
    "LL/CON/INI/BV-19-C": [ ll_con_ini_bv_19_c, "Don't connect to Directed Advertiser using Identity address with Network Privacy thru Resolving List" ],
    "LL/CON/INI/BV-20-C": [ ll_con_ini_bv_20_c, "Connect to Advertiser using Identity address with Device Privacy thru Resolving List" ],
    "LL/CON/INI/BV-21-C": [ ll_con_ini_bv_21_c, "Connect to Directed Advertiser using Identity address with Device Privacy thru Resolving List" ],
    "LL/CON/INI/BV-23-C": [ ll_con_ini_bv_23_c, "Network Privacy - Connection Establishment using filterallowlist and resolving list with address resolution disabled" ],
    "LL/CON/INI/BV-24-C": [ ll_con_ini_bv_24_c, "Network Privacy - Connection Establishment using resolving list with address resolution disabled" ],
    "LL/CON/CEN/BI-06-C": [ ll_con_cen_bi_06_c, "Central responds to Connection Parameter Request - illegal parameters" ],
    "LL/CON/CEN/BV-03-C": [ ll_con_cen_bv_03_c, "Central sending Data packets to Peripheral" ],
    "LL/CON/CEN/BV-04-C": [ ll_con_cen_bv_04_c, "Central receiving Data packets from Peripheral" ],
    "LL/CON/CEN/BV-05-C": [ ll_con_cen_bv_05_c, "Central sending and receiving Data packets to and form Peripheral" ],
    "LL/CON/CEN/BV-07-C": [ ll_con_cen_bv_07_c, "Central requests Connection Parameter Update" ],
    "LL/CON/CEN/BV-08-C": [ ll_con_cen_bv_08_c, "Central Terminating Connection" ],
    "LL/CON/CEN/BV-09-C": [ ll_con_cen_bv_09_c, "Central accepting Connection Termination" ],
    "LL/CON/CEN/BV-13-C": [ ll_con_cen_bv_13_c, "Central requests Feature Setup procedure" ],
    "LL/CON/CEN/BV-20-C": [ ll_con_cen_bv_20_c, "Central requests Version Exchange procedure" ],
    "LL/CON/CEN/BV-21-C": [ ll_con_cen_bv_21_c, "Central responds to Version Exchange procedure" ],
    "LL/CON/CEN/BV-23-C": [ ll_con_cen_bv_23_c, "Central responds to Feature Exchange procedure" ],
    "LL/CON/CEN/BV-24-C": [ ll_con_cen_bv_24_c, "Central requests Connection Parameters - Peripheral Accepts" ],
    "LL/CON/CEN/BV-25-C": [ ll_con_cen_bv_25_c, "Central requests Connection Parameters - Peripheral Rejects" ],
    "LL/CON/CEN/BV-26-C": [ ll_con_cen_bv_26_c, "Central requests Connection Parameters - same procedure collision" ],
    "LL/CON/CEN/BV-27-C": [ ll_con_cen_bv_27_c, "Central requests Connection Parameters - Channel Map Update procedure collision" ],
    "LL/CON/CEN/BV-29-C": [ ll_con_cen_bv_29_c, "Central requests Connection Parameters - Peripheral unsupported" ],
    "LL/CON/CEN/BV-30-C": [ ll_con_cen_bv_30_c, "Central responds to Connection Parameters request - no Preferred_Periodicity" ],
    "LL/CON/CEN/BV-34-C": [ ll_con_cen_bv_34_c, "Central responds to Connection Parameters request - event masked" ],
    "LL/CON/CEN/BV-35-C": [ ll_con_cen_bv_35_c, "Central responds to Connection Parameters request - Host rejects" ],
    "LL/CON/CEN/BV-41-C": [ ll_con_cen_bv_41_c, "Central requests PHY Update procedure" ],
    "LL/CON/CEN/BV-43-C": [ ll_con_cen_bv_43_c, "Central responds to PHY Update procedure" ],
    "LL/CON/CEN/BV-73-C": [ ll_con_cen_bv_73_c, "Central Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 1M PHY" ],
    "LL/CON/CEN/BV-74-C": [ ll_con_cen_bv_74_c, "Central Packet Data Length Update - Initiating Packet Data Length Update Procedure; LE 1M PHY" ],
    "LL/CON/CEN/BV-76-C": [ ll_con_cen_bv_76_c, "Central Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 2M PHY" ],
    "LL/CON/CEN/BV-77-C": [ ll_con_cen_bv_77_c, "Central Packet Data Length Update - Initiating Packet Data Length Update Procedure; LE 2M PHY" ],
    "LL/CON/PER/BI-08-C": [ ll_con_per_bi_08_c, "Peripheral responds to Connection Parameters request - Illegal Parameters" ],
    "LL/CON/PER/BV-04-C": [ ll_con_per_bv_04_c, "Connection where Peripheral sends data to Central" ],
    "LL/CON/PER/BV-05-C": [ ll_con_per_bv_05_c, "Connection where Peripheral receives data from Central" ],
    "LL/CON/PER/BV-06-C": [ ll_con_per_bv_06_c, "Connection where Peripheral sends and receives data to and from Central" ],
    "LL/CON/PER/BV-10-C": [ ll_con_per_bv_10_c, "Peripheral accepting Connection Parameter Update from Central" ],
    "LL/CON/PER/BV-11-C": [ ll_con_per_bv_11_c, "Peripheral sending Termination to Central" ],
    "LL/CON/PER/BV-12-C": [ ll_con_per_bv_12_c, "Peripheral accepting Termination from Central" ],
#   "LL/CON/PER/BV-13-C": [ ll_con_per_bv_13_c, "Peripheral Terminating Connection on Supervision Timer" ],
    "LL/CON/PER/BV-14-C": [ ll_con_per_bv_14_c, "Peripheral performs Feature Setup procedure" ],
    "LL/CON/PER/BV-19-C": [ ll_con_per_bv_19_c, "Peripheral requests Version Exchange procedure" ],
    "LL/CON/PER/BV-20-C": [ ll_con_per_bv_20_c, "Peripheral responds to Version Exchange procedure" ],
    "LL/CON/PER/BV-22-C": [ ll_con_per_bv_22_c, "Peripheral requests Feature Exchange procedure" ],
    "LL/CON/PER/BV-24-C": [ ll_con_per_bv_24_c, "Peripheral requests Connection Parameters - Central Accepts" ],
    "LL/CON/PER/BV-25-C": [ ll_con_per_bv_25_c, "Peripheral requests Connection Parameters - Central Rejects" ],
    "LL/CON/PER/BV-26-C": [ ll_con_per_bv_26_c, "Peripheral requests Connection Parameters - same procedure collision" ],
    "LL/CON/PER/BV-27-C": [ ll_con_per_bv_27_c, "Peripheral requests Connection Parameters - channel map update procedure collision" ],
    "LL/CON/PER/BV-29-C": [ ll_con_per_bv_29_c, "Peripheral responds to Connection Parameters - Central no Preferred Periodicity" ],
    "LL/CON/PER/BV-33-C": [ ll_con_per_bv_33_c, "Peripheral responds to Connection Parameters request - event masked" ],
    "LL/CON/PER/BV-34-C": [ ll_con_per_bv_34_c, "Peripheral responds to Connection Parameters request - Host rejects" ],
    "LL/CON/PER/BV-40-C": [ ll_con_per_bv_40_c, "Peripheral requests PHY Update procedure" ],
    "LL/CON/PER/BV-42-C": [ ll_con_per_bv_42_c, "Peripheral responds to PHY Update procedure" ],
    "LL/CON/PER/BV-77-C": [ ll_con_per_bv_77_c, "Peripheral Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 1M PHY" ],
    "LL/CON/PER/BV-78-C": [ ll_con_per_bv_78_c, "Peripheral requests Packet Data Length Update procedure; LE 1M PHY" ],
    "LL/CON/PER/BV-80-C": [ ll_con_per_bv_80_c, "Peripheral Packet Data Length Update - Responding to Packet Data Length Update Procedure; LE 2M PHY" ],
    "LL/CON/PER/BV-81-C": [ ll_con_per_bv_81_c, "Peripheral requests Packet Data Length Update procedure; LE 2M PHY" ],
    "LL/DDI/ADV/BI-05-C": [ ll_ddi_adv_bi_05_c, "Disallow Extended Advertising PDU sizes for Legacy Advertising when advertising enabled" ],
    "LL/DDI/ADV/BI-06-C": [ ll_ddi_adv_bi_06_c, "Disallow Extended Advertising PDU sizes for Scannable Legacy Advertising when advertising enabled" ],
    "LL/DDI/ADV/BV-01-C": [ ll_ddi_adv_bv_01_c, "Non-Connectable Advertising Packets on one channel" ],
    "LL/DDI/ADV/BV-02-C": [ ll_ddi_adv_bv_02_c, "Undirected Advertising Packets on one channel" ],
    "LL/DDI/ADV/BV-03-C": [ ll_ddi_adv_bv_03_c, "Non-Connectable Advertising Packets on all channels" ],
    "LL/DDI/ADV/BV-04-C": [ ll_ddi_adv_bv_04_c, "Undirected Advertising with Data on all channels " ],
    "LL/DDI/ADV/BV-05-C": [ ll_ddi_adv_bv_05_c, "Undirected Connectable Advertising with Scan Request/Response " ],
    "LL/DDI/ADV/BV-06-C": [ ll_ddi_adv_bv_06_c, "Stop Advertising on Connection Request" ],
    "LL/DDI/ADV/BV-07-C": [ ll_ddi_adv_bv_07_c, "Scan Request/Response followed by Connection Request" ],
    "LL/DDI/ADV/BV-08-C": [ ll_ddi_adv_bv_08_c, "Advertiser Filtering Scan requests" ],
    "LL/DDI/ADV/BV-09-C": [ ll_ddi_adv_bv_09_c, "Advertiser Filtering Connection requests" ],
    "LL/DDI/ADV/BV-11-C": [ ll_ddi_adv_bv_11_c, "High Duty Cycle Connectable Directed Advertising on all channels" ],
    "LL/DDI/ADV/BV-15-C": [ ll_ddi_adv_bv_15_c, "Discoverable Undirected Advertising on all channels" ],
    "LL/DDI/ADV/BV-16-C": [ ll_ddi_adv_bv_16_c, "Discoverable Undirected Advertising with Data on all channels" ],
    "LL/DDI/ADV/BV-17-C": [ ll_ddi_adv_bv_17_c, "Discoverable Undirected Advertising with Scan Request/Response" ],
    "LL/DDI/ADV/BV-18-C": [ ll_ddi_adv_bv_18_c, "Discoverable Undirected Advertiser Filtering Scan requests " ],
    "LL/DDI/ADV/BV-19-C": [ ll_ddi_adv_bv_19_c, "Low Duty Cycle Directed Advertising on all channels" ],
    "LL/DDI/ADV/BV-20-C": [ ll_ddi_adv_bv_20_c, "Advertising on the LE 1M PHY on all channels" ],
    "LL/DDI/ADV/BV-21-C": [ ll_ddi_adv_bv_21_c, "Non-Connectable Extended Legacy Advertising with Data on all channels" ],
    "LL/DDI/ADV/BV-22-C": [ ll_ddi_adv_bv_22_c, "Extended Advertising, Legacy PDUs, Undirected, CSA #2" ],
    "LL/DDI/ADV/BV-27-C": [ ll_ddi_adv_bv_27_c, "Extended Advertising, Host Modifying Data and ADI" ],
    "LL/DDI/ADV/BV-28-C": [ ll_ddi_adv_bv_28_c, "Extended Advertising, Overlapping Extended Advertising Events" ],
    "LL/DDI/ADV/BV-45-C": [ ll_ddi_adv_bv_45_c, "Extended Advertising, Scannable - ADI allowed in scan response", LowLevelDeviceRequired ],
    "LL/DDI/ADV/BV-47-C": [ ll_ddi_adv_bv_47_c, "Extended Advertising, Non-Connectable - LE 1M PHY" ],
    "LL/DDI/ADV/BV-49-C": [ ll_ddi_adv_bv_49_c, "Extended Advertising, Non-Connectable - LE 2M PHY" ],
    "LL/DDI/ADV/BV-52-C": [ ll_ddi_adv_bv_52_c, "Extended Advertising, Scannable - ADI allowed in scan response - LE 2M PHY", LowLevelDeviceRequired ],
    "LL/DDI/SCN/BV-01-C": [ ll_ddi_scn_bv_01_c, "Passive Scanning of Non-Connectable Advertising Packets" ],
    "LL/DDI/SCN/BV-02-C": [ ll_ddi_scn_bv_02_c, "Filtered Passive Scanning of Non-Connectable Advertising Packets" ],
    "LL/DDI/SCN/BV-03-C": [ ll_ddi_scn_bv_03_c, "Active Scanning of Connectable Undirected Advertising Packets" ],
    "LL/DDI/SCN/BV-04-C": [ ll_ddi_scn_bv_04_c, "Filtered Active Scanning of Connectable Undirected Advertising Packets" ],
    "LL/DDI/SCN/BV-05-C": [ ll_ddi_scn_bv_05_c, "Scanning for different Advertiser types with and without Data" ],
    "LL/DDI/SCN/BV-10-C": [ ll_ddi_scn_bv_10_c, "Passive Scanning for Undirected Advertising Packets with Data" ],
    "LL/DDI/SCN/BV-11-C": [ ll_ddi_scn_bv_11_c, "Passive Scanning for Directed Advertising Packets" ],
    "LL/DDI/SCN/BV-12-C": [ ll_ddi_scn_bv_12_c, "Passive Scanning for Discoverable Undirected Advertising Packets" ],
    "LL/DDI/SCN/BV-13-C": [ ll_ddi_scn_bv_13_c, "Passive Scanning for Non-Connectable Advertising Packets using Network Privacy" ],
    "LL/DDI/SCN/BV-14-C": [ ll_ddi_scn_bv_14_c, "Passive Scanning for Connectable Directed Advertising Packets using Network Privacy" ],
    "LL/DDI/SCN/BV-15-C": [ ll_ddi_scn_bv_15_c, "Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with no Local or Peer IRK" ],
    "LL/DDI/SCN/BV-16-C": [ ll_ddi_scn_bv_16_c, "Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with Local and no Peer IRK" ],
    "LL/DDI/SCN/BV-17-C": [ ll_ddi_scn_bv_17_c, "Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with no Local and a Peer IRK" ],
    "LL/DDI/SCN/BV-18-C": [ ll_ddi_scn_bv_18_c, "Active Scanning for Scannable Undirected Advertising Packets using Network Privacy with both Local and Peer IRKs" ],
    "LL/DDI/SCN/BV-26-C": [ ll_ddi_scn_bv_26_c, "Passive Scanning for Non-Connectable Advertising Packets using Network Privacy" ],
    "LL/DDI/SCN/BV-28-C": [ ll_ddi_scn_bv_28_c, "Passive Scanning for Non-Connectable Advertising Packets using Device Privacy" ],
#   "LL/SEC/ADV/BV-01-C": [ ll_sec_adv_bv_01_c, "Changing Static Address while Advertising" ],
    "LL/SEC/ADV/BV-02-C": [ ll_sec_adv_bv_02_c, "Non Connectable Undirected Advertising with non-resolvable private address" ],
    "LL/SEC/ADV/BV-03-C": [ ll_sec_adv_bv_03_c, "Non Connectable Undirected Advertising with resolvable private address" ],
    "LL/SEC/ADV/BV-04-C": [ ll_sec_adv_bv_04_c, "Scannable Undirected Advertising with non-resolvable private address" ],
    "LL/SEC/ADV/BV-05-C": [ ll_sec_adv_bv_05_c, "Scannable Undirected Advertising with resolvable private address" ],
    "LL/SEC/ADV/BV-06-C": [ ll_sec_adv_bv_06_c, "Connecting with Undirected Connectable Advertiser using non-resolvable private address" ],
#   "LL/SEC/ADV/BV-07-C": [ ll_sec_adv_bv_07_c, "Connecting with Undirected Connectable Advertiser with Local IRK but no Peer IRK" ],
    "LL/SEC/ADV/BV-08-C": [ ll_sec_adv_bv_08_c, "Connecting with Undirected Connectable Advertiser with both Local and Peer IRK" ],
    "LL/SEC/ADV/BV-09-C": [ ll_sec_adv_bv_09_c, "Connecting with Undirected Connectable Advertiser with no Local IRK but peer IRK" ],
    "LL/SEC/ADV/BV-10-C": [ ll_sec_adv_bv_10_c, "Connecting with Undirected Connectable Advertiser where no match for Peer Device Identity" ],
    "LL/SEC/ADV/BV-11-C": [ ll_sec_adv_bv_11_c, "Connecting with Directed Connectable Advertiser using local and remote IRK" ],
    "LL/SEC/ADV/BV-12-C": [ ll_sec_adv_bv_12_c, "Connecting with Directed Connectable Advertising with local IRK but without remote IRK" ],
    "LL/SEC/ADV/BV-13-C": [ ll_sec_adv_bv_13_c, "Directed Connectable Advertising without local IRK but with remote IRK" ],
    "LL/SEC/ADV/BV-14-C": [ ll_sec_adv_bv_14_c, "Directed Connectable Advertising using Resolving List and Peer Device Identity not in the List" ],
    "LL/SEC/ADV/BV-15-C": [ ll_sec_adv_bv_15_c, "Scannable Advertising with resolvable private address, no Scan Response to Identity Address" ],
    "LL/SEC/ADV/BV-16-C": [ ll_sec_adv_bv_16_c, "Undirected Connectable Advertising with resolvable private address; no Connection to Identity Address" ],
    "LL/SEC/ADV/BV-17-C": [ ll_sec_adv_bv_17_c, "Directed Connectable Advertising using local and remote IRK, Ignore Identity Address" ],
    "LL/SEC/ADV/BV-18-C": [ ll_sec_adv_bv_18_c, "Scannable Advertising with resolvable private address, accept Identity Address" ],
#   "LL/SEC/ADV/BV-19-C": [ ll_sec_adv_bv_19_c, "Undirected Connectable Advertising with Local IRK and Peer IRK, accept Identity Address" ],
    "LL/SEC/ADV/BV-20-C": [ ll_sec_adv_bv_20_c, "Directed Connectable Advertising with resolvable private address; Connect to Identity Address" ],
    "LL/SEC/SCN/BV-01-C": [ ll_sec_scn_bv_01_c, "Changing Static Address while Scanning" ],
    "LL/CIS/CEN/BV-01-C": [ ll_cis_cen_bv_01_c, "CIS Setup Procedure, Central Initiated" ],
    "LL/CIS/CEN/BV-02-C": [ ll_cis_cen_bv_02_c, "CIS Setup Procedure, Central Initiated" ],
    "LL/CIS/CEN/BV-31-C": [ ll_cis_cen_bv_31_c, "CIS Setup Procedure, Central Initiated" ],
    "LL/CIS/CEN/BV-39-C": [ ll_cis_cen_bv_39_c, "CIS Setup Procedure, Central Initiated" ],
    "LL/CIS/CEN/BV-03-C": [ ll_cis_cen_bv_03_c, "CIS Setup Procedure, Central Initiated, Rejected" ],
    "LL/CIS/CEN/BV-04-C": [ ll_cis_cen_bv_04_c, "New Channel Map" ],
    "LL/CIS/CEN/BV-40-C": [ ll_cis_cen_bv_40_c, "New Channel Map" ],
    "LL/CIS/CEN/BV-20-C": [ ll_cis_cen_bv_20_c, "Set Encryption After CIS Established" ],
    "LL/CIS/CEN/BV-26-C": [ ll_cis_cen_bv_26_c, "Connected Isochronous Stream Using Non-Test Command, Central Initiated" ],
    "LL/CIS/CEN/BV-27-C": [ ll_cis_cen_bv_27_c, "Connected Isochronous Stream Using Non-Test Command, Central Initiated" ],
    "LL/CIS/CEN/BV-30-C": [ ll_cis_cen_bv_30_c, "Isochronous Channels Host Support Feature Bit" ],
    "LL/CIS/CEN/BV-08-C": [ ll_cis_cen_bv_08_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG, Central" ],
    "LL/CIS/CEN/BV-43-C": [ ll_cis_cen_bv_43_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG, Central" ],
    "LL/CIS/CEN/BV-09-C": [ ll_cis_cen_bv_09_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Sequential, Central" ],
    "LL/CIS/CEN/BV-36-C": [ ll_cis_cen_bv_36_c, "Connected Isochronous Stream Using Non-Test Command, Force Framed PDUs" ],
    "LL/CIS/CEN/BV-37-C": [ ll_cis_cen_bv_37_c, "Connected Isochronous Stream Using Non-Test Command, Force Framed PDUs" ],
    "LL/CIS/CEN/BV-44-C": [ ll_cis_cen_bv_44_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG, Central, BN > 1, NSE = 2" ],
    # "LL/CIS/CEN/BV-51-C": [ ll_cis_cen_bv_51_c, "CIS Setup Procedure, Central Initiated, CIG ID Reuse" ],
    "LL/CIS/CEN/BV-06-C": [ ll_cis_cen_bv_06_c, "Receiving data in Unidirectional CIS" ],
    "LL/CIS/CEN/BV-07-C": [ ll_cis_cen_bv_07_c, "Sending and Receiving Data in Bidirectional CIS" ],
    "LL/CIS/CEN/BV-35-C": [ ll_cis_cen_bv_35_c, "Sending and Receiving Data in Bidirectional CIS, Encryption" ],
    # "LL/CIS/CEN/BV-15-C": [ ll_cis_cen_bv_15_c, "CIS Terminate Procedure, Initiated" ],
    "LL/CIS/CEN/BV-16-C": [ ll_cis_cen_bv_16_c, "CIS Terminate Procedure, Accepting" ],
    "LL/CIS/CEN/BV-24-C": [ ll_cis_cen_bv_24_c, "CIS Updating Peer Clock Accuracy" ],
    "LL/CIS/CEN/BV-45-C": [ ll_cis_cen_bv_45_c, "Sending Data in Unidirectional CIS, BN = 1" ],
    "LL/CIS/CEN/BV-46-C": [ ll_cis_cen_bv_46_c, "Receiving Data in Unidirectional CIS, BN = 1" ],
    "LL/CIS/CEN/BV-47-C": [ ll_cis_cen_bv_47_c, "Sending and Receiving Data in Bidirectional CIS, BN = 1" ],
    "LL/CIS/CEN/BV-48-C": [ ll_cis_cen_bv_48_c, "Sending and Receiving Data in Bidirectional CIS, BN = 1, Encryption" ],
    "LL/CIS/PER/BV-01-C": [ ll_cis_per_bv_01_c, "CIS Setup Response Procedure, Peripheral" ],
    "LL/CIS/PER/BV-02-C": [ ll_cis_per_bv_02_c, "CIS Setup Response Procedure, Peripheral, Reject Response" ],
    "LL/CIS/PER/BV-03-C": [ ll_cis_per_bv_03_c, "CIS Map Update" ],
    "LL/CIS/PER/BV-05-C": [ ll_cis_per_bv_05_c, "Receiving data in Unidirectional CIS" ],
    "LL/CIS/PER/BV-06-C": [ ll_cis_per_bv_06_c, "Sending and Receiving Data in Bidirectional CIS" ],
#   "LL/CIS/PER/BV-07-C": [ ll_cis_per_bv_07_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG, Peripheral" ],  # https://github.com/EDTTool/packetcraft/issues/12, https://github.com/EDTTool/packetcraft/issues/15
#   "LL/CIS/PER/BV-08-C": [ ll_cis_per_bv_08_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Sequential, Peripheral" ],  # https://github.com/EDTTool/EDTT-le-audio/issues/72
    "LL/CIS/PER/BV-18-C": [ ll_cis_per_bv_18_c, "CIS Updating Peer Clock Accuracy" ],
    "LL/CIS/PER/BV-19-C": [ ll_cis_per_bv_19_c, "CIS Setup Response Procedure, Peripheral" ],
    "LL/CIS/PER/BV-22-C": [ ll_cis_per_bv_22_c, "CIS Request Event Not Set" ],
    "LL/CIS/PER/BV-23-C": [ ll_cis_per_bv_23_c, "CIS Setup Response Procedure, Peripheral" ],
#   "LL/CIS/PER/BV-27-C": [ ll_cis_per_bv_27_c, "Sending and Receiving Data in Bidirectional CIS, Encryption" ],  # https://github.com/EDTTool/EDTT-le-audio/issues/75
    "LL/CIS/PER/BV-29-C": [ ll_cis_per_bv_29_c, "CIS Setup Response Procedure, Peripheral" ],
    "LL/CIS/PER/BV-31-C": [ ll_cis_per_bv_31_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Interleaved CIG, Peripheral, NSE=2" ],
    "LL/CIS/PER/BV-32-C": [ ll_cis_per_bv_32_c, "Sending and Receiving Data in Multiple CISes, Single CIG, Single Connection, Peripheral, BN=1" ],
    "LL/CIS/PER/BV-33-C": [ ll_cis_per_bv_33_c, "Sending Data in Unidirectional CIS, BN = 1" ],
    "LL/CIS/PER/BV-34-C": [ ll_cis_per_bv_34_c, "Receiving Data in Unidirectional CIS, BN = 1" ],
    "LL/CIS/PER/BV-35-C": [ ll_cis_per_bv_35_c, "Sending and Receiving Data in Bidirectional CIS, BN = 1" ],
    "LL/CIS/PER/BV-36-C": [ ll_cis_per_bv_36_c, "Sending and Receiving Data in Bidirectional CIS, BN = 1, Encryption "],
    "LL/CIS/PER/BV-37-C": [ ll_cis_per_bv_37_c, "CIS Map Update" ],
#   "LL/CIS/PER/BV-39-C": [ ll_cis_per_bv_39_c, "CIS Peripheral Accepts All Supported NSE Values" ],  # https://github.com/EDTTool/EDTT-le-audio/issues/84
#   "LL/CIS/PER/BV-40-C": [ ll_cis_per_bv_40_c, "CIS Setup Response Procedure, Peripheral" ],  # https://github.com/EDTTool/EDTT-le-audio/issues/85
    "LL/CIS/PER/BV-12-C": [ ll_cis_per_bv_12_c, "CIS Terminate Procedure, Initiated - Peripheral" ],
    "LL/CIS/PER/BV-13-C": [ ll_cis_per_bv_13_c, "CIS Terminate Procedure, Accepting, Peripheral" ],
#   "LL/IST/CEN/BV-01-C": [ ll_ist_cen_bv_01_c, "ISO Transmit Test Mode, CIS" ],  # https://github.com/EDTTool/EDTT-le-audio/issues/86
#   "LL/IST/PER/BV-01-C": [ ll_ist_per_bv_01_c, "ISO Transmit Test Mode, CIS" ],  # https://github.com/EDTTool/EDTT-le-audio/issues/86
#   "LL/IST/CEN/BV-03-C": [ ll_ist_cen_bv_03_c, "ISO Receive Test Mode, CIS" ],  # https://github.com/EDTTool/packetcraft/issues/10
#   "LL/IST/PER/BV-03-C": [ ll_ist_per_bv_03_c, "ISO Receive Test Mode, CIS" ],  # https://github.com/EDTTool/packetcraft/issues/10
    "LL/TIM/ADV/BV-03-C": [ ll_tim_adv_bv_03_c, "Extended Advertising, Secondary Channel, Earliest Transmission to Advertiser - LE 1M PHY", LowLevelDeviceRequired],
    "LL/TIM/ADV/BV-04-C": [ ll_tim_adv_bv_04_c, "Extended Advertising, Secondary Channel, Latest Transmission to Advertiser - LE 1M PHY", LowLevelDeviceRequired],
    "LL/TIM/ADV/BV-05-C": [ ll_tim_adv_bv_05_c, "Extended Advertising, Secondary Channel, Earliest Transmission to Advertiser - LE 2M PHY", LowLevelDeviceRequired],
    "LL/TIM/ADV/BV-07-C": [ ll_tim_adv_bv_07_c, "Extended Advertising, Secondary Channel, Latest Transmission to Advertiser - LE 2M PHY", LowLevelDeviceRequired],
};

_maxNameLength = max([ len(key) for key in __tests__ ]);

_spec = {key: TestSpec(name=key, number_devices=2, description="#[" + __tests__[key][1] + "]",
                       test_private=__tests__[key][0],
                       require_low_level_device=len(__tests__[key]) >= 3 and __tests__[key][2] == LowLevelDeviceRequired) for key in __tests__}

"""
    Return the test spec which contains info about all the tests
    this test module provides
"""
def get_tests_specs():
    return _spec;

def preamble(transport, trace):
    global lowerIRK, upperIRK, ENC_KEYS

    ok = success = preamble_standby(transport, 0, trace);
    trace.trace(4, "preamble Standby " + ("PASS" if success else "FAIL"));
    success = preamble_standby(transport, 1, trace);
    ok = ok and success;
    trace.trace(4, "preamble Standby " + ("PASS" if success else "FAIL"));
    success, upperIRK, tests.test_utils.upperRandomAddress = preamble_device_address_set(transport, 0, trace);
    trace.trace(4, "preamble Device Address Set " + ("PASS" if success else "FAIL"));
    ok = ok and success;
    success, lowerIRK, tests.test_utils.lowerRandomAddress = preamble_device_address_set(transport, 1, trace);
    trace.trace(4, "preamble Device Address Set " + ("PASS" if success else "FAIL"));
    ok = ok and success
    success, *ENC_KEYS = preamble_encryption_keys_calculated(transport, 1, trace)
    trace.trace(4, "preamble Encryption Keys Calculated " + ("PASS" if success else "FAIL"))
    return ok and success;

"""
    Run a test given its test_spec
"""
def run_a_test(args, transport, trace, test_spec, device_dumps):
    try:
        success = preamble(transport, trace);
    except Exception as e:
        trace.trace(3, "Preamble generated exception: %s" % str(e));
        success = False;

    trace.trace(2, "%-*s %s test started..." % (_maxNameLength, test_spec.name, test_spec.description[1:]));
    if not success:
        trace.trace(3, "Preamble failed, actual test function will not run" );
    test_f = test_spec.test_private;
    try:
        if test_spec.require_low_level_device and not transport.low_level_device:
            trace.trace(1, "Error: Test case requires a low level device, but transport does not have one!")
            success = False
        elif test_f.__code__.co_argcount > 4:
            success = success and test_f(transport, 0, 1, trace, device_dumps);
        elif test_f.__code__.co_argcount > 3:
            success = success and test_f(transport, 0, 1, trace);
        else:
            success = success and test_f(transport, 0, trace);
    except Exception as e:
        import traceback
        traceback.print_exc()
        trace.trace(3, "Test generated exception: %s" % str(e));
        success = False;

    return not success

