/** * Copyright (c) 2015 - 2018, 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. * */ #ifdef COMMISSIONING_ENABLED #include #include "ble_ncfgs.h" #include "app_error.h" #include "ble.h" #include "nordic_common.h" /**@brief NCFGS database encapsulation. */ typedef struct { uint16_t service_handle; ble_gatts_char_handles_t ssid_handles; ble_gatts_char_handles_t keys_store_handles; ble_gatts_char_handles_t ctrlp_handles; } ble_database_t; static ble_ncfgs_state_t m_service_state = NCFGS_STATE_IDLE; /**< Module state value. */ static ble_ncfgs_evt_handler_t m_app_evt_handler; /**< Parent module callback function. */ static ble_database_t m_database; /**< GATT handles database. */ static uint8_t m_ctrlp_value_buffer[NCFGS_CTRLP_VALUE_LEN]; /**< Stores received Control Point value before parsing. */ static ble_ncfgs_data_t m_ncfgs_data; /**< Stores all values written by the peer device. */ #if NCFGS_CONFIG_LOG_ENABLED #define NRF_LOG_MODULE_NAME ble_ncfgs #define NRF_LOG_LEVEL NCFGS_CONFIG_LOG_LEVEL #define NRF_LOG_INFO_COLOR NCFGS_CONFIG_INFO_COLOR #define NRF_LOG_DEBUG_COLOR NCFGS_CONFIG_DEBUG_COLOR #include "nrf_log.h" NRF_LOG_MODULE_REGISTER(); #define NCFGS_TRC NRF_LOG_DEBUG /**< Used for getting trace of execution in the module. */ #define NCFGS_ERR NRF_LOG_ERROR /**< Used for logging errors in the module. */ #define NCFGS_DUMP NRF_LOG_HEXDUMP_DEBUG /**< Used for dumping octet information to get details of bond information etc. */ #define NCFGS_ENTRY() NCFGS_TRC(">> %s", __func__) #define NCFGS_EXIT() NCFGS_TRC("<< %s", __func__) #else // NCFGS_CONFIG_LOG_ENABLED #define NCFGS_TRC(...) /**< Disables traces. */ #define NCFGS_DUMP(...) /**< Disables dumping of octet streams. */ #define NCFGS_ERR(...) /**< Disables error logs. */ #define NCFGS_ENTRY(...) #define NCFGS_EXIT(...) #endif // NCFGS_CONFIG_LOG_ENABLED /**@brief Function for adding the SSID Store characteristic. * * @return NRF_SUCCESS on success, otherwise an error code. */ static uint32_t add_ssid_characteristic(ble_uuid_t * p_srv_uuid) { ble_gatts_char_md_t char_md; ble_gatts_attr_t attr_char_value; ble_uuid_t char_uuid; ble_gatts_attr_md_t attr_md; memset(&char_md, 0x00, sizeof(char_md)); char_md.char_props.read = 1; char_md.char_props.write = 1; memset(&attr_md, 0x00, sizeof(attr_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); attr_md.wr_auth = 1; attr_md.vloc = BLE_GATTS_VLOC_USER; memset(&attr_char_value, 0x00, sizeof(attr_char_value)); char_uuid.type = p_srv_uuid->type; char_uuid.uuid = BLE_UUID_NCFGS_SSID_CHAR; attr_char_value.p_uuid = &char_uuid; attr_char_value.p_attr_md = &attr_md; attr_char_value.init_len = NCFGS_SSID_MAX_LEN; attr_char_value.init_offs = 0; attr_char_value.max_len = NCFGS_SSID_MAX_LEN; attr_char_value.p_value = &m_ncfgs_data.ssid_from_router.ssid[0]; return sd_ble_gatts_characteristic_add(m_database.service_handle, &char_md, &attr_char_value, &m_database.ssid_handles); } /**@brief Function for adding the Keys Store characteristic. * * @return NRF_SUCCESS on success, otherwise an error code. */ static uint32_t add_keys_store_characteristic(ble_uuid_t * p_srv_uuid) { ble_gatts_char_md_t char_md; ble_gatts_attr_t attr_char_value; ble_uuid_t char_uuid; ble_gatts_attr_md_t attr_md; memset(&char_md, 0x00, sizeof(char_md)); char_md.char_props.read = 1; char_md.char_props.write = 1; memset(&attr_md, 0x00, sizeof(attr_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); attr_md.wr_auth = 1; attr_md.vloc = BLE_GATTS_VLOC_USER; memset(&attr_char_value, 0x00, sizeof(attr_char_value)); char_uuid.type = p_srv_uuid->type; char_uuid.uuid = BLE_UUID_NCFGS_KEYS_STORE_CHAR; attr_char_value.p_uuid = &char_uuid; attr_char_value.p_attr_md = &attr_md; attr_char_value.init_len = NCFGS_KEYS_MAX_LEN; attr_char_value.init_offs = 0; attr_char_value.max_len = NCFGS_KEYS_MAX_LEN; attr_char_value.p_value = &m_ncfgs_data.keys_from_router.keys[0]; return sd_ble_gatts_characteristic_add(m_database.service_handle, &char_md, &attr_char_value, &m_database.keys_store_handles); } /**@brief Function for adding the Control Point characteristic. * * @return NRF_SUCCESS on success, otherwise an error code. */ static uint32_t add_ip_cfg_cp_characteristic(ble_uuid_t * p_srv_uuid) { ble_gatts_char_md_t char_md; ble_gatts_attr_t attr_char_value; ble_uuid_t char_uuid; ble_gatts_attr_md_t attr_md; memset(&char_md, 0x00, sizeof(char_md)); char_md.char_props.read = 1; char_md.char_props.write = 1; memset(&attr_md, 0x00, sizeof(attr_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); attr_md.wr_auth = 1; attr_md.vloc = BLE_GATTS_VLOC_USER; memset(&attr_char_value, 0x00, sizeof(attr_char_value)); char_uuid.type = p_srv_uuid->type; char_uuid.uuid = BLE_UUID_NCFGS_CTRLPT_CHAR; attr_char_value.p_uuid = &char_uuid; attr_char_value.p_attr_md = &attr_md; attr_char_value.init_len = NCFGS_CTRLP_VALUE_LEN; attr_char_value.init_offs = 0; attr_char_value.max_len = NCFGS_CTRLP_VALUE_LEN; attr_char_value.p_value = &m_ctrlp_value_buffer[0]; return sd_ble_gatts_characteristic_add(m_database.service_handle, &char_md, &attr_char_value, &m_database.ctrlp_handles); } /**@brief Function for creating the GATT database. * * @return NRF_SUCCESS on success, otherwise an error code. */ static uint32_t ble_ncfgs_create_database(void) { uint32_t err_code = NRF_SUCCESS; // Add service. ble_uuid_t service_uuid; const ble_uuid128_t base_uuid128 = { { 0x73, 0x3E, 0x2D, 0x02, 0xB7, 0x6B, 0xBE, 0xBE, \ 0xE5, 0x4F, 0x40, 0x8F, 0x00, 0x00, 0x20, 0x54 } }; service_uuid.uuid = BLE_UUID_NODE_CFG_SERVICE; err_code = sd_ble_uuid_vs_add(&base_uuid128, &(service_uuid.type)); if (err_code != NRF_SUCCESS) { return err_code; } err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, \ &service_uuid, \ &m_database.service_handle); if (err_code != NRF_SUCCESS) { return err_code; } err_code = add_ssid_characteristic(&service_uuid); if (err_code != NRF_SUCCESS) { return err_code; } err_code = add_keys_store_characteristic(&service_uuid); if (err_code != NRF_SUCCESS) { return err_code; } err_code = add_ip_cfg_cp_characteristic(&service_uuid); if (err_code != NRF_SUCCESS) { return err_code; } return err_code; } uint32_t ble_ncfgs_init(ble_ncfgs_evt_handler_t ble_ncfgs_cb) { NCFGS_ENTRY(); uint32_t err_code; memset(&m_ncfgs_data, 0x00, sizeof(m_ncfgs_data)); m_app_evt_handler = ble_ncfgs_cb; err_code = ble_ncfgs_create_database(); NCFGS_EXIT(); return err_code; } /**@brief Function for decoding the Control Point characteristic value. * * @return NRF_SUCCESS on success, otherwise an error code. */ static uint32_t ctrlp_value_decode(const ble_evt_t * p_ble_evt) { uint16_t wr_req_value_len = \ p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.len; memcpy(m_ctrlp_value_buffer, \ p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.data, \ wr_req_value_len); m_ncfgs_data.ctrlp_value.opcode = \ (ble_ncfgs_opcode_t)m_ctrlp_value_buffer[0]; memcpy((void *)&m_ncfgs_data.ctrlp_value.delay_sec, \ &m_ctrlp_value_buffer[NCFGS_CTRLP_OPCODE_LEN], \ sizeof(uint32_t)); m_ncfgs_data.ctrlp_value.delay_sec = \ HTONL(m_ncfgs_data.ctrlp_value.delay_sec); memcpy((void *)&m_ncfgs_data.ctrlp_value.duration_sec, \ &m_ctrlp_value_buffer[NCFGS_CTRLP_OPCODE_LEN+NCFGS_CTRLP_DELAY_LEN], \ sizeof(uint32_t)); m_ncfgs_data.ctrlp_value.duration_sec = \ HTONL(m_ncfgs_data.ctrlp_value.duration_sec); m_ncfgs_data.ctrlp_value.state_on_failure = \ (state_on_failure_t)m_ctrlp_value_buffer[NCFGS_CTRLP_OPCODE_LEN+ \ NCFGS_CTRLP_DELAY_LEN+ \ NCFGS_CTRLP_DURATION_LEN]; if ((m_ncfgs_data.ctrlp_value.state_on_failure != NCFGS_SOF_NO_CHANGE) && \ (m_ncfgs_data.ctrlp_value.state_on_failure != NCFGS_SOF_PWR_OFF) && \ (m_ncfgs_data.ctrlp_value.state_on_failure != NCFGS_SOF_CONFIG_MODE)) { return NRF_ERROR_INVALID_DATA; } uint16_t id_data_len = wr_req_value_len - NCFGS_CTRLP_ALL_BUT_ID_DATA_LEN; if (id_data_len != 0) { m_ncfgs_data.id_data.identity_data_len = id_data_len; memcpy(m_ncfgs_data.id_data.identity_data, \ &m_ctrlp_value_buffer[NCFGS_CTRLP_ALL_BUT_ID_DATA_LEN], \ id_data_len); } return NRF_SUCCESS; } void ble_ncfgs_ble_evt_handler(const ble_evt_t * p_ble_evt) { switch (p_ble_evt->header.evt_id) { case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST: { if (p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.op == \ BLE_GATTS_OP_WRITE_REQ) { uint16_t wr_req_handle = \ p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.handle; uint16_t wr_req_value_len = \ p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.len; ble_gatts_rw_authorize_reply_params_t reply_params; memset(&reply_params, 0x00, sizeof(reply_params)); reply_params.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE; reply_params.params.write.update = 1; reply_params.params.write.offset = p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.offset; reply_params.params.write.len = p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.len; reply_params.params.write.p_data = p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.data; if (wr_req_handle == m_database.ssid_handles.value_handle) { NCFGS_TRC("> wr_req: ssid_handle"); if ((wr_req_value_len > NCFGS_SSID_MAX_LEN) || \ (wr_req_value_len < NCFGS_SSID_MIN_LEN)) { reply_params.params.write.gatt_status = \ BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH; } else { m_ncfgs_data.ssid_from_router.ssid_len = wr_req_value_len; m_service_state |= NCFGS_STATE_SSID_WRITTEN; reply_params.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; } UNUSED_RETURN_VALUE( \ sd_ble_gatts_rw_authorize_reply(p_ble_evt->evt.gap_evt.conn_handle, \ &reply_params)); NCFGS_TRC("< wr_req: ssid_handle"); return; } else if (wr_req_handle == m_database.keys_store_handles.value_handle) { NCFGS_TRC("> wr_req: keys_store_handle"); if (wr_req_value_len > NCFGS_KEYS_MAX_LEN) { reply_params.params.write.gatt_status = \ BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH; } else { m_ncfgs_data.keys_from_router.keys_len = wr_req_value_len; m_service_state |= NCFGS_STATE_KEYS_STORE_WRITTEN; reply_params.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; } UNUSED_RETURN_VALUE( \ sd_ble_gatts_rw_authorize_reply(p_ble_evt->evt.gap_evt.conn_handle, \ &reply_params)); NCFGS_TRC("< wr_req: keys_store_handle"); return; } else if (wr_req_handle == m_database.ctrlp_handles.value_handle) { NCFGS_TRC("> wr_req: ctrlp_handle"); bool notify_app = false; if ((wr_req_value_len > NCFGS_CTRLP_VALUE_LEN) || \ (wr_req_value_len < NCFGS_CTRLP_ALL_BUT_ID_DATA_LEN)) { reply_params.params.write.gatt_status = \ BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH; } else { ble_ncfgs_opcode_t opcode_in = (ble_ncfgs_opcode_t) \ p_ble_evt->evt.gatts_evt.params.authorize_request.request.write.data[0]; reply_params.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; if ((opcode_in != NCFGS_OPCODE_GOTO_JOINING_MODE) && \ (opcode_in != NCFGS_OPCODE_GOTO_CONFIG_MODE) && \ (opcode_in != NCFGS_OPCODE_GOTO_IDENTITY_MODE)) { reply_params.params.write.gatt_status = APP_GATTERR_UNKNOWN_OPCODE; } if (opcode_in == NCFGS_OPCODE_GOTO_JOINING_MODE) { if (!((m_service_state & NCFGS_STATE_SSID_WRITTEN) && \ (m_service_state & NCFGS_STATE_KEYS_STORE_WRITTEN))) { reply_params.params.write.gatt_status = APP_GATTERR_NOT_CONFIGURED; } } if (reply_params.params.write.gatt_status == BLE_GATT_STATUS_SUCCESS) { uint32_t err_code = ctrlp_value_decode(p_ble_evt); if (err_code != NRF_SUCCESS) { reply_params.params.write.gatt_status = \ APP_GATTERR_INVALID_ATTR_VALUE; } else { notify_app = true; } } } UNUSED_RETURN_VALUE(sd_ble_gatts_rw_authorize_reply( p_ble_evt->evt.gap_evt.conn_handle, &reply_params)); if (notify_app == true) { NCFGS_TRC("> do notify parent"); m_app_evt_handler(&m_ncfgs_data); NCFGS_TRC("< do notify parent"); } NCFGS_TRC("< wr_req: ctrlp_handle"); } else { // Invalid handle. reply_params.params.write.gatt_status = BLE_GATT_STATUS_ATTERR_INVALID_HANDLE; UNUSED_RETURN_VALUE(sd_ble_gatts_rw_authorize_reply( p_ble_evt->evt.gap_evt.conn_handle, &reply_params)); } } break; } case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST: { ble_gap_data_length_params_t dl_params; // Clearing the struct will effectively set members to @ref BLE_GAP_DATA_LENGTH_AUTO. memset(&dl_params, 0, sizeof(ble_gap_data_length_params_t)); UNUSED_RETURN_VALUE(sd_ble_gap_data_length_update(p_ble_evt->evt.gap_evt.conn_handle, &dl_params, NULL)); break; } case BLE_GAP_EVT_PHY_UPDATE_REQUEST: { NCFGS_TRC("> PHY update request."); ble_gap_phys_t const phys = { .rx_phys = BLE_GAP_PHY_AUTO, .tx_phys = BLE_GAP_PHY_AUTO, }; UNUSED_RETURN_VALUE(sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys); NCFGS_TRC("< PHY update request."); break; } default: { break; } } } #endif // COMMISSIONING_ENABLED