/* * Copyright (c) 2015 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 or object form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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 "gatt_cache_manager.h" #include "nrf_ble_gap.h" #include "ble_conn_state.h" #include "peer_manager_types.h" #include "peer_manager_internal.h" #include "peer_database.h" #include "id_manager.h" #include "security_dispatcher.h" #include "gatts_cache_manager.h" #include "gattc_cache_manager.h" #include "sdk_common.h" /**@brief Structure containing the module variable(s) of the GCM module. */ typedef struct { gcm_evt_handler_t evt_handler; /**< The event handler to use for outbound GSCM events. */ ble_conn_state_user_flag_id_t flag_id_local_db_update_pending; /**< Flag ID for flag collection to keep track of which connections need a local DB update procedure. */ ble_conn_state_user_flag_id_t flag_id_local_db_apply_pending; /**< Flag ID for flag collection to keep track of which connections need a local DB apply procedure. */ ble_conn_state_user_flag_id_t flag_id_service_changed_pending; /**< Flag ID for flag collection to keep track of which connections need to be sent a service changed indication. */ ble_conn_state_user_flag_id_t flag_id_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. */ } gcm_t; static gcm_t m_gcm; /**< Instantiation of module variable(s). */ #define MODULE_INITIALIZED (m_gcm.evt_handler != NULL) #include "sdk_macros.h" static void service_changed_pending_flags_check(void); /**@brief Function for resetting the module variable(s) of the GSCM module. * * @param[out] The instance to reset. */ static void internal_state_reset(gcm_t * p_gcm) { memset(p_gcm, 0, sizeof(gcm_t)); } /**@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 * 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 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; gcm_evt_t event; 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 = GCM_EVT_LOCAL_DB_CACHE_APPLIED; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); event.params.local_db_cache_applied.conn_handle = conn_handle; m_gcm.evt_handler(&event); break; case NRF_ERROR_BUSY: set_procedure_as_pending = true; break; case NRF_ERROR_INVALID_DATA: event.evt_id = GCM_EVT_ERROR_LOCAL_DB_CACHE_APPLY; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); event.params.error_local_db_cache_apply.conn_handle = conn_handle; m_gcm.evt_handler(&event); break; case BLE_ERROR_INVALID_CONN_HANDLE: /* Do nothing */ break; default: event.evt_id = GCM_EVT_ERROR_UNEXPECTED; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); event.params.error_unexpected.conn_handle = conn_handle; event.params.error_unexpected.error = err_code; m_gcm.evt_handler(&event); break; } ble_conn_state_user_flag_set(conn_handle, m_gcm.flag_id_local_db_apply_pending, set_procedure_as_pending); } /**@brief Function for performing the local DB apply 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_update_in_evt(uint16_t conn_handle) { gcm_evt_t event; bool set_procedure_as_pending = false; ret_code_t err_code = gscm_local_db_cache_update(conn_handle); switch(err_code) { case NRF_SUCCESS: event.evt_id = GCM_EVT_LOCAL_DB_CACHE_UPDATED; event.params.local_db_cache_applied.conn_handle = conn_handle; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); m_gcm.evt_handler(&event); break; case BLE_ERROR_INVALID_CONN_HANDLE: /* Do nothing */ break; case NRF_ERROR_BUSY: set_procedure_as_pending = true; break; case NRF_ERROR_DATA_SIZE: event.evt_id = GCM_EVT_ERROR_DATA_SIZE; event.params.error_data_size.conn_handle = conn_handle; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); m_gcm.evt_handler(&event); break; case NRF_ERROR_NO_MEM: event.evt_id = GCM_EVT_ERROR_STORAGE_FULL; event.params.error_no_mem.conn_handle = conn_handle; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); m_gcm.evt_handler(&event); break; default: event.evt_id = GCM_EVT_ERROR_UNEXPECTED; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); event.params.error_unexpected.conn_handle = conn_handle; event.params.error_unexpected.error = err_code; m_gcm.evt_handler(&event); break; } ble_conn_state_user_flag_set(conn_handle, m_gcm.flag_id_local_db_update_pending, set_procedure_as_pending); } /**@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) { gcm_evt_t event; 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: sc_sent_state = true; event.evt_id = GCM_EVT_SERVICE_CHANGED_IND_SENT; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); event.params.service_changed_ind_sent.conn_handle = conn_handle; m_gcm.evt_handler(&event); break; case NRF_ERROR_BUSY: // Do nothing. break; case NRF_ERROR_INVALID_STATE: // CCCDs not enabled. Drop indication. // 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: event.evt_id = GCM_EVT_ERROR_UNEXPECTED; event.params.error_unexpected.conn_handle = conn_handle; event.params.error_unexpected.error = err_code; event.peer_id = im_peer_id_get_by_conn_handle(conn_handle); m_gcm.evt_handler(&event); break; } ble_conn_state_user_flag_set(conn_handle, m_gcm.flag_id_service_changed_pending, sc_pending_state); ble_conn_state_user_flag_set(conn_handle, m_gcm.flag_id_service_changed_sent, sc_sent_state); } /**@brief Callback function for events from the GATT Cache Server Manager module. * * @param[in] p_event The event from the GATT Cache Server Manager module. */ static void gscm_evt_handler(gscm_evt_t const * p_event) { gcm_evt_t event; event.peer_id = p_event->peer_id; switch (p_event->evt_id) { case GSCM_EVT_LOCAL_DB_CACHE_STORED: event.evt_id = GCM_EVT_LOCAL_DB_CACHE_STORED; m_gcm.evt_handler(&event); local_db_apply_in_evt(im_conn_handle_get(p_event->peer_id)); break; case GSCM_EVT_LOCAL_DB_CACHE_UPDATED: event.evt_id = GCM_EVT_LOCAL_DB_CACHE_UPDATED; event.params.local_db_cache_updated.conn_handle = p_event->params.local_db_cache_updated.conn_handle; m_gcm.evt_handler(&event); break; case GSCM_EVT_SC_STATE_STORED: if (p_event->params.sc_state_stored.state) { 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_gcm.flag_id_service_changed_pending, true); service_changed_pending_flags_check(); } } break; } } /**@brief Callback function for events from the GATT Cache Client Manager module. * * @param[in] p_event The event from the GATT Cache Client Manager module. */ static void gccm_evt_handler(gccm_evt_t const * p_event) { } /**@brief Callback function for events from the Identity Manager module. * * @param[in] p_event The event from the Identity Manager module. */ static void im_evt_handler(im_evt_t const * p_event) { switch (p_event->evt_id) { case IM_EVT_BONDED_PEER_CONNECTED: local_db_apply_in_evt(p_event->conn_handle); if (gscm_service_changed_ind_needed(p_event->conn_handle)) { ble_conn_state_user_flag_set(p_event->conn_handle, m_gcm.flag_id_service_changed_pending, true); } break; default: break; } } /**@brief Callback function for events from the Security Dispatcher module. * * @param[in] p_event The event from the Security Dispatcher module. */ static void smd_evt_handler(smd_evt_t const * p_event) { switch (p_event->evt_id) { case SMD_EVT_BONDING_INFO_STORED: local_db_update_in_evt(p_event->conn_handle); break; default: break; } } ret_code_t gcm_init(gcm_evt_handler_t evt_handler) { VERIFY_PARAM_NOT_NULL(evt_handler); ret_code_t err_code; err_code = gscm_init(gscm_evt_handler); VERIFY_SUCCESS(err_code); err_code = gccm_init(gccm_evt_handler); VERIFY_SUCCESS(err_code); internal_state_reset(&m_gcm); m_gcm.evt_handler = evt_handler; err_code = im_register(im_evt_handler); VERIFY_SUCCESS(err_code); err_code = smd_register(smd_evt_handler); VERIFY_SUCCESS(err_code); m_gcm.flag_id_local_db_update_pending = ble_conn_state_user_flag_acquire(); m_gcm.flag_id_local_db_apply_pending = ble_conn_state_user_flag_acquire(); m_gcm.flag_id_service_changed_pending = ble_conn_state_user_flag_acquire(); m_gcm.flag_id_service_changed_sent = ble_conn_state_user_flag_acquire(); if ((m_gcm.flag_id_local_db_update_pending == BLE_CONN_STATE_USER_FLAG_INVALID) || (m_gcm.flag_id_local_db_apply_pending == BLE_CONN_STATE_USER_FLAG_INVALID) || (m_gcm.flag_id_service_changed_pending == BLE_CONN_STATE_USER_FLAG_INVALID) || (m_gcm.flag_id_service_changed_sent == BLE_CONN_STATE_USER_FLAG_INVALID)) { err_code = NRF_ERROR_INTERNAL; } return err_code; } /**@brief Function for performing the Local DB apply procedure if it is pending on any connections. */ static void apply_pending_flags_check(void) { sdk_mapped_flags_t apply_pending_flags; apply_pending_flags = ble_conn_state_user_flag_collection(m_gcm.flag_id_local_db_apply_pending); if (sdk_mapped_flags_any_set(apply_pending_flags)) { sdk_mapped_flags_key_list_t conn_handle_list; conn_handle_list = ble_conn_state_conn_handles(); for (uint32_t i = 0; i < conn_handle_list.len; i++) { if (ble_conn_state_user_flag_get(conn_handle_list.flag_keys[i], m_gcm.flag_id_local_db_apply_pending)) { local_db_apply_in_evt(conn_handle_list.flag_keys[i]); } } } } /**@brief Function for performing the Local DB update procedure if it is pending on any connections. */ static void update_pending_flags_check(void) { sdk_mapped_flags_t update_pending_flags; update_pending_flags = ble_conn_state_user_flag_collection(m_gcm.flag_id_local_db_update_pending); if (sdk_mapped_flags_any_set(update_pending_flags)) { sdk_mapped_flags_key_list_t conn_handle_list; conn_handle_list = ble_conn_state_conn_handles(); for (uint32_t i = 0; i < conn_handle_list.len; i++) { if (ble_conn_state_user_flag_get(conn_handle_list.flag_keys[i], m_gcm.flag_id_local_db_update_pending)) { local_db_update_in_evt(conn_handle_list.flag_keys[i]); } } } } /**@brief Function for sending service changed indications if it is pending on any connections. */ static void service_changed_pending_flags_check(void) { sdk_mapped_flags_t service_changed_pending_flags; service_changed_pending_flags = ble_conn_state_user_flag_collection(m_gcm.flag_id_service_changed_pending); if (sdk_mapped_flags_any_set(service_changed_pending_flags)) { sdk_mapped_flags_key_list_t conn_handle_list; conn_handle_list = ble_conn_state_conn_handles(); for (uint32_t i = 0; i < conn_handle_list.len; i++) { if ( ble_conn_state_user_flag_get(conn_handle_list.flag_keys[i], m_gcm.flag_id_service_changed_pending) && !ble_conn_state_user_flag_get(conn_handle_list.flag_keys[i], m_gcm.flag_id_service_changed_sent)) { service_changed_send_in_evt(conn_handle_list.flag_keys[i]); } } } } /**@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 * p_ble_evt) { gcm_evt_t event; switch(p_ble_evt->header.evt_id) { case BLE_GATTS_EVT_SYS_ATTR_MISSING: local_db_apply_in_evt(p_ble_evt->evt.gatts_evt.conn_handle); break; case BLE_GATTS_EVT_SC_CONFIRM: event.evt_id = GCM_EVT_SERVICE_CHANGED_IND_CONFIRMED; event.peer_id = im_peer_id_get_by_conn_handle(p_ble_evt->evt.gatts_evt.conn_handle); event.params.service_changed_ind_sent.conn_handle = p_ble_evt->evt.gatts_evt.conn_handle; gscm_db_change_notification_done(event.peer_id); ble_conn_state_user_flag_set(p_ble_evt->evt.gatts_evt.conn_handle, m_gcm.flag_id_service_changed_pending, false); m_gcm.evt_handler(&event); break; case BLE_GATTS_EVT_WRITE: if (cccd_written(&p_ble_evt->evt.gatts_evt.params.write)) { local_db_update_in_evt(p_ble_evt->evt.gatts_evt.conn_handle); } break; } apply_pending_flags_check(); update_pending_flags_check(); service_changed_pending_flags_check(); } ret_code_t gcm_remote_db_store(pm_peer_id_t peer_id, ble_gatt_db_srv_t * p_remote_db, uint32_t n_services) { VERIFY_MODULE_INITIALIZED(); return gccm_remote_db_store(peer_id, p_remote_db, n_services); } ret_code_t gcm_remote_db_retrieve(pm_peer_id_t peer_id, ble_gatt_db_srv_t * p_remote_db, uint32_t * p_n_services) { VERIFY_MODULE_INITIALIZED(); VERIFY_PARAM_NOT_NULL(p_remote_db); return gccm_remote_db_retrieve(peer_id, p_remote_db, p_n_services); } ret_code_t gcm_local_db_cache_update(uint16_t conn_handle) { VERIFY_MODULE_INITIALIZED(); ret_code_t err_code = gscm_local_db_cache_update(conn_handle); bool set_procedure_as_pending = false; if (err_code == NRF_ERROR_BUSY) { set_procedure_as_pending = true; err_code = NRF_SUCCESS; } ble_conn_state_user_flag_set(conn_handle, m_gcm.flag_id_local_db_update_pending, set_procedure_as_pending); return err_code; } ret_code_t gcm_local_db_cache_set(pm_peer_id_t peer_id, pm_peer_data_local_gatt_db_t * p_local_db) { VERIFY_MODULE_INITIALIZED(); return gscm_local_db_cache_set(peer_id, p_local_db); } ret_code_t gcm_local_db_cache_get(pm_peer_id_t peer_id, pm_peer_data_local_gatt_db_t * p_local_db) { VERIFY_MODULE_INITIALIZED(); return gscm_local_db_cache_get(peer_id, p_local_db); } void gcm_local_database_has_changed(void) { gscm_local_database_has_changed(); sdk_mapped_flags_key_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.flag_keys[i]) == PM_PEER_ID_INVALID) { ble_conn_state_user_flag_set(conn_handles.flag_keys[i], m_gcm.flag_id_service_changed_pending, true); } } service_changed_pending_flags_check(); }