Ejemplo n.º 1
0
    def test_WINNF_FT_S_SSS_15(self, config_filename):
        """Certificate with inapplicable fields presented during Full Activity Dump 
    """
        config = loadConfig(config_filename)

        # Reset the SAS UUT
        self.SasReset()

        # Notify SAS UUT about peer SAS
        certificate_hash = getCertificateFingerprint(config['sasCert'])
        self._sas_admin.InjectPeerSas({'certificateHash': certificate_hash,\
                                         'url': SAS_TEST_HARNESS_URL })
        self.assertTlsHandshakeSucceed(self._sas_admin._base_url,
                                       ['AES128-GCM-SHA256'],
                                       config['sasCert'], config['sasKey'])
        trigger_succeed = False
        # Initiate Full Activity Dump
        try:
            self.TriggerFullActivityDumpAndWaitUntilComplete(
                config['sasCert'], config['sasKey'])
            trigger_succeed = True
        except HTTPError as e:
            # Check if HTTP status is 403
            self.assertEqual(e.error_code, 403)
        self.assertFalse(trigger_succeed,
                         "Full Activity Dump is expected to fail")
Ejemplo n.º 2
0
    def test_WINNF_FT_S_SSS_11(self, config_filename):
        """Blacklisted certificate presented by SAS Test Harness.

    Checks that SAS UUT response with fatal alert message.
    """
        # Reset SAS UUT
        self.SasReset()

        # Read the configuration
        config = loadConfig(config_filename)

        # Read the fingerprint from the certificate
        certificate_hash = getCertificateFingerprint(config['sasCert'])
        self._sas_admin.InjectPeerSas({
            'certificateHash': certificate_hash,
            'url': SAS_TEST_HARNESS_URL
        })

        # Tls handshake fails or Http 403
        self.assertTlsHandshakeFailureOrHttp403(client_cert=config['sasCert'],
                                                client_key=config['sasKey'],
                                                is_sas=True)

        logging.info(
            "TLS handshake failed as the sas certificate has blacklisted")
Ejemplo n.º 3
0
    def test_WINNF_FT_S_SSS_10(self, config_filename):
        """Certificate of wrong type presented during Full Activity Dump.

    Checks that SAS UUT response with fatal alert message.
    """
        config = loadConfig(config_filename)

        # Reset the SAS UUT
        self.SasReset()

        # Notify SAS UUT about peer SAS
        certificate_hash = getCertificateFingerprint(config['sasCert'])
        self._sas_admin.InjectPeerSas({'certificateHash': certificate_hash,\
                                         'url': SAS_TEST_HARNESS_URL})
        try:
            self.assertTlsHandshakeFailure(client_cert=config['sasCert'],
                                           client_key=config['sasKey'])
        except AssertionError:
            trigger_succeed = False
            try:
                self.TriggerFullActivityDumpAndWaitUntilComplete(
                    config['sasCert'], config['sasKey'])
                trigger_succeed = True
            except HTTPError as e:
                # Check if HTTP status is 403
                self.assertEqual(e.error_code, 403)
            self.assertFalse(trigger_succeed,
                             "Full Activity Dump is expected to fail")
