/*
* Copyright (c) 2005-2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  Statemachine for roaming
*
*/

/*
* %version: 76.1.3 %
*/

#include "core_operation_roam.h"
#include "core_server.h"
#include "core_tools.h"
#include "core_tools_parser.h"
#include "core_sub_operation_connect.h"
#include "core_sub_operation_adhoc.h"
#include "core_sub_operation_wep_connect.h"
#include "core_sub_operation_wpa_connect.h"
#include "core_sub_operation_roam_update_ts.h"
#include "core_operation_update_power_mode.h"
#include "core_frame_assoc_resp.h"
#include "core_frame_dot11.h"
#include "core_frame_qbss_load_ie.h"
#include "core_scan_list.h"
#include "core_frame_nr_ie.h"
#include "core_frame_action_nr.h"
#include "core_frame_action_rm.h"
#include "core_frame_radio_measurement_action.h"
#include "am_debug.h"

const u32_t CORE_ROAMING_LIST_BONUS_PMKSA = 20;
const u32_t CORE_ROAMING_LIST_BONUS_WPX_FAST_ROAM = 20;

/** The maximum amount association/authentication failures when WAPI is used. */
const u32_t CORE_MAX_WAPI_AP_ASSOCIATION_FAILURE_COUNT = 10; 

/** Definitions for RRM Enabled Capabilities bitmask */
const u64_t RRM_CAPABILITY_BIT_MASK_NEIGHBOR_REPORT = 0x0000000002;

