text: /**
   * 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 <string.h>
  #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
  