Ejemplo n.º 4
0
    def test_WINNF_FT_S_SSS_12(self, config_filename):
        """Expired certificate presented by SAS Test Harness.

    Checks that SAS UUT response with fatal alert message.
    """
        self.SasReset()
        config = loadConfig(config_filename)
        certificate_hash = getCertificateFingerprint(config['sasCert'])
        self._sas_admin.InjectPeerSas({
            'certificateHash': certificate_hash,
            'url': SAS_TEST_HARNESS_URL
        })
        self.assertTlsHandshakeFailureOrHttp403(client_cert=config['sasCert'],
                                                client_key=config['sasKey'],
                                                is_sas=True)
    def test_WINNF_FT_S_SSS_18(self, config_filename):
        """Security requirement for Full Activity Dump message Request
    """
        config = loadConfig(config_filename)

        # Reset the SAS UUT
        self.SasReset()

        # Notify SAS UUT about peer SAS
        certificate_hash = getCertificateFingerprint(
            config['validCertKeyPair']['cert'])
        self._sas_admin.InjectPeerSas({'certificateHash': certificate_hash,\
                                         'url': SAS_TEST_HARNESS_URL })
        # Load a Device
        device_a = json.load(
            open(os.path.join('testcases', 'testdata', 'device_a.json')))

        # Load grant request
        grant_a = json.load(
            open(os.path.join('testcases', 'testdata', 'grant_0.json')))
        cbsd_ids, grant_ids = self.assertRegisteredAndGranted([device_a],
                                                              [grant_a])

        # Initiate Full Activity Dump with valid cert, key pair
        fadResponse =  self.TriggerFullActivityDumpAndWaitUntilComplete(config['validCertKeyPair']['cert'], \
                                                                        config['validCertKeyPair']['key'])

        self.assertContainsRequiredFields("FullActivityDump.schema.json",
                                          fadResponse)
        url = fadResponse['files'][0]['url']

        # Attempting FAD record download with different invalid cert,key pair
        try:
            # This uses the same base_url as GetFullActivityDump().
            self.assertTlsHandshakeFailure(
                self._sas.sas_sas_active_base_url,
                client_cert=config['invalidCertKeyPair']['cert'],
                client_key=config['invalidCertKeyPair']['key'])
        except AssertionError as e:
            try:
                self._sas.DownloadFile(url,config['invalidCertKeyPair']['cert'], \
                                                        config['invalidCertKeyPair']['key'])
            except HTTPError as e:
                self.assertEqual(e.error_code, 403)
            else:
                self.fail("FAD record file download should have failed")
Ejemplo n.º 6
0
    def test_WINNF_FT_S_SSS_13(self, config_filename):
        """ Disallowed TLS method attempted during registration.

        Checks that SAS UUT response with fatal alert message.
    """
        self.SasReset()

        # Load the configuration
        config = loadConfig(config_filename)

        # Read the fingerprint from the certificate
        certificate_hash = getCertificateFingerprint(config['sasCert'])
        self._sas_admin.InjectPeerSas({
            'certificateHash': certificate_hash,
            'url': SAS_TEST_HARNESS_URL
        })
        self.assertTlsHandshakeFailureOrHttp403(client_cert=config['sasCert'],
                                                client_key=config['sasKey'],
                                                ssl_method=SSL.TLSv1_1_METHOD,
                                                is_sas=True)
