/**
 * Copyright (c) 2018 - 2020, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include "peer_manager_handler.h"
#include <stdint.h>
#include <string.h>
#include "sdk_errors.h"
#include "app_error.h"
#include "peer_manager.h"
#include "ble_gap.h"
#include "ble_gattc.h"
#include "ble_conn_state.h"
#include "fds.h"
#include "nrf_strerror.h"
#include "sdk_config.h"

#if PM_HANDLER_SEC_DELAY_MS > 0
#include "app_timer.h"
#endif

#define NRF_LOG_MODULE_NAME peer_manager_handler
#if PM_LOG_ENABLED
    #define NRF_LOG_LEVEL       PM_LOG_LEVEL
    #define NRF_LOG_INFO_COLOR  PM_LOG_INFO_COLOR
    #define NRF_LOG_DEBUG_COLOR PM_LOG_DEBUG_COLOR
#else
    #define NRF_LOG_LEVEL       0
#endif // PM_LOG_ENABLED
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
NRF_LOG_MODULE_REGISTER();
#include "nrf_strerror.h"

static const char * m_roles_str[] =
{
    "Invalid Role",
    "Peripheral",
    "Central",
};

static const char * m_sec_procedure_str[] =
{
    "Encryption",
    "Bonding",
    "Pairing",
};

#define PM_EVT_STR(_name) [_name] = STRINGIFY(_name)

static const char * m_event_str[] =
{
    PM_EVT_STR(PM_EVT_BONDED_PEER_CONNECTED),
    PM_EVT_STR(PM_EVT_CONN_SEC_START),
    PM_EVT_STR(PM_EVT_CONN_SEC_SUCCEEDED),
    PM_EVT_STR(PM_EVT_CONN_SEC_FAILED),
    PM_EVT_STR(PM_EVT_CONN_SEC_CONFIG_REQ),
    PM_EVT_STR(PM_EVT_CONN_SEC_PARAMS_REQ),
    PM_EVT_STR(PM_EVT_STORAGE_FULL),
    PM_EVT_STR(PM_EVT_ERROR_UNEXPECTED),
    PM_EVT_STR(PM_EVT_PEER_DATA_UPDATE_SUCCEEDED),
    PM_EVT_STR(PM_EVT_PEER_DATA_UPDATE_FAILED),
    PM_EVT_STR(PM_EVT_PEER_DELETE_SUCCEEDED),
    PM_EVT_STR(PM_EVT_PEER_DELETE_FAILED),
    PM_EVT_STR(PM_EVT_PEERS_DELETE_SUCCEEDED),
    PM_EVT_STR(PM_EVT_PEERS_DELETE_FAILED),
    PM_EVT_STR(PM_EVT_LOCAL_DB_CACHE_APPLIED),
    PM_EVT_STR(PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED),
    PM_EVT_STR(PM_EVT_SERVICE_CHANGED_IND_SENT),
    PM_EVT_STR(PM_EVT_SERVICE_CHANGED_IND_CONFIRMED),
    PM_EVT_STR(PM_EVT_SLAVE_SECURITY_REQ),
    PM_EVT_STR(PM_EVT_FLASH_GARBAGE_COLLECTED),
    PM_EVT_STR(PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED),
};

static const char * m_data_id_str[] =
{
    "Outdated (0)",
    "Service changed pending flag",
    "Outdated (2)",
    "Outdated (3)",
    "Application data",
    "Remote database",
    "Peer rank",
    "Bonding data",
    "Local database",
    "Central address resolution",
};

static const char * m_data_action_str[] =
{
    "Update",
    "Delete"
};

#define PM_SEC_ERR_STR(_name) {.error = _name, .error_str = #_name}

typedef struct
{
    pm_sec_error_code_t error;
    const char * error_str;
} sec_err_str_t;

static const sec_err_str_t m_pm_sec_error_str[] =
{
    PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_PIN_OR_KEY_MISSING),
    PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_MIC_FAILURE),
    PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_DISCONNECT),
    PM_SEC_ERR_STR(PM_CONN_SEC_ERROR_SMP_TIMEOUT),
};

static const char * sec_err_string_get(pm_sec_error_code_t error)
{
    static char errstr[30];

    for (uint32_t i = 0; i < (sizeof(m_pm_sec_error_str)/sizeof(sec_err_str_t)); i++)
    {
        if (m_pm_sec_error_str[i].error == error)
        {
            return m_pm_sec_error_str[i].error_str;
        }
    }

    int len = snprintf(errstr, sizeof(errstr), "%s 0x%hx", (error < PM_CONN_SEC_ERROR_BASE)
                                                            ? "BLE_GAP_SEC_STATUS"
                                                            :"PM_CONN_SEC_ERROR", error);
    UNUSED_VARIABLE(len);
    return errstr;
}


static void _conn_secure(uint16_t conn_handle, bool force)
{
    ret_code_t err_code;

    if (!force)
    {
        pm_conn_sec_status_t status;
        err_code = pm_conn_sec_status_get(conn_handle, &status);
        if (err_code != BLE_ERROR_INVALID_CONN_HANDLE)
        {
            APP_ERROR_CHECK(err_code);
        }

        // If the link is already secured, don't initiate security procedure.
        if (status.encrypted)
        {
            NRF_LOG_DEBUG("Already encrypted, skipping security.");
            return;
        }
    }

    err_code = pm_conn_secure(conn_handle, false);

    if ((err_code == NRF_SUCCESS) || (err_code == NRF_ERROR_BUSY))
    {
        // Success.
    }
    else if (err_code == NRF_ERROR_TIMEOUT)
    {
        NRF_LOG_WARNING("pm_conn_secure() failed because an SMP timeout is preventing security on "\
                        "the link. Disconnecting conn_handle %d.",
                        conn_handle);

        err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_WARNING("sd_ble_gap_disconnect() returned %s on conn_handle %d.",
                            nrf_strerror_get(err_code),
                            conn_handle);
        }
    }
    else if (err_code == NRF_ERROR_INVALID_DATA)
    {
        NRF_LOG_WARNING("pm_conn_secure() failed because the stored data for conn_handle %d does "\
                        "not have a valid key.",
                        conn_handle);
    }
    else if (err_code == BLE_ERROR_INVALID_CONN_HANDLE)
    {
        NRF_LOG_WARNING("pm_conn_secure() failed because conn_handle %d is not a valid connection.",
                        conn_handle);
    }
    else
    {
        NRF_LOG_ERROR("Asserting. pm_conn_secure() returned %s on conn_handle %d.",
                      nrf_strerror_get(err_code),
                      conn_handle);
        APP_ERROR_CHECK(err_code);
    }
}

#if PM_HANDLER_SEC_DELAY_MS > 0
APP_TIMER_DEF(secure_delay_timer);

typedef union
{
    struct
    {
        uint16_t conn_handle;
        bool     force;
    } values;
    void * p_void;
} conn_secure_context_t;

STATIC_ASSERT(sizeof(conn_secure_context_t) <= sizeof(void *), "conn_secure_context_t is too large.");

static void _conn_secure(uint16_t conn_handle, bool force);

static void delayed_conn_secure(void * context)
{
    conn_secure_context_t sec_context = {.p_void = context};
    _conn_secure(sec_context.values.conn_handle, sec_context.values.force);
}

static void conn_secure(uint16_t conn_handle, bool force)
{
    ret_code_t err_code;
    static bool created = false;
    if (!created)
    {
        err_code = app_timer_create(&secure_delay_timer,
                                    APP_TIMER_MODE_SINGLE_SHOT,
                                    delayed_conn_secure);
        APP_ERROR_CHECK(err_code);
        created = true;
    }

    conn_secure_context_t sec_context = {0};
    sec_context.values.conn_handle    = conn_handle;
    sec_context.values.force          = force;

    err_code = app_timer_start(secure_delay_timer,
                               APP_TIMER_TICKS(PM_HANDLER_SEC_DELAY_MS),
                               sec_context.p_void);
    APP_ERROR_CHECK(err_code);
}


#else
static void conn_secure(uint16_t conn_handle, bool force)
{
    _conn_secure(conn_handle, force);
}
#endif


void pm_handler_on_pm_evt(pm_evt_t const * p_pm_evt)
{
    pm_handler_pm_evt_log(p_pm_evt);

    if (p_pm_evt->evt_id == PM_EVT_BONDED_PEER_CONNECTED)
    {
        conn_secure(p_pm_evt->conn_handle, false);
    }
    else if (p_pm_evt->evt_id == PM_EVT_ERROR_UNEXPECTED)
    {
        NRF_LOG_ERROR("Asserting.");
        APP_ERROR_CHECK(p_pm_evt->params.error_unexpected.error);
    }
}


void pm_handler_flash_clean_on_return(void)
{
    // Trigger the mechanism to make more room in flash.
    pm_evt_t storage_full_evt = {.evt_id = PM_EVT_STORAGE_FULL};
    pm_handler_flash_clean(&storage_full_evt);
}


static void rank_highest(pm_peer_id_t peer_id)
{
    // Trigger a pm_peer_rank_highest() with internal bookkeeping.
    pm_evt_t connected_evt = {.evt_id = PM_EVT_BONDED_PEER_CONNECTED, .peer_id = peer_id};
    pm_handler_flash_clean(&connected_evt);
}


void pm_handler_flash_clean(pm_evt_t const * p_pm_evt)
{
    ret_code_t  err_code;
    static bool flash_cleaning       = false;     // Indicates whether garbage collection is currently being run.
    static bool flash_write_after_gc = true;      // Indicates whether a successful write happened after the last garbage
                                                  // collection. If this is false when flash is full, it means just a
                                                  // garbage collection won't work, so some data should be deleted.
    #define RANK_QUEUE_SIZE 8                     // Size of rank_queue.
    #define RANK_QUEUE_INIT() PM_PEER_ID_INVALID, // Initial value of rank_queue.

    //lint -save -e40 -e26 -esym(628,MACRO_REPEAT_8)
    static pm_peer_id_t rank_queue[8]  = {MACRO_REPEAT(RANK_QUEUE_SIZE, RANK_QUEUE_INIT)}; // Queue of rank_highest calls that
                                                                                           // failed because of full flash.
    //lint -restore
    static int          rank_queue_wr = 0;        // Write pointer for rank_queue.

    switch (p_pm_evt->evt_id)
    {
        case PM_EVT_BONDED_PEER_CONNECTED:
            err_code = pm_peer_rank_highest(p_pm_evt->peer_id);
            if ((err_code == NRF_ERROR_STORAGE_FULL) ||
                (err_code == NRF_ERROR_BUSY))
            {
                // Queue pm_peer_rank_highest() call and attempt to clean flash.
                rank_queue[rank_queue_wr] = p_pm_evt->peer_id;
                rank_queue_wr = (rank_queue_wr + 1) % RANK_QUEUE_SIZE;
                pm_handler_flash_clean_on_return();
            }
            else if ((err_code != NRF_ERROR_NOT_SUPPORTED) &&
                     (err_code != NRF_ERROR_INVALID_PARAM) &&
                     (err_code != NRF_ERROR_RESOURCES))
            {
                APP_ERROR_CHECK(err_code);
            }
            else
            {
                NRF_LOG_DEBUG("pm_peer_rank_highest() returned %s for peer id %d",
                              nrf_strerror_get(err_code),
                              p_pm_evt->peer_id);
            }
            break;

        case PM_EVT_CONN_SEC_START:
            break;

        case PM_EVT_CONN_SEC_SUCCEEDED:
            if (   (p_pm_evt->params.conn_sec_succeeded.procedure == PM_CONN_SEC_PROCEDURE_BONDING)
                || (p_pm_evt->params.conn_sec_succeeded.procedure == PM_CONN_SEC_PROCEDURE_ENCRYPTION))
                // PM_CONN_SEC_PROCEDURE_ENCRYPTION in case peer was not recognized at connection time.
            {
                rank_highest(p_pm_evt->peer_id);
            }
            break;

        case PM_EVT_CONN_SEC_FAILED:
        case PM_EVT_CONN_SEC_CONFIG_REQ:
        case PM_EVT_CONN_SEC_PARAMS_REQ:
            break;

        case PM_EVT_STORAGE_FULL:
            if (!flash_cleaning)
            {
                err_code = NRF_SUCCESS;
                NRF_LOG_INFO("Attempting to clean flash.");
                if (!flash_write_after_gc)
                {
                    // Check whether another user of FDS has deleted a record that can be GCed.
                    fds_stat_t fds_stats;
                    err_code = fds_stat(&fds_stats);
                    APP_ERROR_CHECK(err_code);
                    flash_write_after_gc = (fds_stats.dirty_records > 0);
                }
                if (!flash_write_after_gc)
                {
                    pm_peer_id_t peer_id_to_delete;
                    err_code = pm_peer_ranks_get(NULL, NULL, &peer_id_to_delete, NULL);
                    if (err_code == NRF_SUCCESS)
                    {
                        NRF_LOG_INFO("Deleting lowest ranked peer (peer_id: %d)", peer_id_to_delete);
                        err_code = pm_peer_delete(peer_id_to_delete);
                        APP_ERROR_CHECK(err_code);
                        flash_write_after_gc = true;
                    }
                    if (err_code == NRF_ERROR_NOT_FOUND)
                    {
                        NRF_LOG_ERROR("There are no peers to delete.");
                    }
                    else if (err_code == NRF_ERROR_NOT_SUPPORTED)
                    {
                        NRF_LOG_WARNING("Peer ranks functionality is disabled, so no peers are deleted.");
                    }
                    else
                    {
                        APP_ERROR_CHECK(err_code);
                    }
                }
                if (err_code == NRF_SUCCESS)
                {
                    err_code = fds_gc();
                    if (err_code == NRF_SUCCESS)
                    {
                        NRF_LOG_DEBUG("Running flash garbage collection.");
                        flash_cleaning = true;
                    }
                    else if (err_code != FDS_ERR_NO_SPACE_IN_QUEUES)
                    {
                        APP_ERROR_CHECK(err_code);
                    }
                }
            }
            break;

        case PM_EVT_ERROR_UNEXPECTED:
            break;

        case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
            flash_write_after_gc = true;
            break;

        case PM_EVT_PEER_DATA_UPDATE_FAILED:
            break;

        case PM_EVT_PEER_DELETE_SUCCEEDED:
            flash_write_after_gc = true;
            break;

        case PM_EVT_PEER_DELETE_FAILED:
        case PM_EVT_PEERS_DELETE_SUCCEEDED:
        case PM_EVT_PEERS_DELETE_FAILED:
        case PM_EVT_LOCAL_DB_CACHE_APPLIED:
        case PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED:
        case PM_EVT_SERVICE_CHANGED_IND_SENT:
        case PM_EVT_SERVICE_CHANGED_IND_CONFIRMED:
        case PM_EVT_SLAVE_SECURITY_REQ:
            break;

        case PM_EVT_FLASH_GARBAGE_COLLECTED:
            flash_cleaning       = false;
            flash_write_after_gc = false;
            {
                // Reattempt queued pm_peer_rank_highest() calls.
                int rank_queue_rd = rank_queue_wr;
                for (int i = 0; i < RANK_QUEUE_SIZE; i++)
                {
                    pm_peer_id_t peer_id = rank_queue[(i + rank_queue_rd) % RANK_QUEUE_SIZE];
                    if (peer_id != PM_PEER_ID_INVALID)
                    {
                        rank_queue[(i + rank_queue_rd) % RANK_QUEUE_SIZE] = PM_PEER_ID_INVALID;
                        rank_highest(peer_id);
                    }
                }
            }
            break;

        case PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED:
            flash_cleaning = false;

            if (p_pm_evt->params.garbage_collection_failed.error == FDS_ERR_BUSY
             || p_pm_evt->params.garbage_collection_failed.error == FDS_ERR_OPERATION_TIMEOUT)
            {
                // Retry immediately if error is transient.
                pm_handler_flash_clean_on_return();
            }
            break;

        default:
            break;
    }
}


void pm_handler_pm_evt_log(pm_evt_t const * p_pm_evt)
{
    NRF_LOG_DEBUG("Event %s", m_event_str[p_pm_evt->evt_id]);

    switch (p_pm_evt->evt_id)
    {
        case PM_EVT_BONDED_PEER_CONNECTED:
            NRF_LOG_DEBUG("Previously bonded peer connected: role: %s, conn_handle: %d, peer_id: %d",
                          m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)],
                          p_pm_evt->conn_handle,
                          p_pm_evt->peer_id);
            break;

        case PM_EVT_CONN_SEC_START:
            NRF_LOG_DEBUG("Connection security procedure started: role: %s, conn_handle: %d, procedure: %s",
                          m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)],
                          p_pm_evt->conn_handle,
                          m_sec_procedure_str[p_pm_evt->params.conn_sec_start.procedure]);
            break;

        case PM_EVT_CONN_SEC_SUCCEEDED:
            NRF_LOG_INFO("Connection secured: role: %s, conn_handle: %d, procedure: %s",
                         m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)],
                         p_pm_evt->conn_handle,
                         m_sec_procedure_str[p_pm_evt->params.conn_sec_start.procedure]);
            break;

        case PM_EVT_CONN_SEC_FAILED:
            NRF_LOG_INFO("Connection security failed: role: %s, conn_handle: 0x%x, procedure: %s, error: %d",
                         m_roles_str[ble_conn_state_role(p_pm_evt->conn_handle)],
                         p_pm_evt->conn_handle,
                         m_sec_procedure_str[p_pm_evt->params.conn_sec_start.procedure],
                         p_pm_evt->params.conn_sec_failed.error);
            NRF_LOG_DEBUG("Error (decoded): %s",
                          sec_err_string_get(p_pm_evt->params.conn_sec_failed.error));
            break;

        case PM_EVT_CONN_SEC_CONFIG_REQ:
            NRF_LOG_DEBUG("Security configuration request");
            break;

        case PM_EVT_CONN_SEC_PARAMS_REQ:
            NRF_LOG_DEBUG("Security parameter request");
            break;

        case PM_EVT_STORAGE_FULL:
            NRF_LOG_WARNING("Flash storage is full");
            break;

        case PM_EVT_ERROR_UNEXPECTED:
            NRF_LOG_ERROR("Unexpected fatal error occurred: error: %s",
                          nrf_strerror_get(p_pm_evt->params.error_unexpected.error));
            break;

        case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
            NRF_LOG_INFO("Peer data updated in flash: peer_id: %d, data_id: %s, action: %s%s",
                          p_pm_evt->peer_id,
                          m_data_id_str[p_pm_evt->params.peer_data_update_succeeded.data_id],
                          m_data_action_str[p_pm_evt->params.peer_data_update_succeeded.action],
                          p_pm_evt->params.peer_data_update_succeeded.flash_changed ? "" : ", no change");
            break;

        case PM_EVT_PEER_DATA_UPDATE_FAILED:
            // This can happen if the SoftDevice is too busy with BLE operations.
            NRF_LOG_WARNING("Peer data updated failed: peer_id: %d, data_id: %s, action: %s, error: %s",
                            p_pm_evt->peer_id,
                            m_data_id_str[p_pm_evt->params.peer_data_update_failed.data_id],
                            m_data_action_str[p_pm_evt->params.peer_data_update_succeeded.action],
                            nrf_strerror_get(p_pm_evt->params.peer_data_update_failed.error));
            break;

        case PM_EVT_PEER_DELETE_SUCCEEDED:
            NRF_LOG_ERROR("Peer deleted successfully: peer_id: %d", p_pm_evt->peer_id);
            break;

        case PM_EVT_PEER_DELETE_FAILED:
            NRF_LOG_ERROR("Peer deletion failed: peer_id: %d, error: %s",
                          p_pm_evt->peer_id,
                          nrf_strerror_get(p_pm_evt->params.peer_delete_failed.error));
            break;

        case PM_EVT_PEERS_DELETE_SUCCEEDED:
            NRF_LOG_INFO("All peers deleted.");
            break;

        case PM_EVT_PEERS_DELETE_FAILED:
            NRF_LOG_ERROR("All peer deletion failed: error: %s",
                          nrf_strerror_get(p_pm_evt->params.peers_delete_failed_evt.error));
            break;

        case PM_EVT_LOCAL_DB_CACHE_APPLIED:
            NRF_LOG_DEBUG("Previously stored local DB applied: conn_handle: %d, peer_id: %d",
                          p_pm_evt->conn_handle,
                          p_pm_evt->peer_id);
            break;

        case PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED:
            // This can happen when the local DB has changed.
            NRF_LOG_WARNING("Local DB could not be applied: conn_handle: %d, peer_id: %d",
                            p_pm_evt->conn_handle,
                            p_pm_evt->peer_id);
            break;

        case PM_EVT_SERVICE_CHANGED_IND_SENT:
            NRF_LOG_DEBUG("Sending Service Changed indication.");
            break;

        case PM_EVT_SERVICE_CHANGED_IND_CONFIRMED:
            NRF_LOG_DEBUG("Service Changed indication confirmed.");
            break;

        case PM_EVT_SLAVE_SECURITY_REQ:
            NRF_LOG_DEBUG("Security Request received from peer.");
            break;

        case PM_EVT_FLASH_GARBAGE_COLLECTED:
            NRF_LOG_DEBUG("Flash garbage collection complete.");
            break;

        case PM_EVT_FLASH_GARBAGE_COLLECTION_FAILED:
            NRF_LOG_WARNING("Flash garbage collection failed with error %s.",
                            nrf_strerror_get(p_pm_evt->params.garbage_collection_failed.error));
            break;

        default:
            NRF_LOG_WARNING("Unexpected PM event ID: 0x%x.", p_pm_evt->evt_id);
            break;
    }
}


void pm_handler_disconnect_on_sec_failure(pm_evt_t const * p_pm_evt)
{
    ret_code_t err_code;

    if (p_pm_evt->evt_id == PM_EVT_CONN_SEC_FAILED)
    {
        NRF_LOG_WARNING("Disconnecting conn_handle %d.", p_pm_evt->conn_handle);
        err_code = sd_ble_gap_disconnect(p_pm_evt->conn_handle,
                                         BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
        if ((err_code != NRF_ERROR_INVALID_STATE) && (err_code != BLE_ERROR_INVALID_CONN_HANDLE))
        {
            APP_ERROR_CHECK(err_code);
        }
    }
}


void pm_handler_disconnect_on_insufficient_sec(pm_evt_t const * p_pm_evt,
                                               pm_conn_sec_status_t * p_min_conn_sec)
{
    if (p_pm_evt->evt_id == PM_EVT_CONN_SEC_SUCCEEDED)
    {
        if (!pm_sec_is_sufficient(p_pm_evt->conn_handle, p_min_conn_sec))
        {
            NRF_LOG_WARNING("Connection security is insufficient, disconnecting.");
            ret_code_t err_code = sd_ble_gap_disconnect(p_pm_evt->conn_handle,
                                                    BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
        }
    }
}


void pm_handler_secure_on_connection(ble_evt_t const * p_ble_evt)
{
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            NRF_LOG_DEBUG("Connected, securing connection. conn_handle: %d", p_ble_evt->evt.gap_evt.conn_handle);
            conn_secure(p_ble_evt->evt.gap_evt.conn_handle, false);
            break;

#if PM_HANDLER_SEC_DELAY_MS > 0
        case BLE_GAP_EVT_DISCONNECTED:
        {
            ret_code_t err_code = app_timer_stop(secure_delay_timer);
            APP_ERROR_CHECK(err_code);
        } break;
#endif

        default:
            break;
    }
}


void pm_handler_secure_on_error(ble_evt_t const * p_ble_evt)
{
    if ((p_ble_evt->header.evt_id >= BLE_GATTC_EVT_BASE) && (p_ble_evt->header.evt_id <= BLE_GATTC_EVT_LAST))
    {
        if ((p_ble_evt->evt.gattc_evt.gatt_status == BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION) ||
            (p_ble_evt->evt.gattc_evt.gatt_status == BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION))
        {
            NRF_LOG_INFO("GATTC procedure (evt id 0x%x) failed because it needs encryption. Bonding: conn_handle=%d",
                         p_ble_evt->header.evt_id,
                         p_ble_evt->evt.gattc_evt.conn_handle);
            conn_secure(p_ble_evt->evt.gattc_evt.conn_handle, true);
        }
    }
}