Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Peer manager and FDS, new records at each connection.

Hi,

I am working with a NRF52840 with the SDK16 and I am facing field issues with the peer manager flash usage. At each connection the app subscribes to characteristic and the local data base of the MCU is updated (write to the flash). The main problem is since the app subscribe to multiple services at connection, MCU peer data are updated multiple time (644bytes) at each connection.

[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Peer rank, action: Update, no change
[00000015] <info> BLEDriver: Connected to a previously bonded device.
[00000015] <info> SecurityService: Bonded, not yet secured.
[00000015] <debug> nrf_ble_gatt: Requesting to update ATT MTU to 247 bytes on connection 0x0.
[00000015] <info> BLEDriver: Connected.
[00000015] <info> SecurityService: Bonded, not yet secured.
[00000015] <info> BLEDriver: Encryption started.
[00000015] <info> peer_manager_handler: Connection secured: role: Peripheral, conn_handle: 0, procedure: Encryption
[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Peer rank, action: Update, no change
[00000015] <info> BLEDriver: Connection secured: role: peripheral, conn_handle: 0x0, procedure: 0.
[00000015] <info> SecurityService: Secured.
[00000015] <debug> nrf_ble_gatt: ATT MTU updated to 185 bytes on connection 0x0 (response).
[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Local database, action: Update
[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Local database, action: Update
[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Local database, action: Update
[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Local database, action: Update
[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Local database, action: Update
[00000015] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Local database, action: Update
[00000016] <info> peer_manager_handler: Peer data updated in flash: peer_id: 1, data_id: Local database, action: Update

Since our device could be used multiple time per day (connection in background when in range) and support multiple peers, the flash memory is really stressed and do a lot of Garbage collection and lead to various issue.

We found a workaround on Android where the phone first check their local data base at connection and subscribe only if the characteristic descriptor is not up to date. It works well and we don't experience issue. 

However, on iOS, it seems that the OS clears its cached value of the Client Characteristic Configuration descriptor (0x2902) after each disconnection. I checked in the iOS NRF connect app and after a reconnection, the previous characteristic state is overwritten to false. I already look at other post without having a clear solution eg : https://devzone.nordicsemi.com/f/nordic-q-a/59866/new-fds-record-on-each-connection.

What could be a short term solution on iOS or in the device firmware ? 

Thanks,

Florian

  • Hello Florian,

    I wonder if this refreshing of CCCDs may have been introduced in more recent versions of iOS as I haven't started to see reports of this problem until recently. I guess maybe they have implemented it as a workaround in case there are peripherals devices out there that don't preserve CCCDs in their local database.

    Anyway, I think the solution to avoid redundant flash writes is to patch the Peer manage so it only updates its local database when there's is an actual change made to the CCCDs.

    Here's a possible fix I made (tested with SDK 17.0.0):

    diff --git a/gatt_cache_manager.c b/gatt_cache_manager.c
    index 9e4677c..e78a693 100644
    --- a/gatt_cache_manager.c
    +++ b/gatt_cache_manager.c
    @@ -284,6 +284,44 @@ static bool local_db_update_in_evt(uint16_t conn_handle)
         return success;
     }
     
    +/**@brief Function for checking if local db is up to date.
    + *
    + * @param[in]  conn_handle  The connection to check.
    + *
    + * @return Return whether local db needs to be updated or not.
    + */
    +
    +static bool local_db_needs_update(uint16_t conn_handle)
    +{
    +    ret_code_t err_code;
    +    pm_peer_data_flash_t peer_data;
    +    bool needs_update = true;
    +    uint16_t len;
    +
    +
    +    err_code = pdb_peer_data_ptr_get(im_peer_id_get_by_conn_handle(conn_handle),
    +                                  PM_PEER_DATA_ID_GATT_LOCAL,
    +                                  &peer_data);
    +    ASSERT(err_code == NRF_SUCCESS);
    +
    +    len = peer_data.p_local_gatt_db->len;
    +
    +    uint8_t sys_attr_data[len];
    +    
    +    err_code = sd_ble_gatts_sys_attr_get(conn_handle, sys_attr_data, &len,
    +                                         peer_data.p_local_gatt_db->flags);
    +    
    +    ASSERT(err_code == NRF_SUCCESS);
    +
    +    if(memcmp(sys_attr_data, &peer_data.p_local_gatt_db->data, len) == 0)
    +    {
    +        NRF_LOG_DEBUG("Local db is up to date");
    +        needs_update = false;
    +    }
    +
    +   return needs_update;  
    +}
    +
     #if PM_SERVICE_CHANGED_ENABLED
     
     /**@brief Function for getting the value of the CCCD for the service changed characteristic.
    @@ -702,13 +740,16 @@ void gcm_ble_evt_handler(ble_evt_t const * p_ble_evt)
     #endif
     
             case BLE_GATTS_EVT_WRITE:
    -            if (cccd_written(&p_ble_evt->evt.gatts_evt.params.write))
    +        {
    +            /* Skip local db update if new CCCD setting is identical to the previous one */
    +            if (cccd_written(&p_ble_evt->evt.gatts_evt.params.write) &&
    +               (local_db_needs_update(conn_handle))) 
                 {
                     local_db_update(conn_handle, true);
                     update_pending_flags_check();
                 }
                 break;
    -
    +        }
             case BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP:
             {
                 bool handle_found = false;
    

    /**
     * Copyright (c) 2015 - 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 "sdk_common.h"
    #if NRF_MODULE_ENABLED(PEER_MANAGER)
    #include "gatt_cache_manager.h"
    
    #include "ble_gap.h"
    #include "ble_err.h"
    #include "ble_conn_state.h"
    #include "peer_manager_types.h"
    #include "peer_manager_internal.h"
    #include "id_manager.h"
    #include "gatts_cache_manager.h"
    #include "peer_data_storage.h"
    #include "peer_database.h"
    #include "nrf_mtx.h"
    
    #define NRF_LOG_MODULE_NAME peer_manager_gcm
    #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"
    
    // The number of registered event handlers.
    #define GCM_EVENT_HANDLERS_CNT      (sizeof(m_evt_handlers) / sizeof(m_evt_handlers[0]))
    
    // GATT Cache Manager event handler in Peer Manager.
    extern void pm_gcm_evt_handler(pm_evt_t * p_gcm_evt);
    
    // GATT Cache Manager events' handlers.
    // The number of elements in this array is GCM_EVENT_HANDLERS_CNT.
    static pm_evt_handler_internal_t m_evt_handlers[] =
    {
        pm_gcm_evt_handler
    };
    
    static bool                           m_module_initialized;
    static nrf_mtx_t                      m_db_update_in_progress_mutex;  /**< Mutex indicating whether a local DB write operation is ongoing. */
    static ble_conn_state_user_flag_id_t  m_flag_local_db_update_pending; /**< Flag ID for flag collection to keep track of which connections need a local DB update procedure. */
    static ble_conn_state_user_flag_id_t  m_flag_local_db_apply_pending;  /**< Flag ID for flag collection to keep track of which connections need a local DB apply procedure. */
    static ble_conn_state_user_flag_id_t  m_flag_service_changed_pending; /**< Flag ID for flag collection to keep track of which connections need to be sent a service changed indication. */
    static ble_conn_state_user_flag_id_t  m_flag_service_changed_sent;    /**< Flag ID for flag collection to keep track of which connections have been sent a service changed indication and are waiting for a handle value confirmation. */
    static ble_conn_state_user_flag_id_t  m_flag_car_update_pending;      /**< Flag ID for flag collection to keep track of which connections need to have their Central Address Resolution value stored. */
    static ble_conn_state_user_flag_id_t  m_flag_car_handle_queried;      /**< Flag ID for flag collection to keep track of which connections are pending Central Address Resolution handle reply. */
    static ble_conn_state_user_flag_id_t  m_flag_car_value_queried;       /**< Flag ID for flag collection to keep track of which connections are pending Central Address Resolution value reply. */
    
    #ifdef PM_SERVICE_CHANGED_ENABLED
        STATIC_ASSERT(PM_SERVICE_CHANGED_ENABLED || !NRF_SDH_BLE_SERVICE_CHANGED,
                     "PM_SERVICE_CHANGED_ENABLED should be enabled if NRF_SDH_BLE_SERVICE_CHANGED is enabled.");
    #else
        #define PM_SERVICE_CHANGED_ENABLED 1
    #endif
    
    /**@brief Function for resetting the module variable(s) of the GSCM module.
     *
     * @param[out]  The instance to reset.
     */
    static void internal_state_reset()
    {
        m_module_initialized = false;
    }
    
    
    static void evt_send(pm_evt_t * p_gcm_evt)
    {
        p_gcm_evt->peer_id = im_peer_id_get_by_conn_handle(p_gcm_evt->conn_handle);
    
        for (uint32_t i = 0; i < GCM_EVENT_HANDLERS_CNT; i++)
        {
            m_evt_handlers[i](p_gcm_evt);
        }
    }
    
    
    /**@brief Function for checking a write event for whether a CCCD was written during the write
     *        operation.
     *
     * @param[in]  p_write_evt  The parameters of the write event.
     *
     * @return  Whether the write was on a CCCD.
     */
    static bool cccd_written(ble_gatts_evt_write_t const * p_write_evt)
    {
        return (    (p_write_evt->op        == BLE_GATTS_OP_WRITE_REQ)
                 && (p_write_evt->uuid.type == BLE_UUID_TYPE_BLE)
                 && (p_write_evt->uuid.uuid == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG)
               );
    }
    
    
    /**@brief Function for sending an PM_EVT_ERROR_UNEXPECTED event.
     *
     * @param[in]  conn_handle  The connection handle the event pertains to.
     * @param[in]  err_code     The unexpected error that occurred.
     */
    static void send_unexpected_error(uint16_t conn_handle, ret_code_t err_code)
    {
        pm_evt_t error_evt =
        {
            .evt_id = PM_EVT_ERROR_UNEXPECTED,
            .conn_handle = conn_handle,
            .params =
            {
                .error_unexpected =
                {
                    .error = err_code
                }
            }
        };
        evt_send(&error_evt);
    }
    
    
    /**@brief Function for performing the local DB update procedure in an event context, where no return
     *        code can be given.
     *
     * @details This function will do the procedure, and check the result, set a flag if needed, and
     *          send an event if needed.
     *
     * @param[in]  conn_handle  The connection to perform the procedure on.
     */
    static void local_db_apply_in_evt(uint16_t conn_handle)
    {
        bool set_procedure_as_pending = false;
        ret_code_t err_code;
        pm_evt_t event =
        {
            .conn_handle = conn_handle,
        };
    
        if (conn_handle == BLE_CONN_HANDLE_INVALID)
        {
            return;
        }
    
        err_code = gscm_local_db_cache_apply(conn_handle);
    
        switch (err_code)
        {
            case NRF_SUCCESS:
                event.evt_id = PM_EVT_LOCAL_DB_CACHE_APPLIED;
    
                evt_send(&event);
                break;
    
            case NRF_ERROR_BUSY:
                set_procedure_as_pending = true;
                break;
    
            case NRF_ERROR_INVALID_DATA:
                event.evt_id = PM_EVT_LOCAL_DB_CACHE_APPLY_FAILED;
    
                NRF_LOG_WARNING("The local database has changed, so some subscriptions to notifications "\
                                "and indications could not be restored for conn_handle %d",
                                conn_handle);
                evt_send(&event);
                break;
    
            case BLE_ERROR_INVALID_CONN_HANDLE:
                /* Do nothing */
                break;
    
            default:
                NRF_LOG_ERROR("gscm_local_db_cache_apply() returned %s which should not happen. "\
                              "conn_handle: %d",
                              nrf_strerror_get(err_code),
                              conn_handle);
                send_unexpected_error(conn_handle, err_code);
                break;
        }
    
        ble_conn_state_user_flag_set(conn_handle, m_flag_local_db_apply_pending, set_procedure_as_pending);
    }
    
    
    /**@brief Function for asynchronously starting a DB update procedure.
     *
     * @note This procedure can only be started asynchronously.
     *
     * @param[in]  conn_handle  The connection to perform the procedure on.
     * @param[in]  update       Whether to perform the procedure.
     */
    static __INLINE void local_db_update(uint16_t conn_handle, bool update)
    {
        ble_conn_state_user_flag_set(conn_handle, m_flag_local_db_update_pending, update);
    }
    
    
    /**@brief Function for performing the local DB update procedure in an event context, where no return
     *        code can be given.
     *
     * @details This function will do the procedure, and check the result, set a flag if needed, and
     *          send an event if needed.
     *
     * @param[in]  conn_handle  The connection to perform the procedure on.
     */
    static bool local_db_update_in_evt(uint16_t conn_handle)
    {
        bool set_procedure_as_pending = false;
        bool success = false;
        ret_code_t err_code = gscm_local_db_cache_update(conn_handle);
    
        switch (err_code)
        {
            case NRF_SUCCESS:
                success = true;
                break;
    
            case BLE_ERROR_INVALID_CONN_HANDLE:
                /* Do nothing */
                break;
    
            case NRF_ERROR_BUSY:
                set_procedure_as_pending = true;
                break;
    
            case NRF_ERROR_STORAGE_FULL:
            {
                pm_evt_t event =
                {
                    .evt_id      = PM_EVT_STORAGE_FULL,
                    .conn_handle = conn_handle,
                };
    
                NRF_LOG_WARNING("Flash full. Could not store data for conn_handle: %d", conn_handle);
                evt_send(&event);
                break;
            }
    
            default:
                NRF_LOG_ERROR("gscm_local_db_cache_update() returned %s for conn_handle: %d",
                              nrf_strerror_get(err_code),
                              conn_handle);
                send_unexpected_error(conn_handle, err_code);
                break;
        }
    
        local_db_update(conn_handle, set_procedure_as_pending);
    
        return success;
    }
    
    /**@brief Function for checking if local db is up to date.
     *
     * @param[in]  conn_handle  The connection to check.
     *
     * @return Return whether local db needs to be updated or not.
     */
    
    static bool local_db_needs_update(uint16_t conn_handle)
    {
        ret_code_t err_code;
        pm_peer_data_flash_t peer_data;
        bool needs_update = true;
        uint16_t len;
    
    
        err_code = pdb_peer_data_ptr_get(im_peer_id_get_by_conn_handle(conn_handle),
                                      PM_PEER_DATA_ID_GATT_LOCAL,
                                      &peer_data);
        ASSERT(err_code == NRF_SUCCESS);
    
        len = peer_data.p_local_gatt_db->len;
    
        uint8_t sys_attr_data[len];
        
        err_code = sd_ble_gatts_sys_attr_get(conn_handle, sys_attr_data, &len,
                                             peer_data.p_local_gatt_db->flags);
        
        ASSERT(err_code == NRF_SUCCESS);
    
        if(memcmp(sys_attr_data, &peer_data.p_local_gatt_db->data, len) == 0)
        {
            NRF_LOG_DEBUG("Local db is up to date");
            needs_update = false;
        }
    
       return needs_update;  
    }
    
    #if PM_SERVICE_CHANGED_ENABLED
    
    /**@brief Function for getting the value of the CCCD for the service changed characteristic.
     *
     * @details This function will search all system handles consecutively.
     *
     * @param[in]  conn_handle  The connection to check.
     * @param[out] p_cccd       The CCCD value of the service changed characteristic for this link.
     *
     * @return Any error from @ref sd_ble_gatts_value_get or @ref sd_ble_gatts_attr_get.
     */
    static ret_code_t service_changed_cccd(uint16_t conn_handle, uint16_t * p_cccd)
    {
        bool       sc_found = false;
        uint16_t   end_handle;
    
        ret_code_t err_code = sd_ble_gatts_initial_user_handle_get(&end_handle);
        ASSERT(err_code == NRF_SUCCESS);
    
        for (uint16_t handle = 1; handle < end_handle; handle++)
        {
            ble_uuid_t uuid;
            ble_gatts_value_t value = {.p_value = (uint8_t *)&uuid.uuid, .len = 2, .offset = 0};
    
            err_code = sd_ble_gatts_attr_get(handle, &uuid, NULL);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
            else if (!sc_found && (uuid.uuid == BLE_UUID_GATT_CHARACTERISTIC_SERVICE_CHANGED))
            {
                sc_found = true;
            }
            else if (sc_found && (uuid.uuid == BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG))
            {
                value.p_value = (uint8_t *)p_cccd;
                return sd_ble_gatts_value_get(conn_handle, handle, &value);
            }
        }
        return NRF_ERROR_NOT_FOUND;
    }
    
    /**@brief Function for sending a service changed indication in an event context, where no return
     *        code can be given.
     *
     * @details This function will do the procedure, and check the result, set a flag if needed, and
     *          send an event if needed.
     *
     * @param[in]  conn_handle  The connection to perform the procedure on.
     */
    static void service_changed_send_in_evt(uint16_t conn_handle)
    {
        bool sc_pending_state = true;
        bool sc_sent_state = false;
        ret_code_t err_code = gscm_service_changed_ind_send(conn_handle);
    
        switch (err_code)
        {
            case NRF_SUCCESS:
            {
                pm_evt_t event =
                {
                    .evt_id      = PM_EVT_SERVICE_CHANGED_IND_SENT,
                    .conn_handle = conn_handle,
                };
    
                sc_sent_state = true;
    
                evt_send(&event);
                break;
            }
    
            case NRF_ERROR_BUSY:
                // Do nothing.
                break;
    
            case NRF_ERROR_INVALID_STATE:
            {
                uint16_t cccd;
                err_code = service_changed_cccd(conn_handle, &cccd);
                if ((err_code == NRF_SUCCESS) && cccd)
                {
                    // Possible ATT_MTU exchange ongoing.
                    // Do nothing, treat as busy.
                    break;
                }
                else
                {
                    if (err_code != NRF_SUCCESS)
                    {
                        NRF_LOG_DEBUG("Unexpected error when looking for service changed CCCD: %s",
                                      nrf_strerror_get(err_code));
                    }
                    // CCCDs not enabled or an error happened. Drop indication.
                    // Fallthrough.
                }
            }
                // Sometimes fallthrough.
            case NRF_ERROR_NOT_SUPPORTED:
                // Service changed not supported. Drop indication.
                sc_pending_state = false;
                gscm_db_change_notification_done(im_peer_id_get_by_conn_handle(conn_handle));
                break;
    
            case BLE_ERROR_GATTS_SYS_ATTR_MISSING:
                local_db_apply_in_evt(conn_handle);
                break;
    
            case BLE_ERROR_INVALID_CONN_HANDLE:
                // Do nothing.
                break;
    
            default:
                NRF_LOG_ERROR("gscm_service_changed_ind_send() returned %s for conn_handle: %d",
                              nrf_strerror_get(err_code),
                              conn_handle);
                send_unexpected_error(conn_handle, err_code);
                break;
        }
    
        ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_pending, sc_pending_state);
        ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_sent, sc_sent_state);
    }
    #endif
    
    static void apply_pending_handle(uint16_t conn_handle, void * p_context)
    {
        UNUSED_PARAMETER(p_context);
        local_db_apply_in_evt(conn_handle);
    }
    
    
    static __INLINE void apply_pending_flags_check(void)
    {
        UNUSED_RETURN_VALUE(ble_conn_state_for_each_set_user_flag(m_flag_local_db_apply_pending,
                                                                  apply_pending_handle,
                                                                  NULL));
    }
    
    
    static void db_update_pending_handle(uint16_t conn_handle, void * p_context)
    {
        UNUSED_PARAMETER(p_context);
        if (nrf_mtx_trylock(&m_db_update_in_progress_mutex))
        {
            if (local_db_update_in_evt(conn_handle))
            {
                // Successfully started writing to flash.
                return;
            }
            else
            {
                nrf_mtx_unlock(&m_db_update_in_progress_mutex);
            }
        }
    }
    
    
    #if PM_SERVICE_CHANGED_ENABLED
    static void sc_send_pending_handle(uint16_t conn_handle, void * p_context)
    {
        UNUSED_PARAMETER(p_context);
        if (!ble_conn_state_user_flag_get(conn_handle, m_flag_service_changed_sent))
        {
            service_changed_send_in_evt(conn_handle);
        }
    }
    
    
    static __INLINE void service_changed_pending_flags_check(void)
    {
        UNUSED_RETURN_VALUE(ble_conn_state_for_each_set_user_flag(m_flag_service_changed_pending,
                                                                  sc_send_pending_handle,
                                                                  NULL));
    }
    
    
    static void service_changed_needed(uint16_t conn_handle)
    {
        if (gscm_service_changed_ind_needed(conn_handle))
        {
            ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_pending, true);
        }
    }
    #endif
    
    
    static void car_update_pending_handle(uint16_t conn_handle, void * p_context)
    {
        UNUSED_PARAMETER(p_context);
    
        ble_uuid_t car_uuid;
        memset(&car_uuid, 0, sizeof(ble_uuid_t));
        car_uuid.uuid = BLE_UUID_GAP_CHARACTERISTIC_CAR;
        car_uuid.type = BLE_UUID_TYPE_BLE;
    
        ble_gattc_handle_range_t const car_handle_range = {1, 0xFFFF};
    
        ret_code_t err_code = sd_ble_gattc_char_value_by_uuid_read(conn_handle, &car_uuid, &car_handle_range);
        if (err_code == NRF_SUCCESS)
        {
            ble_conn_state_user_flag_set(conn_handle, m_flag_car_handle_queried, true);
        }
    }
    
    
    static void car_update_needed(uint16_t conn_handle)
    {
        pm_peer_data_t peer_data;
        if (pds_peer_data_read(im_peer_id_get_by_conn_handle(conn_handle),
                               PM_PEER_DATA_ID_CENTRAL_ADDR_RES,
                               &peer_data,
                               NULL) == NRF_ERROR_NOT_FOUND)
        {
            ble_conn_state_user_flag_set(conn_handle, m_flag_car_update_pending, true);
        }
    }
    
    
    static __INLINE void update_pending_flags_check(void)
    {
        uint32_t count = ble_conn_state_for_each_set_user_flag(m_flag_local_db_update_pending,
                                                               db_update_pending_handle,
                                                               NULL);
        if (count == 0)
        {
            count = ble_conn_state_for_each_set_user_flag(m_flag_car_update_pending,
                                                          car_update_pending_handle,
                                                          NULL);
            UNUSED_RETURN_VALUE(count);
        }
    }
    
    
    /**@brief Callback function for events from the ID Manager module.
     *        This function is registered in the ID Manager module.
     *
     * @param[in]  p_event  The event from the ID Manager module.
     */
    void gcm_im_evt_handler(pm_evt_t * p_event)
    {
        switch (p_event->evt_id)
        {
            case PM_EVT_BONDED_PEER_CONNECTED:
                local_db_apply_in_evt(p_event->conn_handle);
    #if (PM_SERVICE_CHANGED_ENABLED == 1)
                service_changed_needed(p_event->conn_handle);
    #endif
                car_update_needed(p_event->conn_handle);
                update_pending_flags_check();
                break;
            default:
                break;
        }
    }
    
    
    /**@brief Callback function for events from the Peer Database module.
     *        This handler is extern in Peer Database.
     *
     * @param[in]  p_event  The event from the Security Dispatcher module.
     */
    void gcm_pdb_evt_handler(pm_evt_t * p_event)
    {
        if (   p_event->evt_id == PM_EVT_PEER_DATA_UPDATE_SUCCEEDED
            && p_event->params.peer_data_update_succeeded.action == PM_PEER_DATA_OP_UPDATE)
        {
            switch (p_event->params.peer_data_update_succeeded.data_id)
            {
                case PM_PEER_DATA_ID_BONDING:
                {
                    uint16_t conn_handle = im_conn_handle_get(p_event->peer_id);
    
                    if (conn_handle != BLE_CONN_HANDLE_INVALID)
                    {
                        local_db_update(conn_handle, true);
                        car_update_needed(conn_handle);
                    }
                    break;
                }
    
    #if PM_SERVICE_CHANGED_ENABLED
                case PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING:
                {
                    ret_code_t           err_code;
                    pm_peer_data_flash_t peer_data;
    
                    err_code = pdb_peer_data_ptr_get(p_event->peer_id,
                                                     PM_PEER_DATA_ID_SERVICE_CHANGED_PENDING,
                                                     &peer_data);
    
                    if (err_code == NRF_SUCCESS)
                    {
                        if (*peer_data.p_service_changed_pending)
                        {
                            uint16_t conn_handle = im_conn_handle_get(p_event->peer_id);
                            if (conn_handle != BLE_CONN_HANDLE_INVALID)
                            {
                                ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_pending, true);
                                service_changed_pending_flags_check();
                            }
                        }
                    }
                    break;
                }
    #endif
    
                case PM_PEER_DATA_ID_GATT_LOCAL:
                    if (m_db_update_in_progress_mutex == NRF_MTX_LOCKED)
                    {
                        nrf_mtx_unlock(&m_db_update_in_progress_mutex);
                    }
    
                    // Expecting a call to update_pending_flags_check() immediately.
                    break;
    
                default:
                    /* No action */
                    break;
            }
        }
    
        update_pending_flags_check();
    }
    
    
    ret_code_t gcm_init()
    {
        NRF_PM_DEBUG_CHECK(!m_module_initialized);
    
        internal_state_reset();
    
        m_flag_local_db_update_pending = ble_conn_state_user_flag_acquire();
        m_flag_local_db_apply_pending  = ble_conn_state_user_flag_acquire();
        m_flag_service_changed_pending = ble_conn_state_user_flag_acquire();
        m_flag_service_changed_sent    = ble_conn_state_user_flag_acquire();
        m_flag_car_update_pending      = ble_conn_state_user_flag_acquire();
        m_flag_car_handle_queried      = ble_conn_state_user_flag_acquire();
        m_flag_car_value_queried       = ble_conn_state_user_flag_acquire();
    
        if  ((m_flag_local_db_update_pending  == BLE_CONN_STATE_USER_FLAG_INVALID)
          || (m_flag_local_db_apply_pending   == BLE_CONN_STATE_USER_FLAG_INVALID)
          || (m_flag_service_changed_pending  == BLE_CONN_STATE_USER_FLAG_INVALID)
          || (m_flag_service_changed_sent     == BLE_CONN_STATE_USER_FLAG_INVALID)
          || (m_flag_car_update_pending       == BLE_CONN_STATE_USER_FLAG_INVALID)
          || (m_flag_car_handle_queried       == BLE_CONN_STATE_USER_FLAG_INVALID)
          || (m_flag_car_value_queried        == BLE_CONN_STATE_USER_FLAG_INVALID)
          )
        {
            NRF_LOG_ERROR("Could not acquire conn_state user flags. Increase "\
                          "BLE_CONN_STATE_USER_FLAG_COUNT in the ble_conn_state module.");
            return NRF_ERROR_INTERNAL;
        }
    
        nrf_mtx_init(&m_db_update_in_progress_mutex);
    
        m_module_initialized = true;
    
        return NRF_SUCCESS;
    }
    
    
    void store_car_value(uint16_t conn_handle, bool car_value)
    {
        // Use a uint32_t to enforce 4-byte alignment.
        static const uint32_t car_value_true  = true;
        static const uint32_t car_value_false = false;
    
        pm_peer_data_const_t peer_data =
        {
            .data_id      = PM_PEER_DATA_ID_CENTRAL_ADDR_RES,
            .length_words = 1,
        };
    
        ble_conn_state_user_flag_set(conn_handle, m_flag_car_update_pending, false);
        peer_data.p_central_addr_res = car_value ? &car_value_true : &car_value_false;
        ret_code_t err_code = pds_peer_data_store(im_peer_id_get_by_conn_handle(conn_handle), &peer_data, NULL);
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_WARNING("CAR char value couldn't be stored (error: %s). Reattempt will happen on the next connection.", nrf_strerror_get(err_code));
        }
    }
    
    
    /**@brief Callback function for BLE events from the SoftDevice.
     *
     * @param[in]  p_ble_evt  The BLE event from the SoftDevice.
     */
    void gcm_ble_evt_handler(ble_evt_t const * p_ble_evt)
    {
        uint16_t conn_handle = p_ble_evt->evt.gatts_evt.conn_handle;
    
        switch (p_ble_evt->header.evt_id)
        {
            case BLE_GATTS_EVT_SYS_ATTR_MISSING:
                local_db_apply_in_evt(conn_handle);
                break;
    
    #if PM_SERVICE_CHANGED_ENABLED
            case BLE_GATTS_EVT_SC_CONFIRM:
            {
                pm_evt_t event =
                {
                    .evt_id      = PM_EVT_SERVICE_CHANGED_IND_CONFIRMED,
                    .peer_id     = im_peer_id_get_by_conn_handle(conn_handle),
                    .conn_handle = conn_handle,
                };
    
                gscm_db_change_notification_done(event.peer_id);
    
                ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_sent, false);
                ble_conn_state_user_flag_set(conn_handle, m_flag_service_changed_pending, false);
                evt_send(&event);
                break;
            }
    #endif
    
            case BLE_GATTS_EVT_WRITE:
            {
                /* Skip local db update if new CCCD setting is identical to the previous one */
                if (cccd_written(&p_ble_evt->evt.gatts_evt.params.write) &&
                   (local_db_needs_update(conn_handle))) 
                {
                    local_db_update(conn_handle, true);
                    update_pending_flags_check();
                }
                break;
            }
            case BLE_GATTC_EVT_CHAR_VAL_BY_UUID_READ_RSP:
            {
                bool handle_found = false;
                conn_handle = p_ble_evt->evt.gattc_evt.conn_handle;
                const ble_gattc_evt_char_val_by_uuid_read_rsp_t * p_val = &p_ble_evt->evt.gattc_evt.params.char_val_by_uuid_read_rsp;
    
                if (!ble_conn_state_user_flag_get(conn_handle, m_flag_car_handle_queried))
                {
                    break;
                }
    
                ble_conn_state_user_flag_set(conn_handle, m_flag_car_handle_queried, false);
    
                if (p_ble_evt->evt.gattc_evt.gatt_status == BLE_GATT_STATUS_ATTERR_ATTRIBUTE_NOT_FOUND)
                {
                    // Store 0.
                }
                else if (p_ble_evt->evt.gattc_evt.gatt_status != BLE_GATT_STATUS_SUCCESS)
                {
                    NRF_LOG_WARNING("Unexpected GATT status while getting CAR char value: 0x%x",
                                    p_ble_evt->evt.gattc_evt.gatt_status);
                    // Store 0.
                }
                else
                {
                    if (p_val->count != 1)
                    {
                        NRF_LOG_WARNING("Multiple (%d) CAR characteristics found, using the first.",
                                        p_val->count);
                    }
    
                    if (p_val->value_len != 1)
                    {
                        NRF_LOG_WARNING("Unexpected CAR characteristic value length (%d), store 0.",
                                        p_val->value_len);
                        // Store 0.
                    }
                    else
                    {
                        ret_code_t err_code = sd_ble_gattc_read(conn_handle, *(uint16_t*)p_val->handle_value, 0);
                        if (err_code == NRF_SUCCESS)
                        {
                            handle_found = true;
                            ble_conn_state_user_flag_set(conn_handle, m_flag_car_value_queried, true);
                        }
                    }
                }
    
                if (!handle_found)
                {
                    store_car_value(conn_handle, false);
                }
                break;
            }
    
            case BLE_GATTC_EVT_READ_RSP:
            {
                bool car_value = false;
                conn_handle = p_ble_evt->evt.gattc_evt.conn_handle;
                const ble_gattc_evt_read_rsp_t * p_val = &p_ble_evt->evt.gattc_evt.params.read_rsp;
    
                if (!ble_conn_state_user_flag_get(conn_handle, m_flag_car_value_queried))
                {
                    break;
                }
    
                ble_conn_state_user_flag_set(conn_handle, m_flag_car_value_queried, false);
    
                if (p_ble_evt->evt.gattc_evt.gatt_status != BLE_GATT_STATUS_SUCCESS)
                {
                    NRF_LOG_WARNING("Unexpected GATT status while getting CAR char value: 0x%x",
                                    p_ble_evt->evt.gattc_evt.gatt_status);
                    // Store 0.
                }
                else
                {
                    if (p_val->len != 1)
                    {
                        NRF_LOG_WARNING("Unexpected CAR characteristic value length (%d), store 0.",
                                        p_val->len);
                        // Store 0.
                    }
                    else
                    {
                        car_value = *p_val->data;
                    }
                }
    
                store_car_value(conn_handle, car_value);
            }
        }
    
        apply_pending_flags_check();
    #if PM_SERVICE_CHANGED_ENABLED
        service_changed_pending_flags_check();
    #endif
    }
    
    
    ret_code_t gcm_local_db_cache_update(uint16_t conn_handle)
    {
        NRF_PM_DEBUG_CHECK(m_module_initialized);
    
        local_db_update(conn_handle, true);
        update_pending_flags_check();
    
        return NRF_SUCCESS;
    }
    
    
    #if PM_SERVICE_CHANGED_ENABLED
    void gcm_local_database_has_changed(void)
    {
        gscm_local_database_has_changed();
    
        ble_conn_state_conn_handle_list_t conn_handles = ble_conn_state_conn_handles();
    
        for (uint16_t i = 0; i < conn_handles.len; i++)
        {
            if (im_peer_id_get_by_conn_handle(conn_handles.conn_handles[i]) == PM_PEER_ID_INVALID)
            {
                ble_conn_state_user_flag_set(conn_handles.conn_handles[i], m_flag_service_changed_pending, true);
            }
        }
    
        service_changed_pending_flags_check();
    }
    #endif
    #endif // NRF_MODULE_ENABLED(PEER_MANAGER)
    

    Best regards,

    Vidar

  • Hello Vidar,

    Thank you for this patch, it works well. 


    However I had to remove the ASSERT check because at the first connection on iOS, the ATT MTU negociation returns NRF_ERROR_NOT_SUPPORTED.

    [00000003] <debug> nrf_ble_gatt: Requesting to update ATT MTU to 247 bytes on connection 0x0.
    [00000003] <info> BLEDriver: Connected.
    [00000003] <info> SecurityService: Not bonded.
    [00000003] <debug> nrf_ble_gatt: ATT MTU updated to 185 bytes on connection 0x0 (response).
    [00000004] <error> app: ASSERTION FAILED at ../nordic_sdk/nRF5_SDK/components/ble/peer_manager/gatt_cache_manager.c:315

    It could be my configuration, but because since there was no ASSERT before it was not detected. 

    Anyway thank you very much for the great support.

    Florian

  • Hello Florian,

    Thanks for reporting back. I assume you meant to say it returned NRF_ERROR_NOT_FOUND? The initial "local database" record was always written prior to the client CCCD write in my tests, but it seems now like that might not always be the case.

    Instead of removing the ASSERT line, you may consider implementing a check for this error as shown below.

    --- a/gatt_cache_manager.c
    +++ b/gatt_cache_manager.c
    @@ -302,6 +302,12 @@ static bool local_db_needs_update(uint16_t conn_handle)
         err_code = pdb_peer_data_ptr_get(im_peer_id_get_by_conn_handle(conn_handle),
                                       PM_PEER_DATA_ID_GATT_LOCAL,
                                       &peer_data);
    +    
    +    if (err_code == NRF_ERROR_NOT_FOUND)
    +    {
    +        return needs_update;
    +    }
    +
         ASSERT(err_code == NRF_SUCCESS);
     
         len = peer_data.p_local_gatt_db->len;
    

    Edit: Added missing parenthesis. Thanks

  • I got an NRF_ERROR_INVALID_PARAM. 

    It seems to happen when the NRF local peer data are deleted but not on iOS side. At the next connection, the phone tries to secure, fail but try to update the MTU. 

    I tried to trace the operation but i did not manage to find where the write event comes from.

    Here some log : 

    [00000043] <info> BLEDriver: Connected.
    [00000043] <info> SecurityService: Not bonded.
    [00000043] <info> BLEDriver: Encryption started.
    [00000043] <info> peer_manager_handler: Connection security failed: role: Peripheral, conn_handle: 0x0, procedure: Encryption, error: 4102
    [00000043] <info> BLEDriver: Failed to secure connection!
    [00000043] <info> SecurityService: Securing failed.
    [00000043] <debug> BLEDriver: handleBLEEvent: evt_id = 0x14 : BLE_GAP_EVT_SEC_INFO_REQUEST
    [00000043] <info> app: ATT MTU updated to 185 bytes on connection 0x0 (response).
    [00000043] <debug> BLEDriver: handleBLEEvent: evt_id = 0x3A : BLE_GATTC_EVT_EXCHANGE_MTU_RSP
    [00000043] <info> in local_db_needs_update -> error_id = 0x07 : NRF_ERROR_INVALID_PARAM , at pdb_peer_data_ptr_get
    [00000048] <error> app: SOFTDEVICE: ASSERTION FAILED

    It is probably my application or the phone that try to set some value even if it is not authorized. 

    I added a condition to handle this fault to avoid triggering the ASSERT : 

    --- a/gatt_cache_manager.c
    +++ b/gatt_cache_manager.c
    
    static bool local_db_needs_update(uint16_t conn_handle)
         err_code = pdb_peer_data_ptr_get(im_peer_id_get_by_conn_handle(conn_handle),
                                       PM_PEER_DATA_ID_GATT_LOCAL,
                                       &peer_data);
         
         if (err_code == NRF_ERROR_NOT_FOUND)
         {
             return needs_update;
         }
         
         if (err_code == NRF_ERROR_INVALID_PARAM)
         {
             return false;
         }
         
         ASSERT(err_code == NRF_SUCCESS);
     
         len = peer_data.p_local_gatt_db->len;
    

Related