PM_EVT_CONN_SEC_FAILED reason and delete_bond in nRF52832 peripheral

We have a product that uses nRF52832 and works as a peripheral, connecting to mobile phones. 
Lately we have noticed that in some clients with Android phones, they cannot connect to the device. The logging info that we get is that function delete_bonds is called and indeed the issue is solved after the client erases bond info from their phone and implement pair and bond again. 

In our code, we call delete_bonds function only in pm_evt_handler, in case of event PM_EVT_CONN_SEC_FAILED. 
The security is SEC_MITM, with static passkey, no LESC, and all is handled by the peer manager. 

So my questions are:
1. What could cause the device to delete bonding information? One reason I am thinking is probably when storage gets full, but could this be a case? If not, what could be the reason?
2. Is there any better way to handle this event? 
3. I have also noticed sometimes reason for disconnection 0x22 (BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT). What does this mean and how should I handle it? 

Here are some parts of my code:

/**@brief Function for the Peer Manager initialization.
 */
static void peer_manager_init(void)
{
    ble_gap_sec_params_t sec_param;
    ret_code_t           err_code;

    err_code = pm_init();
    hardfault = app_error_check_logger(err_code, true, log_str[PEER_MANAGER_INIT], 0);

    memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));

    // Security parameters to be used for all security procedures. These are common parameters for bonding.
    sec_param.bond           = 1;
    sec_param.mitm           = 0;
    sec_param.lesc           = 0;
    sec_param.keypress       = 0;
    sec_param.io_caps        = BLE_GAP_IO_CAPS_DISPLAY_ONLY;
    sec_param.oob            = 0;
    sec_param.min_key_size   = 7;
    sec_param.max_key_size   = 16;
    sec_param.kdist_own.enc  = 1;
    sec_param.kdist_own.id   = 1;
    sec_param.kdist_peer.enc = 1;
    sec_param.kdist_peer.id  = 1;

    err_code = pm_sec_params_set(&sec_param);   // sets security parameters for pairing and bonding
    hardfault = app_error_check_logger(err_code, true, log_str[PEER_MANAGER_INIT], 1);

    err_code = pm_register(pm_evt_handler);   // register an event handler for the module
    hardfault = app_error_check_logger(err_code, true, log_str[PEER_MANAGER_INIT], 2);
}

/**@brief Function for handling Peer Manager events.
 *
 * @param[in] p_evt  Peer Manager event.
 */

static void pm_evt_handler(pm_evt_t const * p_evt)
{
    pm_handler_on_pm_evt(p_evt);    // Logging peer events. Starts encryption if connected to a bonded device.
    pm_handler_disconnect_on_sec_failure(p_evt);    // Disconnects if the connection was not secured.
    pm_handler_flash_clean(p_evt);

    switch (p_evt->evt_id)
    {
        case PM_EVT_CONN_SEC_SUCCEEDED:   //a link has been encrypted, result of a call of pm_conn_secure or of an action by the peer.
            m_peer_id = p_evt->peer_id;
            pm_local_database_has_changed();
            break;

        case PM_EVT_PEERS_DELETE_SUCCEEDED:   // a peer was cleared from flash storage (result of pm_peer_delete)
            break;

        case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: // a piece of peer data was tored, updated or cleared in flash storage.
            if (     p_evt->params.peer_data_update_succeeded.flash_changed
                 && (p_evt->params.peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_BONDING))
            {
                NRF_LOG_INFO("New Bond. Peer data update succeeded.");
            }
            break;
        case PM_EVT_CONN_SEC_FAILED:    // a pairing or encryption procedure has failed. in some cases, this means that security is not possible on this link.
            
            delete_bonds();
            
            break;

        default:
            break;
    }
}

/**@brief Clear bond information from persistent storage.
 */
static void delete_bonds(void)
{
    ret_code_t err_code;


    NRF_LOG_INFO("Erase bonds!");

    err_code = pm_peers_delete();
    hardfault = app_error_check_logger(err_code, true, log_str[DELETE_BONDS], 0);
}