// ======== MEMBER FUNCTIONS ========

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
core_operation_roam_c::core_operation_roam_c(
    u32_t request_id,
    core_server_c* server,
    abs_core_driverif_c* drivers,
    abs_core_server_callback_c* adaptation,
    bool_t& is_connected,
    u8_t tag,        
    u8_t min_required_rcpi,
    const medium_time_s& min_medium_time,        
    const core_ssid_s& ssid,
    const core_mac_address_s& bssid ) :
    core_operation_base_c( core_operation_roam, request_id, server, drivers, adaptation, 
        core_base_flag_drivers_needed ),
    is_connected_m( is_connected ),
    tag_m( tag ),
    min_required_rcpi_m( min_required_rcpi ),
    min_medium_time_m( min_medium_time ),
    ssid_m( ssid ),
    bssid_m( bssid ),    
    roaming_list_m( ),
    management_status_m( core_management_status_success ),
    connect_status_m( core_connect_network_not_found ),
    current_ap_m( NULL ),
    current_bssid_m( ZERO_MAC_ADDR ),
    assoc_ie_list_m( false_t ),
    is_cached_sa_used_m( false_t ),
    assoc_resp_m( NULL )
    {
    DEBUG( "core_operation_roam_c::core_operation_roam_c()" );    
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
core_operation_roam_c::~core_operation_roam_c()
    {
    DEBUG( "core_operation_roam_c::~core_operation_roam_c()" );

    for ( roaming_list_entry_s* iter = roaming_list_m.first(); iter; iter = roaming_list_m.next() )
        {
        delete iter->ap_data;
        }

    roaming_list_m.clear();
    assoc_ie_list_m.clear();
    delete assoc_resp_m;
    current_ap_m = NULL;
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
core_error_e core_operation_roam_c::next_state()
    {
    DEBUG( "core_operation_roam_c::next_state()" );

    switch ( operation_state_m )
        {
        case core_state_init:
            {
            if( server_m->get_connection_data()->last_roam_reason() != core_roam_reason_initial_connect )
                {
                server_m->get_core_settings().roam_metrics().inc_roam_attempt_count(
                    server_m->get_connection_data()->last_roam_reason() );
                }

            if( is_connected_m )
                {
                current_bssid_m = server_m->get_connection_data()->current_ap_data()->bssid();

                DEBUG6( "core_operation_roam_c::next_state() - current BSSID: %02X:%02X:%02X:%02X:%02X:%02X",
                    current_bssid_m.addr[0], current_bssid_m.addr[1], current_bssid_m.addr[2],
                    current_bssid_m.addr[3], current_bssid_m.addr[4], current_bssid_m.addr[5] );
                }

            DEBUG6( "core_operation_roam_c::next_state() - requested BSSID: %02X:%02X:%02X:%02X:%02X:%02X",
                bssid_m.addr[0], bssid_m.addr[1], bssid_m.addr[2],
                bssid_m.addr[3], bssid_m.addr[4], bssid_m.addr[5] );           
            DEBUG1( "core_operation_roam_c::next_state() - minimum required RCPI value: %u",
                min_required_rcpi_m );

            /**
             * Set all the traffic streams as not active and reset TSPEC parameters to the
             * default values.
             */
            core_traffic_stream_list_c& ts_list(
                server_m->get_connection_data()->traffic_stream_list() );
            for( core_traffic_stream_c* iter = ts_list.first(); iter; iter = ts_list.next() )
                {
                iter->set_status( core_traffic_stream_status_undefined );
                iter->reset_to_default_values();
                }

            if ( !is_connected_m )
                {
                /**
                 * Indicate core_connection_state_searching state to adaptation.
                 */
                core_connection_state_e state = core_connection_state_searching;
                server_m->get_core_settings().set_connection_state( state );

                if ( server_m->get_connection_data()->last_connection_state() != state )
                    {
                    u8_t buf[5];
                    buf[0] = static_cast<u8_t>( state );
                    adaptation_m->notify(
                        core_notification_connection_state_changed,
                        1,
                        buf );

                    server_m->get_connection_data()->set_last_connection_state(
                        state );
                    }
                }

            server_m->get_scan_list().print_contents();

            DEBUG1( "core_operation_roam_c::next_state() - tag entry for iterator is 0x%02X",
                tag_m );
            core_scan_list_iterator_by_tag_and_ssid_and_bssid_c iter(
                server_m->get_scan_list(),
                tag_m,
                ssid_m,
                bssid_m );
            for ( core_ap_data_c* ap_data = iter.first(); ap_data; ap_data = iter.next() )
                {
                if ( process_frame( *ap_data ) )
                    {
                    DEBUG( "core_operation_roam_c::next_state() - AP added to roaming list" );
                    }                
                }               

#ifdef _DEBUG
            DEBUG( "core_operation_roam_c::next_state() - roaming list start" );

            roaming_list_entry_s* entry_iter = roaming_list_m.first();
            TUint idx( 1 );

            while ( entry_iter )
                {
                const core_mac_address_s bssid = entry_iter->ap_data->bssid();

                DEBUG8( "core_operation_roam_c::next_state() - entry #%02u %02X:%02X:%02X:%02X:%02X:%02X (RCPI %u)",
                    idx, bssid.addr[0], bssid.addr[1], bssid.addr[2], 
                    bssid.addr[3], bssid.addr[4], bssid.addr[5], entry_iter->ap_data->rcpi() );

                entry_iter = roaming_list_m.next();
                ++idx;
                }

            DEBUG( "core_operation_roam_c::next_state() - roaming list end" );
#endif // _DEBUG            

            if ( roaming_list_m.first() )
                {
                current_ap_m = roaming_list_m.first()->ap_data;
                }

            /**
             * We can proceed with the connection if there are entries in
             * the roaming list OR we are going to start our own IBSS network.
             */
            if( current_ap_m ||
                ( !is_connected_m &&
                  server_m->get_connection_data()->iap_data().operating_mode() == core_operating_mode_ibss ) )
                {
                operation_state_m = core_state_req_disable_userdata_before_connect;
                
                if ( !is_connected_m )
                    {
                    return next_state();
                    }

                DEBUG( "core_operation_roam_c::next_state() - disabling user data before connect" );

                server_m->get_core_settings().roam_metrics().set_roam_ts_userdata_disabled();

                drivers_m->disable_user_data(
                    request_id_m );
                }
            else
                {
                DEBUG( "core_operation_roam_c::next_state() - no suitable AP found" );
                failure_reason_m = core_error_not_found;
                if ( server_m->get_connection_data()->connect_status() == core_connect_undefined )
                    {
                    server_m->get_connection_data()->set_connect_status( connect_status_m );
                    }

                server_m->get_core_settings().roam_metrics().inc_roam_attempt_failed_count(
                    core_roam_failed_reason_no_suitable_ap );
                if ( server_m->get_connection_data()->last_roam_failed_reason() == core_roam_failed_reason_none )
                    {
                    server_m->get_connection_data()->set_last_roam_failed_reason( core_roam_failed_reason_no_suitable_ap );
                    }

                /**
                 * Set all traffic stream statuses and parameters back to the values
                 * they were before the roam attempt.
                 */
                core_traffic_stream_list_c& ts_list(
                    server_m->get_connection_data()->traffic_stream_list() );

                for( core_traffic_stream_c* iter = ts_list.first(); iter; iter = ts_list.next() )
                    {
                    iter->set_status( core_traffic_stream_status_active );
                    iter->reset_to_previous_values();
                    }

                return cancel();
                }            

            break;                
            }
        case core_state_req_disable_userdata_before_connect:
            {
            operation_state_m = core_state_req_connect;

#ifdef _DEBUG
            if( current_ap_m )
                {
                const core_mac_address_s current_bssid =
                    current_ap_m->bssid();

                DEBUG6( "core_operation_roam_c::next_state() - selecting BSSID %02X:%02X:%02X:%02X:%02X:%02X",
                    current_bssid.addr[0], current_bssid.addr[1], current_bssid.addr[2],
                    current_bssid.addr[3], current_bssid.addr[4], current_bssid.addr[5] );                
                }
            else
                {
                DEBUG("core_operation_roam_c::next_state() - establishing adhoc network");
                }
#endif // _DEBUG

            core_security_mode_e mode(
                server_m->get_connection_data()->iap_data().security_mode() );
            bool_t is_ibss_mode(
                server_m->get_connection_data()->iap_data().operating_mode() == core_operating_mode_ibss );

            /**
             * Establish Adhoc network
             */
            if( !current_ap_m &&
                is_ibss_mode )
                {
                core_operation_base_c* operation = new core_sub_operation_adhoc_c(
                    request_id_m,
                    server_m,
                    drivers_m,
                    adaptation_m,
                    &current_ap_m ); // conveying a pointer to pointer

                return run_sub_operation( operation );
                }

            /**
             * IAP with no security.
             */
            if ( mode == core_security_mode_allow_unsecure )                     
                {                                        
                core_operation_base_c* operation = new core_sub_operation_connect_c(
                    request_id_m,
                    server_m,
                    drivers_m,
                    adaptation_m,
                    is_connected_m,
                    management_status_m,
                    ssid_m,
                    *current_ap_m,
                    core_authentication_mode_open,
                    core_encryption_mode_disabled,
                    core_cipher_key_type_none,
                    assoc_ie_list_m,
                    is_ibss_mode ? NULL : &assoc_resp_m,
                    true_t,    // ignored, pairwise key not used
                    true_t,    // ignored, group key not used
                    NULL );

                return run_sub_operation( operation );
                }

            /**
             * IAP with static WEP.
             */
            if ( mode == core_security_mode_wep )
                {
                core_operation_base_c* operation = new core_sub_operation_wep_connect_c(
                    request_id_m,
                    server_m,
                    drivers_m,
                    adaptation_m,
                    is_connected_m,
                    management_status_m,
                    ssid_m,
                    *current_ap_m,
                    server_m->get_connection_data()->iap_data().authentication_mode(),
                    core_encryption_mode_wep,
                    assoc_ie_list_m,
                    is_ibss_mode ? NULL : &assoc_resp_m );
                        
                return run_sub_operation( operation );
                }

            /**
             * IAP with EAP.
             */
            is_cached_sa_used_m = roaming_list_m.first()->is_cached_sa_available;

            core_operation_base_c* operation = new core_sub_operation_wpa_connect_c(
                request_id_m,
                server_m,
                drivers_m,
                adaptation_m,
                is_connected_m,
                management_status_m,
                ssid_m,
                *current_ap_m,
                is_cached_sa_used_m,
                assoc_ie_list_m,
                is_ibss_mode ? NULL : &assoc_resp_m );

            return run_sub_operation( operation );
            }
        case core_state_req_connect:
            {
            operation_state_m = core_state_req_update_ts;
            
            DEBUG( "core_operation_roam_c::next_state() - connection success" );
    
            /**
             * Send information about the current AP to the subscribed clients.
             *
             * This has to be done before updating traffic stream statuses so
             * that NIF won't try to recreate rejected streams.
             */
            core_ap_information_s info = core_tools_parser_c::get_ap_info(
                server_m->get_connection_data()->iap_data(),
                *current_ap_m );
            DEBUG( "core_operation_roam_c::next_state() - notifying access point information:" );
            DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_voice = %u",
                info.is_ac_required_for_voice );
            DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_video = %u",
                info.is_ac_required_for_video );
            DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_best_effort = %u",
                info.is_ac_required_for_best_effort );
            DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_background = %u",
                info.is_ac_required_for_background );
            DEBUG1( "core_operation_roam_c::next_state() - is_wpx = %u",
                info.is_wpx );
            adaptation_m->notify(
                core_notification_ap_info_changed,
                sizeof ( info ),
                reinterpret_cast<u8_t*>( &info ) );

            if ( server_m->get_connection_data()->current_ap_data() &&
                 server_m->get_connection_data()->current_ap_data()->bssid() != current_ap_m->bssid() )
                {
                server_m->get_connection_data()->set_previous_ap_data(
                    *server_m->get_connection_data()->current_ap_data() );

                /**
                 * EAPOL must notified about the disassociation.
                 */
                if ( server_m->get_connection_data()->iap_data().is_eap_used() ||
                     server_m->get_connection_data()->iap_data().is_wapi_used() )
                    {
                    core_mac_address_s bssid =
                        server_m->get_connection_data()->previous_ap_data()->bssid();

                    network_id_c network(
                        &bssid.addr[0],
                        MAC_ADDR_LEN,
                        &server_m->own_mac_addr().addr[0],
                        MAC_ADDR_LEN,
                        server_m->get_eapol_instance().ethernet_type() );

                    DEBUG6( "core_operation_roam_c::next_state() - EAPOL disassociation from BSSID %02X:%02X:%02X:%02X:%02X:%02X",
                        bssid.addr[0], bssid.addr[1], bssid.addr[2],
                        bssid.addr[3], bssid.addr[4], bssid.addr[5] );

                    /**
                     * is_eapol_disconnecting is not updated here because this disassociation
                     * is for the previous AP and thus we don't really care when this
                     * request gets completed.
                     */

                    server_m->get_eapol_instance().disassociation( &network );
                    }
                }

            server_m->get_connection_data()->set_current_ap_data(
                *current_ap_m );

            core_operation_base_c* operation = new core_sub_operation_roam_update_ts_c(
                request_id_m,
                server_m,
                drivers_m,
                adaptation_m,
                *current_ap_m );

            return run_sub_operation( operation );           
            }        
        case core_state_req_update_ts:
            {
            operation_state_m = core_state_req_enable_userdata;

            DEBUG( "core_operation_roam_c::next_state() - allowing user data" );
            
            drivers_m->enable_user_data(
                request_id_m );

            break;
            }
        case core_state_req_enable_userdata:
            {                        
            DEBUG( "core_operation_roam_c::next_state() - connection done" );

            server_m->get_connection_data()->set_ssid( ssid_m );
            server_m->get_core_settings().roam_metrics().set_roam_ts_userdata_enabled();
            server_m->get_core_settings().roam_metrics().inc_roam_success_count();

            server_m->get_connection_data()->clear_ap_association_failure_count(
                current_ap_m->bssid() );
            
            server_m->get_connection_data()->clear_all_authentication_failure_counts();

            server_m->get_connection_data()->remove_temporary_blacklist_entries(
                core_ap_blacklist_reason_eapol_failure |
                core_ap_blacklist_reason_max_association_failure_count );

            if ( current_ap_m->bssid() != server_m->get_connection_data()->last_bssid() )
                {
                DEBUG( "core_operation_roam_c::next_state() - BSSID changed, notifying change" );

                const core_mac_address_s bssid = current_ap_m->bssid();

                adaptation_m->notify(
                    core_notification_bssid_changed,
                    sizeof ( bssid ),
                    &bssid.addr[0] );
                    
                server_m->get_connection_data()->set_last_bssid(
                    bssid );
                }

            if ( !is_connected_m )
                {
                core_connection_state_e state = server_m->get_connection_data()->connection_state();
                server_m->get_core_settings().set_connection_state( state );

                if ( server_m->get_connection_data()->last_connection_state() != state )
                    {
                    u8_t buf[5];
                    buf[0] = static_cast<u8_t>( state );

                    adaptation_m->notify(
                        core_notification_connection_state_changed,
                        1,
                        buf );
                    
                    server_m->get_connection_data()->set_last_connection_state(
                        state );
                    }
                }

            if ( server_m->get_connection_data()->last_rcp_class() != core_rcp_normal )
                {
                DEBUG( "core_operation_roam_c::next_state() - sending a notification about the normal signal level" );

                u8_t buf[5];
                buf[0] = static_cast<u8_t>( core_rcp_normal );
                buf[1] = current_ap_m->rcpi();

                adaptation_m->notify(
                    core_notification_rcp_changed,
                    sizeof( buf ),
                    buf );
                server_m->get_connection_data()->set_last_rcp_class( core_rcp_normal );
                }

            if ( current_ap_m->is_wpx() )
                {
                server_m->get_wpx_adaptation_instance().handle_wpx_roam_success(
                    *current_ap_m );
                }

            if( current_ap_m->rrm_capabilities() & RRM_CAPABILITY_BIT_MASK_NEIGHBOR_REPORT )
                {
                handle_neighbor_request();
                }
            
            /**
             * If roaming to another AP, schedule a power save update.
             *
             * This is not done on first connect because we do not want to disturb
             * DHCP negotiation.
             */
            if ( server_m->get_connection_data()->iap_data().operating_mode() == core_operating_mode_infrastructure &&
                 server_m->get_connection_data()->previous_ap_data() )
                {
                DEBUG( "core_operation_roam_c::next_state() - scheduling a power save update" );

                core_operation_base_c* operation = new core_operation_update_power_mode_c(
                    REQUEST_ID_CORE_INTERNAL,
                    server_m,
                    drivers_m,
                    adaptation_m );

                server_m->queue_int_operation( operation );
                }

            return core_error_ok;
            }
        case core_state_fail_connect:
            {
            DEBUG( "core_operation_roam_c::next_state() - connection failed" );

            DEBUG1( "core_operation_roam_c::next_state() - failure_reason_m is %u",
                failure_reason_m );
            DEBUG1( "core_operation_roam_c::next_state() - management_status_m is %u",
                management_status_m );
            DEBUG1( "core_operation_roam_c::next_state() - is_cached_sa_used_m is %u",
                is_cached_sa_used_m );

            core_connect_status_e status = connect_status(
                failure_reason_m,
                management_status_m );

            DEBUG1( "core_operation_roam_c::next_state() - connect_status is %u",
                status );

            server_m->get_connection_data()->set_connect_status( status );

            const core_mac_address_s bssid =
                current_ap_m->bssid();

            DEBUG( "core_operation_roam_c::next_state() - removing current AP entries from scan list" );
            server_m->get_scan_list().remove_entries_by_bssid(
                bssid );

            update_roam_failure_count(
                failure_reason_m,
                management_status_m );

            core_ap_blacklist_reason_e reason =
                is_fatal_failure(
                    failure_reason_m,
                    management_status_m,
                    is_cached_sa_used_m );
            if( reason != core_ap_blacklist_reason_none )
                {
                if ( reason == core_ap_blacklist_reason_eapol_failure )
                    {     
                    u8_t count_af( server_m->get_connection_data()->ap_authentication_failure_count( bssid ) );
                
                    u32_t max_ap_authentication_failure_count(
                     server_m->get_device_settings().max_ap_authentication_failure_count );
                
                    DEBUG1( "core_operation_roam_c::next_state() - this AP has failed %u time(s) in authentication earlier",
                        count_af );
                
                    if ( count_af >=  max_ap_authentication_failure_count )
                        {
                        DEBUG1( "core_operation_roam_c::next_state() - authentication failure count (%u), blacklisting the AP", count_af );
                        server_m->get_connection_data()->add_mac_to_temporary_blacklist(
                            bssid, reason );
                        }
                    else
                        {
                        DEBUG( "core_operation_roam_c::next_state() - increasing authentication failure count" );
                        server_m->get_connection_data()->increase_ap_authentication_failure_count( bssid );
                        }
                    }
                else
                    {
                    DEBUG( "core_operation_roam_c::next_state() - fatal failure, blacklisting the AP");
                    server_m->get_connection_data()->add_mac_to_temporary_blacklist(
                        bssid, reason );
                    }
                }
            else
                {
                u8_t count( server_m->get_connection_data()->ap_association_failure_count( bssid ) );
                DEBUG1( "core_operation_roam_c::next_state() - this AP has failed %u time(s) earlier",
                    count );
                
                u32_t max_ap_association_failure_count(
                    server_m->get_device_settings().max_ap_association_failure_count );
                if( server_m->get_connection_data()->iap_data().is_wapi_used() )
                    {
                    max_ap_association_failure_count = CORE_MAX_WAPI_AP_ASSOCIATION_FAILURE_COUNT;
                    }
                
                if( count >= max_ap_association_failure_count - 1 )
                    {
                    DEBUG1( "core_operation_roam_c::next_state() - failure count (%u) exceeded, blacklisting the AP",
                        max_ap_association_failure_count );
                    server_m->get_connection_data()->add_mac_to_temporary_blacklist(
                        bssid, core_ap_blacklist_reason_max_association_failure_count );
                    }
                else
                    {
                    DEBUG( "core_operation_roam_c::next_state() - increasing AP failure count" );
                    server_m->get_connection_data()->increase_ap_association_failure_count( bssid );
                    }
                }

            DEBUG1( "core_operation_roam_c::next_state() - setting tx power (%u)",
                server_m->get_device_settings().tx_power_level );

            operation_state_m = core_state_fail_set_tx_power;
            
            drivers_m->set_tx_power_level(
                request_id_m,
                server_m->get_device_settings().tx_power_level );

            break;
            }
        case core_state_fail_set_tx_power:
            {
            u32_t tx_level = server_m->get_device_settings().tx_power_level;
            if ( server_m->get_connection_data()->last_tx_level() != tx_level )
                {                
                DEBUG( "core_operation_roam_c::next_state() - TX level has changed, notifying change" );

                adaptation_m->notify(
                    core_notification_tx_power_level_changed,
                    sizeof( tx_level ),
                    reinterpret_cast<u8_t*>(&tx_level) );

                server_m->get_connection_data()->set_last_tx_level( tx_level );
                }          

            return failure_reason_m;
            }
        default:
            {
            ASSERT( false_t );
            }
        }

    return core_error_request_pending;
    }
    
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
core_error_e core_operation_roam_c::cancel()
    {
    DEBUG( "core_operation_roam_c::cancel() " );    
    
    switch ( operation_state_m )
        {
        case core_state_req_connect:
            {
            operation_state_m = core_state_fail_connect;

            return next_state();        
            }            
        default:
            {
            return failure_reason_m;    
            }            
        }        
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// 
bool_t core_operation_roam_c::is_security_association_available(
    const core_ap_data_c& ap_data )
    {
    DEBUG( "core_operation_roam_c::is_security_association_available() " );
    
    core_type_list_c<network_id_c> network_id_list;

    /*const*/ core_mac_address_s bssid( ap_data.bssid() );
    network_id_c * network_id = new network_id_c(
        &bssid.addr[0],
        MAC_ADDR_LEN,
        &server_m->own_mac_addr().addr[0],
        MAC_ADDR_LEN,
        server_m->get_eapol_instance().ethernet_type() );
    if ( !network_id )
        {
        DEBUG( "core_operation_roam_c::is_security_association_available() - unable to create network_id_c" );
        return false_t;
        }
    
    network_id_list.append( network_id );

    wlan_eapol_if_eapol_key_authentication_type_e auth_type = core_tools_c::eap_authentication_type(
        server_m->get_connection_data()->iap_data(),
        ap_data );
    
    core_error_e ret = server_m->get_eapol_instance().check_pmksa_cache(
        network_id_list,
        auth_type,
        core_tools_c::eapol_cipher( ap_data.best_pairwise_cipher() ),
        core_tools_c::eapol_cipher( ap_data.best_group_cipher() ) );
    if ( ret != core_error_ok )
        {
        DEBUG1( "core_operation_roam_c::is_security_association_available() - check_pmksa_cache returned %i", ret );
        if ( ret == core_error_request_pending )
            {
            DEBUG( "core_operation_roam_c::is_security_association_available() - check_pmksa_cache returns core_error_request_pending! Implement complete_check_pmksa_cache for asynchronous operation! " );
            }
        
        return false_t;
        }
    
    // Ok, because ret == core_error_ok, we know that complete_check_pmksa_cache is already called from EAPOL.
    // If ret == core_error_request_pending, we should wait until complete_check_pmksa_cache.
    core_type_list_c< core_mac_address_s > & pmksa_list = server_m->get_eapol_instance().get_completed_check_pmksa_cache_list();
    
    DEBUG1( "core_operation_roam_c::is_security_association_available() - pmksa_list.count() = %i", pmksa_list.count() );

    if ( !pmksa_list.count() )
        {
        pmksa_list.clear();
        return false_t;
        }

    core_mac_address_s* list_obj = pmksa_list.first();
    if ( !list_obj )
        {
        pmksa_list.clear();
        return false_t;
        }

    if ( *list_obj != bssid )
        {
        pmksa_list.clear();
        return false_t;
        }

    pmksa_list.clear();

    return true_t;        
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
core_connect_status_e core_operation_roam_c::connect_status(
    core_error_e request_status,
    core_management_status_e management_status )
    {
    DEBUG( "core_operation_roam_c::connect_status()" );
    
    core_iap_data_c& iap_data( server_m->get_connection_data()->iap_data() );

    if ( management_status == core_management_status_assoc_denied_full_ap )
        {
        DEBUG( "core_operation_roam_c::connect_status() - AP full" );
        return core_connect_ap_full;
        }

    if ( request_status == core_error_eapol_auth_start_timeout )
        {
        DEBUG( "core_operation_roam_c::connect_status() - EAPOL authentication timeout before authentication was started" );
        return core_connect_eapol_auth_start_timeout;
        }

    switch( iap_data.security_mode() )
        {
        case core_security_mode_wep:
            {
            if ( management_status == core_management_status_auth_algo_not_supported )
                {
                if ( iap_data.authentication_mode() == core_authentication_mode_open )
                    {
                    DEBUG( "core_operation_roam_c::connect_status() - open authentication unsupported (WEP)" );
                    return core_connect_wep_open_authentication_unsupported;
                    }
                else
                    {
                    DEBUG( "core_operation_roam_c::connect_status() - shared authentication unsupported (WEP)" );
                    return core_connect_wep_shared_authentication_unsupported;
                    }
                }
            else if ( iap_data.authentication_mode() == core_authentication_mode_shared &&
                      ( management_status == core_management_status_auth_challenge_failure ||
                        management_status == core_management_status_auth_frame_out_of_sequence ||
                        management_status == core_management_status_unspecified_failure ||
                        request_status == core_error_timeout ) )
                {
                DEBUG( "core_operation_roam_c::connect_status() - shared authentication failed (WEP)" );
                return core_connect_wep_shared_authentication_failed;
                }
            else if ( management_status == core_management_status_unsupported_capabilities &&
                      !current_ap_m->is_privacy_enabled() )
                {
                DEBUG( "core_operation_roam_c::connect_status() - association using privacy failed (WEP)" );
                return core_connect_iap_wep_but_ap_has_no_privacy;
                }
                
            break;
            }
        case core_security_mode_allow_unsecure:
            {
            break;
            }            
        case core_security_mode_802dot1x:
            {
            if ( request_status == core_error_eapol_total_failure ||
                 request_status == core_error_eapol_failure )
                {
                DEBUG( "core_operation_roam_c::connect_status() - 802.1x EAP failure" );
                return eap_connect_status(
                    iap_data.security_mode(),
                    server_m->get_connection_data()->last_failed_eap_type(),
                    server_m->get_connection_data()->last_eap_error() );
                }
            else if ( management_status == core_management_status_auth_algo_not_supported )
                {
                DEBUG( "core_operation_roam_c::connect_status() - authentication algorithm not supported (802.1x)" );
                return core_connect_802_1x_authentication_algorithm_not_supported;
                }

            break;
            }
        case core_security_mode_wpa:
            /** Falls through on purpose. */
        case core_security_mode_wpa2only:            
            {
            if ( request_status == core_error_eapol_total_failure ||
                 request_status == core_error_eapol_failure )
                {
                if ( iap_data.is_psk_used() )
                    {
                    DEBUG( "core_operation_roam_c::connect_status() - WPA-PSK failure" );
                    return core_connect_wpa_psk_failure;
                    }
                else
                    {
                    DEBUG( "core_operation_roam_c::connect_status() - WPA EAP failure" );
                    return eap_connect_status(
                        iap_data.security_mode(),
                        server_m->get_connection_data()->last_failed_eap_type(),
                        server_m->get_connection_data()->last_eap_error() );
                    }
                }

            break;        
            }
        case core_security_mode_wapi:            
            {
            if ( request_status == core_error_eapol_total_failure ||
                 request_status == core_error_eapol_failure )
                {
                if ( iap_data.is_psk_used() )
                    {
                    DEBUG( "core_operation_roam_c::connect_status() - WAPI-PSK failure" );
                    return core_connect_wapi_psk_failure;
                    }
                else
                    {
                    DEBUG( "core_operation_roam_c::connect_status() - WAPI certificate failure" );
                    return core_connect_wapi_certificate_failure;
                   }
                }

            break;        
            }

        default:
            {
            ASSERT( false_t );
            }
        }

    return core_connect_undefined;
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
core_ap_blacklist_reason_e core_operation_roam_c::is_fatal_failure(
    core_error_e request_status,
    core_management_status_e management_status,
    bool_t is_cached_sa_used ) const
    {
    DEBUG( "core_operation_roam_c::is_fatal_failure()" );
    
    /**
     * EAPOL failures in 802.1x/WPA mode are all fatal but blacklisting
     * is only done with core_error_eapol_failure because others cause
     * the connection to be shutdown immediately.
     */
    if( request_status == core_error_eapol_failure &&
        server_m->get_connection_data()->iap_data().is_eap_used() )
        {
        DEBUG( "core_operation_roam_c::is_fatal_failure() - fatal, EAP failure" );
        return core_ap_blacklist_reason_eapol_failure;
        }

    /**
     * Don't consider a failed (re-)association attempt as fatal since it's
     * possible AP just doesn't have our authentication cached any more.
     */
    if ( is_cached_sa_used )
        {
        DEBUG( "core_operation_roam_c::is_fatal_failure() - not fatal, a reassociation attempt" );
        return core_ap_blacklist_reason_none;
        }

    /**
     * Non-fatal WPX management status codes should not cause blacklisting.
     */
    if ( server_m->get_wpx_adaptation_instance().is_wpx_management_status( management_status ) &&
         !server_m->get_wpx_adaptation_instance().is_fatal_wpx_management_status( management_status ) )
        {
        DEBUG( "core_operation_roam_c::is_fatal_failure() - not fatal, WPX-specific management status" );
        return core_ap_blacklist_reason_none;
        }

    if ( management_status != core_management_status_success &&
         management_status != core_management_status_assoc_denied_full_ap )
        {
        DEBUG1( "core_operation_roam_c::is_fatal_failure() - fatal, management status %u",
            management_status );
        return core_ap_blacklist_reason_association_status;
        }

    DEBUG( "core_operation_roam_c::is_fatal_failure() - not fatal" );
    return core_ap_blacklist_reason_none;
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
core_connect_status_e core_operation_roam_c::eap_connect_status(
    core_security_mode_e security_mode,
    u32_t eap_type,
    u32_t eap_error ) const
    {
    DEBUG( "core_operation_roam_c::eap_connect_status()" );
    DEBUG1( "core_operation_roam_c::eap_connect_status() - eap_type %u",
        eap_type );
    DEBUG1( "core_operation_roam_c::eap_connect_status() - eap_error %u",
        eap_error );
 
    core_connect_status_e status( core_connect_wpa_eap_failure );
    if ( security_mode == core_security_mode_802dot1x )
        {
        status = core_connect_802_1x_failure;
        }
 
    /** 
     * Handle EAP type specific errors.
     */
    switch ( eap_type )
        {
        case wlan_eapol_if_eap_type_tls:
            {
            switch ( eap_error )
                {
                case wlan_eapol_if_eap_status_certificate_expired:
                case wlan_eapol_if_eap_status_certificate_revoked:
                    status = core_connect_eap_tls_server_certificate_expired;
                    break;
                case wlan_eapol_if_eap_status_unsupported_certificate:
                    status = core_connect_eap_tls_server_certificate_unknown;
                    break;
                case wlan_eapol_if_eap_status_user_certificate_unknown:
                    status = core_connect_eap_tls_user_certificate_unknown;
                    break;
                case wlan_eapol_if_eap_status_illegal_cipher_suite:
                    status = core_connect_eap_tls_illegal_cipher_suite;
                    break;
                case wlan_eapol_if_eap_status_bad_certificate:
                    status = core_connect_eap_tls_user_rejected;
                    break;
                default:
                    status = core_connect_eap_tls_failure;
                }             
            break;
            }
        case wlan_eapol_if_eap_type_leap:
            {
            status = core_connect_eap_leap_failure;
            break;
            }
        case wlan_eapol_if_eap_type_gsmsim:
            {
            switch ( eap_error )
                {
                case wlan_eapol_if_eap_status_identity_query_failed:
                    status = core_connect_eap_sim_identity_query_failed;
                    break;
                case wlan_eapol_if_eap_status_user_has_not_subscribed_to_the_requested_service:
                    status = core_connect_eap_sim_user_has_not_subscribed_to_the_requested_service;
                    break;
                case wlan_eapol_if_eap_status_users_calls_are_barred:
                    status = core_connect_eap_sim_users_calls_are_barred;
                    break;
                default:
                    status = core_connect_eap_sim_failure;
                }            
            break;
            }
        case wlan_eapol_if_eap_type_ttls:
            {
            switch ( eap_error )
                {
                case wlan_eapol_if_eap_status_certificate_expired:
                case wlan_eapol_if_eap_status_certificate_revoked:
                    status = core_connect_eap_ttls_server_certificate_expired;
                    break;
                case wlan_eapol_if_eap_status_unsupported_certificate:
                    status = core_connect_eap_ttls_server_certificate_unknown;
                    break;
                case wlan_eapol_if_eap_status_user_certificate_unknown:
                    status = core_connect_eap_ttls_user_certificate_unknown;
                    break;
                case wlan_eapol_if_eap_status_illegal_cipher_suite:
                    status = core_connect_eap_ttls_illegal_cipher_suite;
                    break;
                case wlan_eapol_if_eap_status_bad_certificate:
                    status = core_connect_eap_ttls_user_rejected;
                    break;
                default:
                    status = core_connect_eap_ttls_failure;
                }             
            break;
            }
        case wlan_eapol_if_eap_type_aka:
            {
            switch ( eap_error )
                {
                case wlan_eapol_if_eap_status_identity_query_failed:
                    status = core_connect_eap_sim_identity_query_failed;
                    break;
                case wlan_eapol_if_eap_status_user_has_not_subscribed_to_the_requested_service:
                    status = core_connect_eap_sim_user_has_not_subscribed_to_the_requested_service;
                    break;
                case wlan_eapol_if_eap_status_users_calls_are_barred:
                    status = core_connect_eap_sim_users_calls_are_barred;
                    break;
                default:
                    status = core_connect_eap_aka_failure;
                }
            break;
            }
        case wlan_eapol_if_eap_type_peap:
            {
            switch ( eap_error )
                {
                case wlan_eapol_if_eap_status_certificate_expired:
                case wlan_eapol_if_eap_status_certificate_revoked:
                    status = core_connect_eap_peap_server_certificate_expired;
                    break;
                case wlan_eapol_if_eap_status_unsupported_certificate:
                    status = core_connect_eap_peap_server_certificate_unknown;
                    break;
                case wlan_eapol_if_eap_status_user_certificate_unknown:
                    status = core_connect_eap_peap_user_certificate_unknown;
                    break;
                case wlan_eapol_if_eap_status_illegal_cipher_suite:
                    status = core_connect_eap_peap_illegal_cipher_suite;
                    break;
                case wlan_eapol_if_eap_status_bad_certificate:
                    status = core_connect_eap_peap_user_rejected;
                    break;
                default:
                    status = core_connect_eap_peap_failure;
                }             
            break;
            }
        case wlan_eapol_if_eap_type_mschapv2:
            {
            switch ( eap_error )
                {
                case wlan_eapol_if_eap_status_password_expired:
                    status = core_connect_eap_mschapv2_password_expired;
                    break;
                case wlan_eapol_if_eap_status_no_dialin_permission:
                    status = core_connect_eap_mschapv2_no_dialin_permission;
                    break;
                case wlan_eapol_if_eap_status_account_disabled:
                    status = core_connect_eap_mschapv2_account_disabled;
                    break;
                case wlan_eapol_if_eap_status_restricted_logon_hours:
                    status = core_connect_eap_mschapv2_restricted_logon_hours;
                    break;                    
                default:
                    status = core_connect_eap_mschapv2_failure;
                }
            break;
            }
        case wlan_eapol_if_eap_type_fast:
            {
            switch ( eap_error )
                {
                case wlan_eapol_if_eap_status_tunnel_compromise_error:
                    status = core_connect_eap_fast_tunnel_compromise_error;
                    break;
                case wlan_eapol_if_eap_status_unexpected_tlv_exhanged:
                    status = core_connect_eap_fast_unexpected_tlv_exhanged;
                    break;
                case wlan_eapol_if_eap_status_no_pac_nor_certs_to_authenticate_with_provision_disabled:
                    status = core_connect_eap_fast_no_pac_nor_certs_to_authenticate_with_provision_disabled;
                    break;
                case wlan_eapol_if_eap_status_no_matching_pac_for_aid:
                    status = core_connect_eap_fast_no_matching_pac_for_aid;
                    break;
                case wlan_eapol_if_eap_status_pac_store_corrupted:
                    status = core_connect_eap_fast_pac_store_corrupted;
                    break;
                case wlan_eapol_if_eap_status_authentication_failure: /* flow through on purpose */
                default:
                    status = core_connect_eap_fast_authentication_failed;
                }
            break;
            }
        default:
            break;
        }     

    return status;
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//    
bool_t core_operation_roam_c::process_frame(
    core_ap_data_c& ap_data )
    {
    connect_status_m = core_tools_parser_c::is_ap_suitable(
        server_m->get_wpx_adaptation_instance(),
        ap_data,
        server_m->get_connection_data()->iap_data(),
        server_m->get_core_settings(),
        *server_m->get_connection_data(),
        min_required_rcpi_m,
        min_medium_time_m,
        server_m->is_cm_active(),
        current_bssid_m );

    if( connect_status_m == core_connect_ok )
        {
        roaming_list_entry_s* entry = new roaming_list_entry_s;
        if( !entry )
            {
            DEBUG( "core_operation_roam_c::process_frame() - unable to roaming_list_entry_s instance()" );

            return false_t;                        
            }

        entry->ap_data = NULL;
        entry->is_cached_sa_available = false_t;

        entry->ap_data = core_ap_data_c::instance( ap_data );
        if( !entry->ap_data )
            {
            DEBUG( "core_operation_roam_c::process_frame() - unable to create core_ap_data_c instance()" );

            delete entry;
            entry = NULL;
            
            return false_t;
            }

        u32_t entry_score( entry->ap_data->rcpi() );

        /**
         * If this is a roaming situation, APs supporting WPX fast-roam or with
         * cached PMKSAs are preferred over others.
         */
        if( server_m->get_connection_data()->current_ap_data() &&
            server_m->get_connection_data()->iap_data().is_802dot1x_used() &&
            entry->ap_data->is_wpx_fast_roam_available() &&
            is_security_association_available( *server_m->get_connection_data()->current_ap_data() ) )
            {
            DEBUG( "core_operation_roam_c::process_frame() - WPX fast-roam supported" );

            entry->is_cached_sa_available = true_t;
            entry_score += CORE_ROAMING_LIST_BONUS_WPX_FAST_ROAM;
            }
        else if( server_m->get_connection_data()->iap_data().is_eap_used() &&
                 is_security_association_available( *entry->ap_data ) )
            {
            DEBUG( "core_operation_roam_c::process_frame() - a cached PMKSA found" );
                
            entry->is_cached_sa_available = true_t;
            entry_score += CORE_ROAMING_LIST_BONUS_PMKSA;
            }
            
        // These conditions could be added to previous conditions...
        if( server_m->get_connection_data()->current_ap_data() ) // previous connection exists
            {
            if( !server_m->get_connection_data()->current_ap_data()->is_wpx_fast_roam_available() &&  // previous connection was not WPX
                entry->ap_data->is_wpx_fast_roam_available() ) // AND new connection is WPX
                {
                DEBUG( "core_operation_roam_c::process_frame() - reassociation is not possible, previous connection was not WPX, but new connection is WPX" );
                entry->is_cached_sa_available = false_t;
                }
            }
        else // previous connection does not exists
            {
            if( entry->ap_data->is_wpx_fast_roam_available() ) // new connection has WPX
                {
                DEBUG( "core_operation_roam_c::process_frame() - reassociation is not possible, there is no previous connection and new is WPX" );
                entry->is_cached_sa_available = false_t;
                }
            }
                
        /**
         * If AP advertises QBSS Load IE, check the amount of connected
         * stations and adjust the score accordingly.
         */
        const core_frame_qbss_load_ie_c* qbss_load_ie = entry->ap_data->qbss_load_ie();
        if( qbss_load_ie )
            {
            DEBUG1( "core_ap_data_c::process_frame() - QBSS Load IE detected, %u station(s) associated",
                qbss_load_ie->station_count() );

            delete qbss_load_ie;
            }

        roaming_list_m.append(
            entry,
            entry_score );
                
        return true_t;            
        }

    DEBUG1( "core_operation_roam_c::process_frame() - AP is unsuitable, reason %u",
        connect_status_m );

    return false_t;    
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
void core_operation_roam_c::update_roam_failure_count(
    core_error_e request_status,
    core_management_status_e management_status )
    {
    core_roam_failed_reason_e failure_reason( core_roam_failed_reason_none );

    if ( request_status == core_error_eapol_failure ||
         request_status == core_error_eapol_total_failure )
        {
        failure_reason = core_roam_failed_reason_eapol_failure;
        }
    else if ( request_status == core_error_timeout )
        {
        failure_reason = core_roam_failed_reason_timeout;
        }
    else if ( management_status != core_management_status_success )    
        {
        failure_reason = core_roam_failed_reason_ap_status_code;
        }
    else
        {
        failure_reason = core_roam_failed_reason_other_failure;
        }

    server_m->get_core_settings().roam_metrics().inc_roam_attempt_failed_count(
        failure_reason );
    if ( server_m->get_connection_data()->last_roam_failed_reason() == core_roam_failed_reason_none ||
         server_m->get_connection_data()->last_roam_failed_reason() == core_roam_failed_reason_no_suitable_ap )
        {
        server_m->get_connection_data()->set_last_roam_failed_reason( failure_reason );
        }
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
void core_operation_roam_c::handle_neighbor_request()
    {
    /* neighbor report request */
    DEBUG( "core_operation_roam_c::handle_neighbor_request() - Neighbor report bit is on" );
    
    core_frame_action_c* request = NULL;

    // create IE
    core_frame_nr_ie_c* nr_request = core_frame_nr_ie_c::instance( ssid_m );
    if ( nr_request )
        {
        // create frame, frame ownership will belong to caller of this method
        request = core_frame_action_nr_c::instance(
                                            0,                          // Duration
                                            current_ap_m->bssid(),      // Destination 
                                            server_m->own_mac_addr(),   // Source
                                            current_ap_m->bssid(),      // BSSID
                                            0,                          // Sequence Control
                                            core_frame_radio_measurement_action_c::core_dot11_action_rm_type_neighbor_req, // Action Type
                                            0,                          // Dialog Token
                                            nr_request );

        if ( !request )
            {
            DEBUG( "core_operation_roam_c::handle_neighbor_request() - Unable to instantiate core_frame_action_nr_c" );
            }
        
        delete nr_request;
        nr_request = NULL;
        }
    else
        {
        DEBUG( "core_operation_roam_c::handle_neighbor_request() - Unable to instantiate core_frame_nr_ie_c" );
        }

    if ( request )
        {
        DEBUG1( "core_operation_roam_c::handle_neighbor_request() - sending frame (%u bytes): ",
            request->data_length() );
        DEBUG_BUFFER(
            request->data_length(),
            request->data() );
        
        server_m->send_management_frame(
            core_frame_type_dot11,
            request->data_length(),
            request->data(),
            current_ap_m->bssid() );

        delete request;
        request = NULL;
        }
    }