Ejemplo n.º 7
0
    def test_WINNF_FT_S_SSS_14(self, config_filename):
        """ Invalid cipher suite presented during registration.

        Checks that SAS UUT response with fatal alert message.
    """
        self.SasReset()

        # Load the configuration
        config = loadConfig(config_filename)

        # Read the fingerprint from the certificate
        certificate_hash = getCertificateFingerprint(config['sasCert'])
        self._sas_admin.InjectPeerSas({
            'certificateHash': certificate_hash,
            'url': SAS_TEST_HARNESS_URL
        })
        self.assertTlsHandshakeFailureOrHttp403(
            client_cert=config['sasCert'],
            client_key=config['sasKey'],
            ciphers='ECDHE-RSA-AES256-GCM-SHA384',
            is_sas=True)
    def triggerFadGenerationAndRetrievePpaZone(self, ssl_cert, ssl_key):
        """Triggers FAD and Retrieves PPA Zone Record.

    Pulls FAD from SAS UUT. Retrieves the ZoneData Records from FAD,
    checks that only one record is present.

    Args:
      ssl_cert: Path to SAS type cert file to be used for pulling FAD record.
      ssl_key: Path to SAS type key file to be used for pulling FAD record.

    Returns:
      A PPA record of format of ZoneData Object.

    """
        # Notify the SAS UUT about the SAS Test Harness.
        certificate_hash = getCertificateFingerprint(ssl_cert)
        self._sas_admin.InjectPeerSas({
            'certificateHash': certificate_hash,
            'url': SAS_TEST_HARNESS_URL
        })

        # As SAS is reset at the beginning of the test, the FAD records should
        # contain only one zone record containing the PPA that was generated.
        # Hence the first zone record is retrieved.
        uut_fad = getFullActivityDumpSasUut(self._sas, self._sas_admin,
                                            ssl_cert, ssl_key)

        # Check if the retrieved FAD that has valid and at least one
        # PPA zone record.
        uut_ppa_zone_data = uut_fad.getZoneRecords()
        self.assertEquals(
            len(uut_ppa_zone_data),
            1,
            msg='There is no single PPA Zone record received from SAS'
            ' UUT')

        return uut_ppa_zone_data[0]
    def doSasTestCipher(self, cipher, client_cert, client_key, client_url):
        """Does a cipher test as described in SSS tests 1 to 5 specification.

    Args:
      cipher: the cipher openSSL string name to test.
      client_cert: path to (peer) SAS client certificate file in PEM format to use.
      client_key: path to associated key file in PEM format to use.
      client_url: base URL of the (peer) SAS client.
    """
        self._sas.UpdateSasRequestUrl(cipher)

        # Does a regular SAS registration
        self.SasReset()
        certificate_hash = getCertificateFingerprint(client_cert)
        self._sas_admin.InjectPeerSas({
            'certificateHash': certificate_hash,
            'url': client_url
        })
        # Using pyOpenSSL low level API, does the SAS UUT server TLS session checks.
        self.assertTlsHandshakeSucceed(self._sas.sas_sas_active_base_url,
                                       [cipher], client_cert, client_key)
        self._sas_admin.TriggerFullActivityDump()
        with CiphersOverload(self._sas, [cipher], client_cert, client_key):
            self._sas.GetFullActivityDump(client_cert, client_key)
    def test_WINNF_FT_S_FAD_2(self, config_filename):
        """Full Activity Dump Pull Command by SAS UUT.

    Checks SAS UUT can successfully request a Full Activity Dump and utilize
    the retrieved data.
    Checks SAS UUT responds with HeartbeatResponse either GRANT_TERMINATED or INVALID_VALUE,
    indicating a terminated Grant.

    """

        config = loadConfig(config_filename)

        device_c2 = config['registrationRequestC2']
        device_c4 = config['registrationRequestC4']
        grant_g2 = config['grantRequestG2']
        grant_g4 = config['grantRequestG4']
        sas_test_harness_dump_records = [
            config['sasTestHarnessDumpRecords']['cbsdRecords'],
            config['sasTestHarnessDumpRecords']['ppaRecords'],
            config['sasTestHarnessDumpRecords']['escSensorRecords']
        ]

        # Initialize SAS Test Harness Server instance to dump FAD records
        sas_test_harness = SasTestHarnessServer(
            config['sasTestHarnessConfig']['sasTestHarnessName'],
            config['sasTestHarnessConfig']['hostName'],
            config['sasTestHarnessConfig']['port'],
            config['sasTestHarnessConfig']['serverCert'],
            config['sasTestHarnessConfig']['serverKey'],
            config['sasTestHarnessConfig']['caCert'])
        self.InjectTestHarnessFccIds(
            config['sasTestHarnessDumpRecords']['cbsdRecords'])
        sas_test_harness.writeFadRecords(sas_test_harness_dump_records)

        # Start the server
        sas_test_harness.start()

        # Whitelist the FCCID and UserID for CBSD C2 in SAS UUT
        self._sas_admin.InjectFccId({'fccId': device_c2['fccId']})
        self._sas_admin.InjectUserId({'userId': device_c2['userId']})

        # Whitelist the FCCID and UserID for CBSD C4 in SAS UUT
        self._sas_admin.InjectFccId({'fccId': device_c4['fccId']})
        self._sas_admin.InjectUserId({'userId': device_c4['userId']})

        # Whitelist the FCCID and UserID of the devices loaded to SAS-test_harness in SAS UUT
        for cbsdRecord in config['sasTestHarnessDumpRecords']['cbsdRecords']:
            self._sas_admin.InjectFccId(
                {'fccId': cbsdRecord['registration']['fccId']})

        # Pre-load conditional registration data for C2 and C4 CBSDs.
        if ('conditionalRegistrationData'
                in config) and (config['conditionalRegistrationData']):
            self._sas_admin.PreloadRegistrationData(
                config['conditionalRegistrationData'])

        # Register devices C2 and C4, request grants G2 and G4 respectively with SAS UUT.
        # Ensure the registration and grant requests are successful.
        try:
            cbsd_ids, grant_ids = self.assertRegisteredAndGranted(
                [device_c2, device_c4], [grant_g2, grant_g4])
        except Exception:
            logging.error(
                common_strings.EXPECTED_SUCCESSFUL_REGISTRATION_AND_GRANT)
            raise

        # Send the Heartbeat request for the Grant G2 and G4 of CBSD C2 and C4
        # respectively to SAS UUT.
        transmit_expire_times = self.assertHeartbeatsSuccessful(
            cbsd_ids, grant_ids, ['GRANTED', 'GRANTED'])

        # Notify the SAS UUT about the SAS Test Harness
        certificate_hash = getCertificateFingerprint(
            config['sasTestHarnessConfig']['serverCert'])
        self._sas_admin.InjectPeerSas({
            'certificateHash': certificate_hash,
            'url': sas_test_harness.getBaseUrl()
        })

        # Injecting the PAL Records of the PPA into SAS UUT.
        for pal_record in config['palRecords']:
            try:
                self._sas_admin.InjectPalDatabaseRecord(pal_record)
            except Exception:
                logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
                raise

        # Trigger CPAS in the SAS UUT and wait until complete.
        self.TriggerDailyActivitiesImmediatelyAndWaitUntilComplete()

        # Send the Heartbeat request again for the G2 and G4 to SAS UUT
        request = {
            'heartbeatRequest': [{
                'cbsdId': cbsd_ids[0],
                'grantId': grant_ids[0],
                'operationState': 'GRANTED'
            }, {
                'cbsdId': cbsd_ids[1],
                'grantId': grant_ids[1],
                'operationState': 'GRANTED'
            }]
        }
        response = self._sas.Heartbeat(request)['heartbeatResponse']

        # Check the length of request and response match.
        self.assertEqual(len(request['heartbeatRequest']), len(response))

        # Check grant response, must be response code 0.
        for resp in response:
            self.assertTrue(resp['response']['responseCode'] in (103, 500))

        # As Python garbage collector is not very consistent, directory is not getting deleted.
        # Hence, explicitly stopping SAS Test Hanress and cleaning up
        sas_test_harness.shutdown()
        del sas_test_harness
    def test_WINNF_FT_S_FAD_1(self, config_filename):
        """ This test verifies that a SAS UUT can successfully respond to a full
            activity dump request from a SAS Test Harness

		  SAS UUT approves the request and responds,
		  with correct content and format for both dump message and files
      """
        # load config file
        config = loadConfig(config_filename)
        # Very light checking of the config file.
        self.assertEqual(len(config['registrationRequests']),
                         len(config['grantRequests']))
        # check that the config file contains consistent PAL&PPA data
        for index, grant in enumerate(config['grantRequests']):
            grant_frequency_range = grant['operationParam'][
                'operationFrequencyRange']
            for ppa in config['ppaRecords']:
                if index in ppa['ppaClusterList']:
                    frequency_ranges_of_pals = [{'frequencyRange' : {'lowFrequency' : pal['channelAssignment']['primaryAssignment']['lowFrequency'],\
                      'highFrequency' : pal['channelAssignment']['primaryAssignment']['highFrequency']}} \
                      for pal in config['palRecords'] if pal['palId'] in ppa['ppaRecord']['ppaInfo']['palId']]
                    self.assertLessEqual(
                        1, frequency_ranges_of_pals,
                        'Empty list of Frequency Ranges in the PAL config')
                    low_freq = min([
                        freq_range['frequencyRange']['lowFrequency']
                        for freq_range in frequency_ranges_of_pals
                    ])
                    high_freq = max([
                        freq_range['frequencyRange']['highFrequency']
                        for freq_range in frequency_ranges_of_pals
                    ])
                    self.assertChannelsContainFrequencyRange(
                        frequency_ranges_of_pals, {
                            'lowFrequency': low_freq,
                            'highFrequency': high_freq
                        })
                    # check that the grant in config file is not mixed of PAL and GAA channels
                    if low_freq <= grant_frequency_range[
                            'lowFrequency'] <= high_freq:
                        self.assertLessEqual(
                            grant_frequency_range['highFrequency'], high_freq,
                            'incorrect high frequency of the grant with index {0}, makes it GAA&PAL Mixed Grant'
                            .format(index))
                    if low_freq <= grant_frequency_range[
                            'highFrequency'] <= high_freq:
                        self.assertGreaterEqual(
                            grant_frequency_range['lowFrequency'], low_freq,
                            'incorrect low frequency of the grant with index {0}, makes it a GAA&PAL Mixed Grant'
                            .format(index))
        for index, device in enumerate(config['registrationRequests']):
            # check azimuth in the CBSD config, if the beamwidth is not 0 or 360 and the azimuth is not provided, CBSD registration may be rejected
            reg_conditional_device_data_list = [reg for reg in \
                  config['conditionalRegistrationData']['registrationData'] if reg['fccId'] == device['fccId'] and \
                  reg['cbsdSerialNumber'] == device['cbsdSerialNumber'] ]
            if len(reg_conditional_device_data_list) == 1:
                reg_conditional_installation_param = reg_conditional_device_data_list[
                    0]['installationParam']
            elif len(reg_conditional_device_data_list) > 1:
                self.fail(
                    'invalid conditional Registration Data, multi conditional Registration configs for the cbsd with index: {0} '
                    .format(index))
            else:
                reg_conditional_installation_param = {}
            registeration_antenna_azimuth = device['installationParam']['antennaAzimuth'] \
                if 'installationParam' in device and 'antennaAzimuth' in device['installationParam'] \
                else reg_conditional_installation_param['antennaAzimuth'] \
                if 'antennaAzimuth' in reg_conditional_installation_param else None
            registeration_antenna_beamwidth = device['installationParam']['antennaBeamwidth'] \
                if  'installationParam' in device and 'antennaBeamwidth' in device['installationParam'] \
                else reg_conditional_installation_param['antennaBeamwidth'] \
                if 'antennaBeamwidth' in reg_conditional_installation_param else None
            if registeration_antenna_beamwidth != None and registeration_antenna_beamwidth not in [0, 360]\
              and registeration_antenna_azimuth is None:
                self.fail(
                    'invalid config, missing azimuth value for CBSD config with index: {0} '
                    .format(index))
            # inject FCC ID and User ID of CBSD
            self._sas_admin.InjectFccId({
                'fccId': device['fccId'],
                'fccMaxEirp': 47
            })
            self._sas_admin.InjectUserId({'userId': device['userId']})

        # Pre-load conditional registration data for N3 CBSDs.
        self._sas_admin.PreloadRegistrationData(
            config['conditionalRegistrationData'])

        # Register N1 CBSDs.
        request = {'registrationRequest': config['registrationRequests']}
        responses = self._sas.Registration(request)['registrationResponse']

        # Check registration responses and get cbsd Id
        self.assertEqual(len(responses), len(config['registrationRequests']))
        cbsd_ids = []
        for response in responses:
            self.assertEqual(response['response']['responseCode'], 0)
            cbsd_ids.append(response['cbsdId'])
        # inject PALs and N2 PPAs
        ppa_ids = []
        for pal in config['palRecords']:
            try:
                self._sas_admin.InjectPalDatabaseRecord(pal)
            except Exception:
                logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
                raise
        for ppa in config['ppaRecords']:
            # fill the PPA cbsdReferenceIds with values according to admin testing API spec
            ppa['ppaRecord']['ppaInfo']['cbsdReferenceId'] = []
            for index in ppa['ppaClusterList']:
                ppa['ppaRecord']['ppaInfo']['cbsdReferenceId'].append(
                    cbsd_ids[index])
            try:
                ppa_ids.append(
                    self._sas_admin.InjectZoneData(
                        {'record': ppa['ppaRecord']}))
            except Exception:
                logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
                raise
            # re-fill the PPA cbsdReferenceIds with the values expected in the dump according to SAS-SAS TS
            ppa['ppaRecord']['ppaInfo']['cbsdReferenceId'] = []
            for index in ppa['ppaClusterList']:
                cbsd = config['registrationRequests'][index]
                ppa['ppaRecord']['ppaInfo']['cbsdReferenceId'].append(
                    generateCbsdReferenceId(cbsd['fccId'],
                                            cbsd['cbsdSerialNumber']))
        grants = config['grantRequests']
        for index, response in enumerate(responses):
            self.assertEqual(response['response']['responseCode'], 0)
            grants[index]['cbsdId'] = response['cbsdId']
        # send grant request with N1 grants
        del responses
        grant_responses = self._sas.Grant({'grantRequest':
                                           grants})['grantResponse']
        # check grant response
        self.assertEqual(len(grant_responses), len(config['grantRequests']))
        for grant_response in grant_responses:
            self.assertEqual(grant_response['response']['responseCode'], 0)
        # inject N3 Esc sensor
        for esc_sensor in config['escSensorRecords']:
            try:
                self._sas_admin.InjectEscSensorDataRecord(
                    {'record': esc_sensor})
            except Exception:
                logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
                raise
        # step 7
        # Notify the SAS UUT about the SAS Test Harness
        for sas_th in config['sasTestHarnessConfigs']:
            certificate_hash = getCertificateFingerprint(sas_th['serverCert'])
            url = 'https://' + sas_th['hostName'] + ':' + str(sas_th['port'])
            self._sas_admin.InjectPeerSas({
                'certificateHash': certificate_hash,
                'url': url
            })
        sas_th_config = config['sasTestHarnessConfigs'][0]
        response = self.TriggerFullActivityDumpAndWaitUntilComplete(
            sas_th_config['serverCert'], sas_th_config['serverKey'])
        # verify that all the SASes get the same response :
        # check dump message format
        self.assertContainsRequiredFields("FullActivityDump.schema.json",
                                          response)
        # an array for each record type
        cbsd_dump_data = []
        ppa_dump_data = []
        esc_sensor_dump_data = []
        # step 8 and check
        # download dump files and fill corresponding arrays
        downloaded_files = {}
        for dump_file in response['files']:
            self.assertContainsRequiredFields("ActivityDumpFile.schema.json",
                                              dump_file)
            downloaded_file = None
            if dump_file['recordType'] != 'coordination':
                downloaded_file = self._sas.DownloadFile(dump_file['url'],\
                  sas_th_config['serverCert'], sas_th_config['serverKey'])
                # The downloaded_file is being modified in the assertions below,
                # and hence we need a deep copy to verify that dump files are the
                # same when requested by different SASs.
                downloaded_files[dump_file['url']] = copy.deepcopy(
                    downloaded_file)
            if dump_file['recordType'] == 'cbsd':
                cbsd_dump_data.extend(downloaded_file['recordData'])
            elif dump_file['recordType'] == 'esc_sensor':
                esc_sensor_dump_data.extend(downloaded_file['recordData'])
            elif dump_file['recordType'] == 'zone':
                ppa_dump_data.extend(downloaded_file['recordData'])
            else:
                self.assertEqual('coordination', dump_file['recordType'])
        # verify the length of records equal to the inserted ones
        self.assertEqual(len(config['registrationRequests']),
                         len(cbsd_dump_data))
        self.assertEqual(len(config['ppaRecords']), len(ppa_dump_data))
        self.assertEqual(len(config['escSensorRecords']),
                         len(esc_sensor_dump_data))
        # verify the schema of record and first two parts of PPA record Id
        for ppa_record in ppa_dump_data:
            self.assertContainsRequiredFields("ZoneData.schema.json",
                                              ppa_record)
            self.assertEqual(ppa_record['id'].split("/")[0], 'zone')
            self.assertEqual(ppa_record['id'].split("/")[1], 'ppa')
            self.assertEqual(ppa_record['id'].split("/")[2],
                             self._sas._sas_admin_id)
            del ppa_record['id']
            # remove creator from value check
            del ppa_record['creator']
            # verify that the injected ppas exist in the dump files
            # check GeoJson Winding of PPA record
            utils.HasCorrectGeoJsonWinding(
                ppa_record['zone']['features'][0]['geometry'])
            exist_in_dump = False
            for ppa_conf in config['ppaRecords']:
                ppa = ppa_conf['ppaRecord']
                if 'id' in ppa:
                    del ppa['id']
                # remove creator from value check
                if 'creator' in ppa:
                    del ppa['creator']
                exist_in_dump = exist_in_dump or areTwoPpasEqual(
                    ppa_record, ppa)
            if exist_in_dump:
                break
            self.assertTrue(exist_in_dump)
        # verify the schema of record and two first parts of esc sensor record  Id
        for esc_record in esc_sensor_dump_data:
            self.assertContainsRequiredFields("EscSensorRecord.schema.json",
                                              esc_record)
            self.assertEqual(esc_record['id'].split("/")[0], 'esc_sensor')
            self.assertEqual(esc_record['id'].split("/")[1],
                             self._sas._sas_admin_id)
            del esc_record['id']
            # verify that all the injected Esc sensors exist in the dump files
            exist_in_dump = False
            for esc in config['escSensorRecords']:
                if 'id' in esc:
                    del esc['id']
                exist_in_dump = exist_in_dump or compareDictWithUnorderedLists(
                    esc_record, esc)
                if exist_in_dump:
                    break
            self.assertTrue(exist_in_dump)

        # verify that retrieved cbsd dump files have correct schema
        for cbsd_record in cbsd_dump_data:
            self.assertContainsRequiredFields("CbsdData.schema.json",
                                              cbsd_record)
            self.assertFalse("cbsdInfo" in cbsd_record)
        # verify all the previous activities on CBSDs and Grants exist in the dump files
        self.assertCbsdRecord(config['registrationRequests'], grants,
                              grant_responses, cbsd_dump_data,
                              config['conditionalRegistrationData'])
        # step 10 check all SAS Test Harnesses retrieve all of the data in the Full Activity Dump from the SAS UUT
        for sas_th in config['sasTestHarnessConfigs'][1:]:
            dump_message = self._sas.GetFullActivityDump(
                sas_th['serverCert'], sas_th['serverKey'])
            # check that dump message is the same as the message retreived by the first SAS TH
            compareDictWithUnorderedLists(response, dump_message)
            # check that dump files are the same as the files retreived by the first SAS TH
            for dump_file in dump_message['files']:
                if dump_file['recordType'] != 'coordination':
                    downloaded_file = self._sas.DownloadFile(dump_file['url'],\
                      sas_th['serverCert'], sas_th['serverKey'])
                    self.assertDictEqual(downloaded_files[dump_file['url']],
                                         downloaded_file)
  def test_WINNF_FT_S_PPR_3(self, config_filename):
    config = loadConfig(config_filename)

    # Light config checking.
    self.assertValidConfig(
        config, {
            'domainProxy': dict,
            'ppaRecord': dict,
            'ppaClusterList': list,
            'palRecords': list,
            'sasTestHarnessDumpRecords': dict,
            'sasTestHarnessConfig': dict
        })
    self.assertEqual(
        len(config['sasTestHarnessDumpRecords']['ppaRecords']), 1,
        'Only one PPA is supported.')
    # Make sure ID formats are correct.
    ppa = config['sasTestHarnessDumpRecords']['ppaRecords'][0]
    self.assertGreater(
        len(ppa['ppaInfo']['cbsdReferenceId']), 0,
        'Must have at least one ID on the cluster list.')
    for cbsd_ref_id in ppa['ppaInfo']['cbsdReferenceId']:
      self.assertFalse(
          cbsd_ref_id.startswith('cbsd/'),
          'IDs in the cluster list should not start with "cbsd/".')
    for cbsd in config['sasTestHarnessDumpRecords']['cbsdRecords']:
      self.assertTrue(cbsd['id'].startswith('cbsd/'),
                      'IDs of individual CBSDs must start with "cbsd/".')

    # Initialize test-wide variables, and state variables.
    self.config = config
    self.active_dpas = []
    self.sas_test_harness_objects = []
    self.domain_proxy_objects = []
    self.protected_entity_records = {}
    self.num_peer_sases = 1
    self.cpas_executor = ThreadPoolExecutor(max_workers=1)
    self.agg_interf_check_executor = ThreadPoolExecutor(max_workers=1)
    self.sas_uut_fad = None
    self.test_harness_fads = []  # List for consistency with MCP code.
    self.all_dpa_checks_succeeded = True

    # Notify SAS UUT that a peer SAS exists (and start the SAS server)
    logging.info('Step 1: activate one SAS test harness and notify SAS UUT.')
    test_harness = config['sasTestHarnessConfig']
    logging.info('Creating SAS TH with config %s', test_harness)

    # Initialize SAS Test Harness Server instance to dump FAD records
    sas_test_harness_object = SasTestHarnessServer(
        test_harness['sasTestHarnessName'], test_harness['hostName'],
        test_harness['port'], test_harness['serverCert'],
        test_harness['serverKey'], test_harness['caCert'])
    self.InjectTestHarnessFccIds(
        config['sasTestHarnessDumpRecords']['cbsdRecords'])
    sas_test_harness_dump_records = [
        config['sasTestHarnessDumpRecords']['cbsdRecords'],
        config['sasTestHarnessDumpRecords']['ppaRecords']
    ]
    sas_test_harness_object.writeFadRecords(sas_test_harness_dump_records)
    # Start the server
    sas_test_harness_object.start()

    # Inform SAS UUT about SAS Test Harness.
    certificate_hash = getCertificateFingerprint(test_harness['serverCert'])
    self._sas_admin.InjectPeerSas({'certificateHash': certificate_hash,
                                   'url': sas_test_harness_object.getBaseUrl()})

    # Store required info in the test harness.
    self.fad_cert = test_harness['serverCert']
    self.fad_key = test_harness['serverKey']
    self.sas_test_harness_objects.append(sas_test_harness_object)

    # Extract PPA record from peer SAS and add to local protected entities.
    peer_sas_ppa = config['sasTestHarnessDumpRecords']['ppaRecords'][0]
    # The ID for each CBSD's record is of the format "cbsd/$REFERENCE_ID". The
    # IDs on the cluster list are of the format "$REFERENCE_ID". Here we prepend
    # "cbsd/" so that the values will be correctly matched in the zone purge
    # reference model.
    cluster_list = peer_sas_ppa['ppaInfo']['cbsdReferenceId']
    for i in range(len(cluster_list)):
      cluster_list[i] = 'cbsd/%s' % cluster_list[i]
    self.protected_entity_records['ppaRecords'] = [peer_sas_ppa]

    # Inject all PALs (used by SAS UUT PPA and peer SAS PPA)
    logging.info('Step 2: inject PAL records.')
    for index, pal_record in enumerate(config['palRecords']):
      try:
        logging.info('Injecting PAL record #%d', index)
        self._sas_admin.InjectPalDatabaseRecord(pal_record)
      except Exception:
        logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
        raise
    self.protected_entity_records['palRecords'] = config['palRecords']

    # Register, inject PPA, and request grants.
    logging.info('Steps 3 - 5: register, inject PPA, request grants.')
    domain_proxy_config = config['domainProxy']
    domain_proxy = test_harness_objects.DomainProxy(self,
                                                    domain_proxy_config['cert'],
                                                    domain_proxy_config['key'])
    self.domain_proxy_objects.append(domain_proxy)
    (sas_uut_ppa_record_with_cbsd_ids, sas_uut_ppa_record_with_reference_ids
    ) = domain_proxy.registerCbsdsAndRequestGrantsWithPpa(
        domain_proxy_config['registrationRequests'],
        domain_proxy_config['grantRequests'], config['ppaRecord'],
        config['ppaClusterList'],
        domain_proxy_config['conditionalRegistrationData'])
    # Make sure SAS UUT's PPA is also checked for protection.
    # At this point, we use the "with reference IDs" version because the pre-IAP
    # filtering code compares against the CBSD reference ID in the FAD.
    self.protected_entity_records['ppaRecords'].append(
        sas_uut_ppa_record_with_reference_ids)

    # FAD exchange.
    logging.info('Step 6 + 7: FAD exchange.')
    self.sas_uut_fad = getFullActivityDumpSasUut(self._sas, self._sas_admin,
                                                 self.fad_cert, self.fad_key)
    self.test_harness_fads.append(
        getFullActivityDumpSasTestHarness(
            self.sas_test_harness_objects[0].getSasTestHarnessInterface()))

    # Trigger CPAS in SAS UUT, and wait until completion.
    logging.info('Step 8: trigger CPAS.')
    self.cpas = self.cpas_executor.submit(
        self.TriggerDailyActivitiesImmediatelyAndWaitUntilComplete)

    logging.info('Step 9: execute IAP reference model.')
    # Pre-IAP filtering.
    pre_iap_filtering.preIapReferenceModel(self.protected_entity_records,
                                           self.sas_uut_fad,
                                           self.test_harness_fads)
    # IAP reference model.
    self.performIap()

    logging.info('Waiting for CPAS to complete (started in step 8).')
    self.cpas.result()
    logging.info('CPAS started in step 8 complete.')

    # Heartbeat, relinquish, grant, heartbeat
    logging.info('Steps 10 - 13: heartbeat, relinquish, grant, heartbeat.')
    domain_proxy.performHeartbeatAndUpdateGrants()

    # Aggregate interference check
    logging.info(
        'Step 14 and CHECK: calculating and checking aggregate interference.')
    # Before performing this check, we need to update the cluster list of SAS
    # UUT's PPA to use the CBSD IDs -- rather than reference IDs -- since this
    # is what the function getAuthorizedGrantsFromDomainProxies() expects. Note
    # that we must keep the indexing the same since
    # self.ppa_ap_iap_ref_values_list assumes consistent ordering of protected
    # entities.
    self.protected_entity_records['ppaRecords'][
        1] = sas_uut_ppa_record_with_cbsd_ids

    self.performIapAndDpaChecks()