We have now added some more logs at the PM_EVT_CONN_SEC_FAILED about the procedure and the error (provided by pm_conn_secure_failed_evt_t struct) that will probably help in the future, but unfortunately we haven't had this info before to provide you also. 
The problem is also, that we cannot reproduce the error in order to solve it, so hopefully you could have some ideas.
Thank you very much in advance.

Best regards
Dimitra

Parents
  • Hi Dimitra

    It seems a bit odd to me to delete bonds when PM_EVT_CONN_SEC_FAILED occurs, since this could occur for a multitude of reasons. As an example if some unknown central device connects to you, and the pairing fails for whatever reason, then the bond to your intended device will be removed. 

    Is there any particular reason you are doing this? Did you copy this behavior from one of the standard examples in the SDK? 

    Would removing this cause any issues in your application? 

    Best regards
    Torbjørn

  • Is there any particular reason you are doing this?

    Our initial thought was that calling delete_bonds when security fails, would be safe if something wrong happens with the bonding of the intended device, and deleting the bonds would allow the target device to reconnect and bond again. 

    Also, this passed all internal testing, it never occured on our testing mobile devices, so didn't think it was wrong. 

    Now that you mentioned it, I can see that this is not recommended in standard examples and I also cannot find similar suggestion in devzone. 

    Would removing this cause any issues in your application? 

    First, removing this would solve our issue, since we have noticed that  the call of delete_bonds function occurs in several clients, which are not able then to connect, and must do unpair and pair again. This is not user friendly. 

    1. However, if we remove this call when security fails, then where whould we call it for safety, so that the device will delete its bonds when it will be necessary to do so?

    2. In ble_peripheral/ble_app_hrs example, delete bonds is only called here:

    /**@brief Function for starting advertising.
     */
    void advertising_start(bool erase_bonds)
    {
        if (erase_bonds == true)
        {
            delete_bonds();
            // Advertising is started by PM_EVT_PEERS_DELETE_SUCCEEDED event.
        }
        else
        {
            ret_code_t err_code;
    
            err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
            APP_ERROR_CHECK(err_code);
        }
    }

    and the variable erase_bonds becomes true when a button is pressed. We do not have and do not want such a functionality, our board has only one button for reset purpose. 
    So, in what event would it be safe to call delete_bonds, so that doesn't occur casually, but only when it is ultimately necessary, meaning that the client's/the target phone cannot connect. 

    Best regards, 
    Dimitra

  • Hi Dimitra

    Unless you want some kind of 'factory reset' function in your product there is no particular need to call delete_bonds() anywhere. 

    Instead, what you might want to consider is to enable the allow_repairing functionality, by adding the following code to your pm_evt_handler(..) function: 

    case PM_EVT_CONN_SEC_CONFIG_REQ:
    {
      // Allow or reject pairing request from an already bonded peer.
      pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true};
      pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
    } break;

    Then bonding will still succeed if you inadvertently delete the bonds on the central side, and try to reconnect. Without .allow_repairing set the only other way to handle this situation would be to delete the bonds. 

    Secondly, it is recommended to call the pm_handler_flash_clean(..) function in the pm_evt_handler, as this allows the pm library to delete old bonds in the case that you run out of room for storing new bonds. 

    The ble_app_hids_keyboard example in the SDK shows you how this can be done:

    /**@brief Function for handling Peer Manager events.
     *
     * @param[in] p_evt  Peer Manager event.
     */
    static void pm_evt_handler(pm_evt_t const * p_evt)
    {
        pm_handler_on_pm_evt(p_evt);
        pm_handler_disconnect_on_sec_failure(p_evt);
        pm_handler_flash_clean(p_evt);
    
        switch (p_evt->evt_id)
        {
             .......
        }
    }
    

    Best regards
    Torbjørn

  • Instead, what you might want to consider is to enable the allow_repairing functionality, by adding the following code to your pm_evt_handler(..) function: 

    Thank you, I have added this and seems to work fine so far.. 

    Secondly, it is recommended to call the pm_handler_flash_clean(..) function in the pm_evt_handler, as this allows the pm library to delete old bonds in the case that you run out of room for storing new bonds. 

    I had already included this call in pm_evt_handler! 
    Now my code is this:

    static void pm_evt_handler(pm_evt_t const * p_evt)
    {
        pm_handler_on_pm_evt(p_evt);    // Logging peer events. Starts encryption if connected to a bonded device.
        pm_handler_disconnect_on_sec_failure(p_evt);    // Disconnects if the connection was not secured.
        pm_handler_flash_clean(p_evt);
    
        switch (p_evt->evt_id)
        {
            case PM_EVT_CONN_SEC_SUCCEEDED:   //a link has been encrypted, result of a call of pm_conn_secure or of an action by the peer.
                m_peer_id = p_evt->peer_id;
                pm_local_database_has_changed();
                break;
    
            case PM_EVT_PEERS_DELETE_SUCCEEDED:   // a peer was cleared from flash storage (result of pm_peer_delete)
                NRF_LOG_INFO("PM_EVT_PEERS_DELETE_SUCCEEDED");
                advertising_start(false);
                break;
    
            case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: // a piece of peer data was tored, updated or cleared in flash storage.
                if (     p_evt->params.peer_data_update_succeeded.flash_changed
                     && (p_evt->params.peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_BONDING))
                {
                    NRF_LOG_INFO("New Bond. Peer data update succeeded.");
                }
                break;
            case PM_EVT_CONN_SEC_FAILED:    // a pairing or encryption procedure has failed. in some cases, this means that security is not possible on this link.
                NRF_LOG_INFO("Pairing or Encryption procedure failed. Peer id=%d, Error=%x, Procedure=%d, Source=%d", p_evt->peer_id, p_evt->params.conn_sec_failed.error,
                                                                                            p_evt->params.conn_sec_failed.procedure,p_evt->params.conn_sec_failed.error_src);
                
                break;
             case PM_EVT_CONN_SEC_CONFIG_REQ:
             {
                // Allow or reject pairing request from an already bonded peer.
                NRF_LOG_INFO("Repairing Process was initiated.");
                pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true};
                pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
             } 
             break;
    
            default:
                break;
        }
    }

    1. Can you confirm please that this should work fine?
    2. I have tested the case where the storage is full, and how my device will operate if the client do not collect data soon. 
    The functionality is this: The device collects data, the client will connect with the phone and retrieve data over BLE, and as soon as we send one packet of data to the client we delete the record from fds. 
    So, in the extreme situation when the client leaves the device collecting and does not connect in order to have some data deleted, the storage will eventually be full and then when we connect there is some strange behaviour (or not?) on behalf of peer manager: 



    I am trying to connect, and seems that there is bond (see line BLE_GAP_AUTH_STATUS bond=0x1), but then it tries to delete the peers, and it seems that it does delete it cause later the error 1006 occurs, which means that the phone has bond and device hasn't. 

    At a second attempt this happens: 

    Also, once it let me connected, and I was able to retrieve some data in order to have some deleted, but on a second connection it asked for the passkey again. 

    How should I handle this? Is there a clever way to inform the client that the device has not stored bond data and therefore they need to delete bond data from phone? 
    On the other hand, we are considering to delete some data when the flash is close to get full, so that the connection problem doesn't occur.


    Unless you want some kind of 'factory reset' function in your product

    In an attempt of adding this functionality in the product, we added the lines: 

    int main(void)
    {
      //Initialization
      log_init();                             //Initialize logs.
      int32_t reset_reason = NRF_POWER->RESETREAS;
      NRF_LOG_INFO("Reset reason = 0x%x.\n", reset_reason);
      
      ... // other initializations 
      
      if(reset_reason != 0)
      {
            
            if(reset_reason == 1)   // if pin-reset, delete bonds
            {
                  advertising_start(true);
            }
            else
            {
                  advertising_start(false);
            }
      }

    is this safe and correct? We will have to do more testing of course, but maybe there is another way to know the cause of reset.

    Best regards, 
    Dimitra

Reply
  • Instead, what you might want to consider is to enable the allow_repairing functionality, by adding the following code to your pm_evt_handler(..) function: 

    Thank you, I have added this and seems to work fine so far.. 

    Secondly, it is recommended to call the pm_handler_flash_clean(..) function in the pm_evt_handler, as this allows the pm library to delete old bonds in the case that you run out of room for storing new bonds. 

    I had already included this call in pm_evt_handler! 
    Now my code is this:

    static void pm_evt_handler(pm_evt_t const * p_evt)
    {
        pm_handler_on_pm_evt(p_evt);    // Logging peer events. Starts encryption if connected to a bonded device.
        pm_handler_disconnect_on_sec_failure(p_evt);    // Disconnects if the connection was not secured.
        pm_handler_flash_clean(p_evt);
    
        switch (p_evt->evt_id)
        {
            case PM_EVT_CONN_SEC_SUCCEEDED:   //a link has been encrypted, result of a call of pm_conn_secure or of an action by the peer.
                m_peer_id = p_evt->peer_id;
                pm_local_database_has_changed();
                break;
    
            case PM_EVT_PEERS_DELETE_SUCCEEDED:   // a peer was cleared from flash storage (result of pm_peer_delete)
                NRF_LOG_INFO("PM_EVT_PEERS_DELETE_SUCCEEDED");
                advertising_start(false);
                break;
    
            case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED: // a piece of peer data was tored, updated or cleared in flash storage.
                if (     p_evt->params.peer_data_update_succeeded.flash_changed
                     && (p_evt->params.peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_BONDING))
                {
                    NRF_LOG_INFO("New Bond. Peer data update succeeded.");
                }
                break;
            case PM_EVT_CONN_SEC_FAILED:    // a pairing or encryption procedure has failed. in some cases, this means that security is not possible on this link.
                NRF_LOG_INFO("Pairing or Encryption procedure failed. Peer id=%d, Error=%x, Procedure=%d, Source=%d", p_evt->peer_id, p_evt->params.conn_sec_failed.error,
                                                                                            p_evt->params.conn_sec_failed.procedure,p_evt->params.conn_sec_failed.error_src);
                
                break;
             case PM_EVT_CONN_SEC_CONFIG_REQ:
             {
                // Allow or reject pairing request from an already bonded peer.
                NRF_LOG_INFO("Repairing Process was initiated.");
                pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true};
                pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
             } 
             break;
    
            default:
                break;
        }
    }

    1. Can you confirm please that this should work fine?
    2. I have tested the case where the storage is full, and how my device will operate if the client do not collect data soon. 
    The functionality is this: The device collects data, the client will connect with the phone and retrieve data over BLE, and as soon as we send one packet of data to the client we delete the record from fds. 
    So, in the extreme situation when the client leaves the device collecting and does not connect in order to have some data deleted, the storage will eventually be full and then when we connect there is some strange behaviour (or not?) on behalf of peer manager: 



    I am trying to connect, and seems that there is bond (see line BLE_GAP_AUTH_STATUS bond=0x1), but then it tries to delete the peers, and it seems that it does delete it cause later the error 1006 occurs, which means that the phone has bond and device hasn't. 

    At a second attempt this happens: 

    Also, once it let me connected, and I was able to retrieve some data in order to have some deleted, but on a second connection it asked for the passkey again. 

    How should I handle this? Is there a clever way to inform the client that the device has not stored bond data and therefore they need to delete bond data from phone? 
    On the other hand, we are considering to delete some data when the flash is close to get full, so that the connection problem doesn't occur.


    Unless you want some kind of 'factory reset' function in your product

    In an attempt of adding this functionality in the product, we added the lines: 

    int main(void)
    {
      //Initialization
      log_init();                             //Initialize logs.
      int32_t reset_reason = NRF_POWER->RESETREAS;
      NRF_LOG_INFO("Reset reason = 0x%x.\n", reset_reason);
      
      ... // other initializations 
      
      if(reset_reason != 0)
      {
            
            if(reset_reason == 1)   // if pin-reset, delete bonds
            {
                  advertising_start(true);
            }
            else
            {
                  advertising_start(false);
            }
      }

    is this safe and correct? We will have to do more testing of course, but maybe there is another way to know the cause of reset.

    Best regards, 
    Dimitra

Children
Related