/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

/* Attention! 
*  To maintain compliance with Nordic Semiconductor ASA’s Bluetooth profile 
*  qualification listings, this section of source code must not be modified.
*/

#include "ble_hids.h"
#include <string.h>
#include "app_error.h"
#include "nordic_common.h"
#include "ble_srv_common.h"
#include "app_util.h"


// Protocol Mode values
#define PROTOCOL_MODE_BOOT               0x00                        /**< Boot Protocol Mode. */
#define PROTOCOL_MODE_REPORT             0x01                        /**< Report Protocol Mode. */

// HID Control Point values
#define HIDS_CONTROL_POINT_SUSPEND       0                           /**< Suspend command. */
#define HIDS_CONTROL_POINT_EXIT_SUSPEND  1                           /**< Exit Suspend command. */

#define DEFAULT_PROTOCOL_MODE            PROTOCOL_MODE_REPORT        /**< Default value for the Protocol Mode characteristic. */
#define INITIAL_VALUE_HID_CONTROL_POINT  HIDS_CONTROL_POINT_SUSPEND  /**< Initial value for the HID Control Point characteristic. */

#define ENCODED_HID_INFORMATION_LEN      4                           /**< Maximum size of an encoded HID Information characteristic. */

#define BOOT_KB_INPUT_REPORT_MAX_SIZE    8                           /**< Maximum size of a Boot Keyboard Input Report (as per Appendix B in Device Class Definition for Human Interface Devices (HID), Version 1.11). */
#define BOOT_KB_OUTPUT_REPORT_MAX_SIZE   1                           /**< Maximum size of a Boot Keyboard Output Report (as per Appendix B in Device Class Definition for Human Interface Devices (HID), Version 1.11). */
#define BOOT_MOUSE_INPUT_REPORT_MIN_SIZE 3                           /**< Minimum size of a Boot Mouse Input Report (as per Appendix B in Device Class Definition for Human Interface Devices (HID), Version 1.11). */
#define BOOT_MOUSE_INPUT_REPORT_MAX_SIZE 8                           /**< Maximum size of a Boot Mouse Input Report (as per Appendix B in Device Class Definition for Human Interface Devices (HID), Version 1.11). */


/**@brief Function for making a HID Service characteristic id.
 *
 * @param[in]   uuid        UUID of characteristic.
 * @param[in]   rep_type    Type of report.
 * @param[in]   rep_index   Index of the characteristic.
 *
 * @return      HID Service characteristic id structure.
 */
static ble_hids_char_id_t make_char_id(uint16_t uuid, uint8_t rep_type, uint8_t rep_index)
{
    ble_hids_char_id_t char_id;
    
    char_id.uuid      = uuid;
    char_id.rep_type  = rep_type;
    char_id.rep_index = rep_index;
    
    return char_id;
}


/**@brief Function for handling the Connect event.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_ble_evt   Event received from the BLE stack.
 */
static void on_connect(ble_hids_t * p_hids, ble_evt_t * p_ble_evt)
{
    uint32_t err_code;
    uint16_t len;
    uint8_t  default_protocol_mode;
    
    p_hids->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;

    // Set Protocol Mode characteristic value to default value
    len                   = sizeof(uint8_t);
    default_protocol_mode = DEFAULT_PROTOCOL_MODE;

    err_code = sd_ble_gatts_value_set(p_hids->protocol_mode_handles.value_handle, 
                                      0, 
                                      &len,
                                      &default_protocol_mode);
    if ((err_code != NRF_SUCCESS) && (p_hids->error_handler != NULL))
    {
        p_hids->error_handler(err_code);
    }
}


/**@brief Function for handling the Disconnect event.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_ble_evt   Event received from the BLE stack.
 */
static void on_disconnect(ble_hids_t * p_hids, ble_evt_t * p_ble_evt)
{
    UNUSED_PARAMETER(p_ble_evt);
    p_hids->conn_handle = BLE_CONN_HANDLE_INVALID;
}


/**@brief Function for handling write events to the HID Control Point value.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_evt_write   Write event received from the BLE stack.
 */
static void on_control_point_write(ble_hids_t * p_hids, ble_gatts_evt_write_t * p_evt_write)
{
   if ((p_evt_write->len == 1) && (p_hids->evt_handler != NULL))
   {
        ble_hids_evt_t evt;
        
        // HID Control Point written, propagate event to application
        switch (p_evt_write->data[0])
        {
            case HIDS_CONTROL_POINT_SUSPEND:
                evt.evt_type = BLE_HIDS_EVT_HOST_SUSP;
                break;
                
            case HIDS_CONTROL_POINT_EXIT_SUSPEND:
                evt.evt_type = BLE_HIDS_EVT_HOST_EXIT_SUSP;
                break;
                
            default:
                // Illegal Control Point value, ignore
                return;
        }
        
        p_hids->evt_handler(p_hids, &evt);
    }
}


/**@brief Function for handling write events to the Protocol Mode value.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_evt_write   Write event received from the BLE stack.
 */
static void on_protocol_mode_write(ble_hids_t * p_hids, ble_gatts_evt_write_t * p_evt_write)
{
   if ((p_evt_write->len == 1) && (p_hids->evt_handler != NULL))
   {
        ble_hids_evt_t evt;
        
        // HID Protocol Mode written, propagate event to application
        switch (p_evt_write->data[0])
        {
            case PROTOCOL_MODE_BOOT:
                evt.evt_type = BLE_HIDS_EVT_BOOT_MODE_ENTERED;
                break;
                
            case PROTOCOL_MODE_REPORT:
                evt.evt_type = BLE_HIDS_EVT_REPORT_MODE_ENTERED;
                break;
                
            default:
                // Illegal Protocol Mode value, ignore
                return;
        }
        
        p_hids->evt_handler(p_hids, &evt);
    }
}


/**@brief Function for handling write events to a report CCCD.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_char_id     Id of report characteristic.
 * @param[in]   p_evt_write   Write event received from the BLE stack.
 */
static void on_report_cccd_write(ble_hids_t *            p_hids, 
                                 ble_hids_char_id_t *    p_char_id,
                                 ble_gatts_evt_write_t * p_evt_write)
{
    if (p_evt_write->len == 2)
    {
        // CCCD written, update notification state
        if (p_hids->evt_handler != NULL)
        {
            ble_hids_evt_t evt;
            
            if (ble_srv_is_notification_enabled(p_evt_write->data))
            {
                evt.evt_type = BLE_HIDS_EVT_NOTIF_ENABLED;
            }
            else
            {
                evt.evt_type = BLE_HIDS_EVT_NOTIF_DISABLED;
            }
            evt.params.notification.char_id = *p_char_id;

            p_hids->evt_handler(p_hids, &evt);
        }
    }
}


/**@brief Function for handling write events to a report value.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_char_id   Id of report characteristic.
 */
static void on_report_value_write(ble_hids_t * p_hids, ble_hids_char_id_t * p_char_id)
{
    if (p_hids->evt_handler != NULL)
    {
        ble_hids_evt_t evt;
        
        evt.evt_type                  = BLE_HIDS_EVT_REP_CHAR_WRITE;
        evt.params.char_write.char_id = *p_char_id;

        p_hids->evt_handler(p_hids, &evt);
    }
}


/**@brief Function for finding the Characteristic Id of a characteristic corresponding to a CCCD handle.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   handle      Handle to search for.
 * @param[out]  p_char_id   Id of report characteristic.
 *
 * @return      TRUE if CCCD handle was found, FALSE otherwise.
 */
static bool inp_rep_cccd_identify(ble_hids_t *         p_hids,
                                  uint16_t             handle,
                                  ble_hids_char_id_t * p_char_id)
{
    uint8_t i;
    
    for (i = 0; i < p_hids->inp_rep_count; i++)
    {
        if (handle == p_hids->inp_rep_array[i].char_handles.cccd_handle)
        {
            *p_char_id = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_INPUT, i);
            return true;
        }
    }
    
    return false;
}


/**@brief Function for finding the Characteristic Id of a characteristic corresponding to a value handle.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   handle      Handle to search for.
 * @param[out]  p_char_id   Id of report characteristic.
 *
 * @return      TRUE if value handle was found, FALSE otherwise.
 */
static bool rep_value_identify(ble_hids_t *         p_hids,
                               uint16_t             handle,
                               ble_hids_char_id_t * p_char_id)
{
    uint8_t i;
    
    for (i = 0; i < p_hids->inp_rep_count; i++)
    {
        if (handle == p_hids->inp_rep_array[i].char_handles.value_handle)
        {
            *p_char_id = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_INPUT, i);
            return true;
        }
    }
    
    for (i = 0; i < p_hids->outp_rep_count; i++)
    {
        if (handle == p_hids->outp_rep_array[i].char_handles.value_handle)
        {
            *p_char_id = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_OUTPUT, i);
            return true;
        }
    }
    
    for (i = 0; i < p_hids->feature_rep_count; i++)
    {
        if (handle == p_hids->feature_rep_array[i].char_handles.value_handle)
        {
            *p_char_id = make_char_id(BLE_UUID_REPORT_CHAR, BLE_HIDS_REP_TYPE_FEATURE, i);
            return true;
        }
    }
    
    return false;
}


/**@brief Function for handling the Write event.
 *
 * @param[in]   p_hids      HID Service structure.
 * @param[in]   p_ble_evt   Event received from the BLE stack.
 */
static void on_write(ble_hids_t * p_hids, ble_evt_t * p_ble_evt)
{
    ble_gatts_evt_write_t * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
    ble_hids_char_id_t      char_id;

    if (p_evt_write->handle == p_hids->hid_control_point_handles.value_handle)
    {
        on_control_point_write(p_hids, p_evt_write);
    }
    else if (p_evt_write->handle == p_hids->protocol_mode_handles.value_handle)
    {
        on_protocol_mode_write(p_hids, p_evt_write);
    }
    else if (p_evt_write->handle == p_hids->boot_kb_inp_rep_handles.cccd_handle)
    {
        char_id = make_char_id(BLE_UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR, 0, 0);
        on_report_cccd_write(p_hids, &char_id, p_evt_write);
    }
    else if (p_evt_write->handle == p_hids->boot_kb_inp_rep_handles.value_handle)
    {
        char_id = make_char_id(BLE_UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR, 0, 0);
        on_report_value_write(p_hids, &char_id);
    }
    else if (p_evt_write->handle == p_hids->boot_mouse_inp_rep_handles.cccd_handle)
    {
        char_id = make_char_id(BLE_UUID_BOOT_MOUSE_INPUT_REPORT_CHAR, 0, 0);
        on_report_cccd_write(p_hids, &char_id, p_evt_write);
    }
    else if (p_evt_write->handle == p_hids->boot_mouse_inp_rep_handles.value_handle)
    {
        char_id = make_char_id(BLE_UUID_BOOT_MOUSE_INPUT_REPORT_CHAR, 0, 0);
        on_report_value_write(p_hids, &char_id);
    }
    else if (inp_rep_cccd_identify(p_hids, p_evt_write->handle, &char_id))
    {
        on_report_cccd_write(p_hids, &char_id, p_evt_write);
    }
    else if (rep_value_identify(p_hids, p_evt_write->handle, &char_id))
    {
        on_report_value_write(p_hids, &char_id);
    }
}


void ble_hids_on_ble_evt(ble_hids_t * p_hids, ble_evt_t * p_ble_evt)
{
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            on_connect(p_hids, p_ble_evt);
            break;
            
        case BLE_GAP_EVT_DISCONNECTED:
            on_disconnect(p_hids, p_ble_evt);
            break;
            
        case BLE_GATTS_EVT_WRITE:
            on_write(p_hids, p_ble_evt);
            break;
            
        default:
            // No implementation needed.
            break;
    }
}


/**@brief Function for adding Protocol Mode characteristics.
 *
 * @param[in]   p_hids       HID Service structure.
 * @param[in]   p_sec_mode   Characteristic security settings.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t protocol_mode_char_add(ble_hids_t                    * p_hids,
                                       const ble_srv_security_mode_t * p_sec_mode)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    uint8_t             initial_protocol_mode;
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read          = 1;
    char_md.char_props.write_wo_resp = 1;
    char_md.p_char_user_desc         = NULL;
    char_md.p_char_pf                = NULL;
    char_md.p_user_desc_md           = NULL;
    char_md.p_cccd_md                = NULL;
    char_md.p_sccd_md                = NULL;
    
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_PROTOCOL_MODE_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));

    attr_md.read_perm  = p_sec_mode->read_perm;
    attr_md.write_perm = p_sec_mode->write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    initial_protocol_mode = DEFAULT_PROTOCOL_MODE;
    
    attr_char_value.p_uuid       = &ble_uuid;
    attr_char_value.p_attr_md    = &attr_md;
    attr_char_value.init_len     = sizeof(uint8_t);
    attr_char_value.init_offs    = 0;
    attr_char_value.max_len      = sizeof(uint8_t);
    attr_char_value.p_value      = &initial_protocol_mode;
    
    return sd_ble_gatts_characteristic_add(p_hids->service_handle,
                                           &char_md,
                                           &attr_char_value,
                                           &p_hids->protocol_mode_handles);
}


/**@brief Function for adding report characteristics.
 *
 * @param[in]   p_hids              HID Service structure.
 * @param[in]   p_properties        Report characteristic properties.
 * @param[in]   max_len             Maximum length of report value.
 * @param[in]   p_rep_ref           Report Reference descriptor.
 * @param[in]   p_rep_ref_attr_md   Characteristic security settings.
 * @param[out]  p_rep_char          Handles of new characteristic.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t rep_char_add(ble_hids_t *                   p_hids,
                             ble_gatt_char_props_t *        p_properties,
                             uint16_t                       max_len,
                             ble_srv_report_ref_t *         p_rep_ref,
                             ble_srv_cccd_security_mode_t * p_rep_ref_attr_md,
                             ble_hids_rep_char_t *          p_rep_char)
{
    uint32_t            err_code;
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    uint8_t             encoded_rep_ref[BLE_SRV_ENCODED_REPORT_REF_LEN];
    
    // Add Report characteristic
    if (p_properties->notify)
    {
        memset(&cccd_md, 0, sizeof(cccd_md));
        BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
        cccd_md.write_perm = p_rep_ref_attr_md->cccd_write_perm;
        cccd_md.vloc = BLE_GATTS_VLOC_STACK;
    }
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props       = *p_properties;
    char_md.p_char_user_desc = NULL;
    char_md.p_char_pf        = NULL;
    char_md.p_user_desc_md   = NULL;
    char_md.p_cccd_md        = (p_properties->notify) ? &cccd_md : NULL;
    char_md.p_sccd_md        = NULL;
    
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_REPORT_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));

    attr_md.read_perm  = p_rep_ref_attr_md->read_perm;
    attr_md.write_perm = p_rep_ref_attr_md->write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 1;
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    attr_char_value.p_uuid    = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = 0;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = max_len;
    attr_char_value.p_value   = NULL;
    
    err_code = sd_ble_gatts_characteristic_add(p_hids->service_handle,
                                               &char_md,
                                               &attr_char_value,
                                               &p_rep_char->char_handles);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add Report Reference descriptor
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_REPORT_REF_DESCR);
    
    memset(&attr_md, 0, sizeof(attr_md));

    attr_md.read_perm  = p_rep_ref_attr_md->read_perm;
    attr_md.write_perm = p_rep_ref_attr_md->write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    attr_char_value.p_uuid    = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = ble_srv_report_ref_encode(encoded_rep_ref, p_rep_ref);
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = attr_char_value.init_len;
    attr_char_value.p_value   = encoded_rep_ref;
    
    return sd_ble_gatts_descriptor_add(p_rep_char->char_handles.value_handle,
                                       &attr_char_value,
                                       &p_rep_char->ref_handle);
}


/**@brief Function for adding Report Map characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t rep_map_char_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    uint32_t            err_code;
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    
    // Add Report Map characteristic
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read  = 1;
    char_md.p_char_user_desc = NULL;
    char_md.p_char_pf        = NULL;
    char_md.p_user_desc_md   = NULL;
    char_md.p_cccd_md        = NULL;
    char_md.p_sccd_md        = NULL;
    
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_REPORT_MAP_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));
    
    attr_md.read_perm  = p_hids_init->rep_map.security_mode.read_perm;
    attr_md.write_perm = p_hids_init->rep_map.security_mode.write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 1;
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    attr_char_value.p_uuid    = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = p_hids_init->rep_map.data_len;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = p_hids_init->rep_map.data_len;
    attr_char_value.p_value   = p_hids_init->rep_map.p_data;
    
    err_code = sd_ble_gatts_characteristic_add(p_hids->service_handle,
                                               &char_md,
                                               &attr_char_value,
                                               &p_hids->rep_map_handles);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }
    
    if (p_hids_init->rep_map.p_ext_rep_ref != NULL)
    {
        uint8_t encoded_rep_ref[sizeof(ble_uuid128_t)];
        uint8_t encoded_rep_ref_len;
        
        // Add External Report Reference descriptor
        BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_EXTERNAL_REPORT_REF_DESCR);
        
        memset(&attr_md, 0, sizeof(attr_md));

        BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(&attr_md.read_perm);
        BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&attr_md.write_perm);

        attr_md.vloc    = BLE_GATTS_VLOC_STACK;
        attr_md.rd_auth = 0;
        attr_md.wr_auth = 0;
        attr_md.vlen    = 0;
        
        err_code = sd_ble_uuid_encode(p_hids_init->rep_map.p_ext_rep_ref,
                                      &encoded_rep_ref_len,
                                      encoded_rep_ref);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        
        memset(&attr_char_value, 0, sizeof(attr_char_value));
    
        attr_char_value.p_uuid    = &ble_uuid;
        attr_char_value.p_attr_md = &attr_md;
        attr_char_value.init_len  = encoded_rep_ref_len;
        attr_char_value.init_offs = 0;
        attr_char_value.max_len   = attr_char_value.init_len;
        attr_char_value.p_value   = encoded_rep_ref;
        
        err_code = sd_ble_gatts_descriptor_add(p_hids->rep_map_handles.value_handle,
                                               &attr_char_value,
                                               &p_hids->rep_map_ext_rep_ref_handle);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }
    
    return NRF_SUCCESS;
}


/**@brief Function for adding Input Report characteristics.
 *
 * @param[in]   p_hids           HID Service structure.
 * @param[in]   uuid             UUID of report characteristic to be added.
 * @param[in]   max_data_len     Maximum length of report value.
 * @param[in]   p_sec_mode       Characteristic security settings.
 * @param[out]  p_char_handles   Handles of new characteristic.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t boot_inp_rep_char_add(ble_hids_t *                         p_hids,
                                      uint16_t                             uuid,
                                      uint16_t                             max_data_len,
                                      const ble_srv_cccd_security_mode_t * p_sec_mode,
                                      ble_gatts_char_handles_t *           p_char_handles)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_md_t cccd_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    
    memset(&cccd_md, 0, sizeof(cccd_md));

    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
    cccd_md.write_perm = p_sec_mode->cccd_write_perm;
    cccd_md.vloc       = BLE_GATTS_VLOC_STACK;
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read   = 1;
    char_md.char_props.write  = (p_sec_mode->write_perm.sm) ? 1 : 0;
    char_md.char_props.notify = 1;
    char_md.p_char_user_desc  = NULL;
    char_md.p_char_pf         = NULL;
    char_md.p_user_desc_md    = NULL;
    char_md.p_cccd_md         = &cccd_md;
    char_md.p_sccd_md         = NULL;
    
    BLE_UUID_BLE_ASSIGN(ble_uuid, uuid);
    
    memset(&attr_md, 0, sizeof(attr_md));

    attr_md.read_perm  = p_sec_mode->read_perm;
    attr_md.write_perm = p_sec_mode->write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    attr_char_value.p_uuid    = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = 0;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = max_data_len;
    attr_char_value.p_value   = NULL;
    
    return sd_ble_gatts_characteristic_add(p_hids->service_handle,
                                           &char_md,
                                           &attr_char_value,
                                           p_char_handles);
}


/**@brief Function for adding Boot Keyboard Output Report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t boot_kb_outp_rep_char_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read          = 1;
    char_md.char_props.write         = 1;
    char_md.char_props.write_wo_resp = 1;
    char_md.p_char_user_desc         = NULL;
    char_md.p_char_pf                = NULL;
    char_md.p_user_desc_md           = NULL;
    char_md.p_cccd_md                = NULL;
    char_md.p_sccd_md                = NULL;
    
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_BOOT_KEYBOARD_OUTPUT_REPORT_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));

    attr_md.read_perm  = p_hids_init->security_mode_boot_kb_outp_rep.read_perm;
    attr_md.write_perm = p_hids_init->security_mode_boot_kb_outp_rep.write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    attr_char_value.p_uuid    = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = 0;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = BOOT_KB_OUTPUT_REPORT_MAX_SIZE;
    attr_char_value.p_value   = NULL;
    
    return sd_ble_gatts_characteristic_add(p_hids->service_handle, &char_md, &attr_char_value,
                                           &p_hids->boot_kb_outp_rep_handles);
}


/**@brief Function for encoding a HID Information characteristic value.
 *
 * @param[out]  p_encoded_buffer    Buffer where the encoded data will be written.
 * @param[in]   p_hid_information   Measurement to be encoded.
 *
 * @return      Size of encoded data.
 */
static uint8_t encode_hid_information(uint8_t *                          p_encoded_buffer,
                                      const ble_hids_hid_information_t * p_hid_information)
{
    uint8_t len = uint16_encode(p_hid_information->bcd_hid, p_encoded_buffer);
    
    p_encoded_buffer[len++] = p_hid_information->b_country_code;
    p_encoded_buffer[len++] = p_hid_information->flags;
    
    APP_ERROR_CHECK_BOOL(len == ENCODED_HID_INFORMATION_LEN);

    return len;
}


/**@brief Function for adding HID Information characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t hid_information_char_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    uint8_t             encoded_hid_information[ENCODED_HID_INFORMATION_LEN];
    uint8_t             hid_info_len;
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.read  = 1;
    char_md.p_char_user_desc = NULL;
    char_md.p_char_pf        = NULL;
    char_md.p_user_desc_md   = NULL;
    char_md.p_cccd_md        = NULL;
    char_md.p_sccd_md        = NULL;
    
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_HID_INFORMATION_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));

    attr_md.read_perm  = p_hids_init->hid_information.security_mode.read_perm;
    attr_md.write_perm = p_hids_init->hid_information.security_mode.write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
    
    hid_info_len = encode_hid_information(encoded_hid_information, &p_hids_init->hid_information);
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    attr_char_value.p_uuid    = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = hid_info_len;
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = attr_char_value.init_len;
    attr_char_value.p_value   = encoded_hid_information;
    
    return sd_ble_gatts_characteristic_add(p_hids->service_handle, &char_md, 
                                           &attr_char_value, 
                                           &p_hids->hid_information_handles);
}


/**@brief Function for adding HID Control Point characteristics.
 *
 * @param[in]   p_hids       HID Service structure.
 * @param[in]   p_sec_mode   Characteristic security settings.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t hid_control_point_char_add(ble_hids_t *                    p_hids,
                                           const ble_srv_security_mode_t * p_sec_mode)
{
    ble_gatts_char_md_t char_md;
    ble_gatts_attr_t    attr_char_value;
    ble_uuid_t          ble_uuid;
    ble_gatts_attr_md_t attr_md;
    uint8_t             initial_hid_control_point;
    
    memset(&char_md, 0, sizeof(char_md));
    
    char_md.char_props.write_wo_resp = 1;
    char_md.p_char_user_desc         = NULL;
    char_md.p_char_pf                = NULL;
    char_md.p_user_desc_md           = NULL;
    char_md.p_cccd_md                = NULL;
    char_md.p_sccd_md                = NULL;
    
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_HID_CONTROL_POINT_CHAR);
    
    memset(&attr_md, 0, sizeof(attr_md));

    attr_md.read_perm  = p_sec_mode->read_perm;
    attr_md.write_perm = p_sec_mode->write_perm;
    attr_md.vloc       = BLE_GATTS_VLOC_STACK;
    attr_md.rd_auth    = 0;
    attr_md.wr_auth    = 0;
    attr_md.vlen       = 0;
    
    initial_hid_control_point = INITIAL_VALUE_HID_CONTROL_POINT;
    
    memset(&attr_char_value, 0, sizeof(attr_char_value));
    
    attr_char_value.p_uuid    = &ble_uuid;
    attr_char_value.p_attr_md = &attr_md;
    attr_char_value.init_len  = sizeof(uint8_t);
    attr_char_value.init_offs = 0;
    attr_char_value.max_len   = sizeof(uint8_t);
    attr_char_value.p_value   = &initial_hid_control_point;
    
    return sd_ble_gatts_characteristic_add(p_hids->service_handle, &char_md, 
                                           &attr_char_value, 
                                           &p_hids->hid_control_point_handles);
}


/**@brief Function for adding input report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t inp_rep_characteristics_add(ble_hids_t *            p_hids,
                                            const ble_hids_init_t * p_hids_init)
{
    if ((p_hids_init->inp_rep_count != 0) && (p_hids_init->p_inp_rep_array != NULL))
    {
        uint8_t i;
        
        for (i = 0; i < p_hids_init->inp_rep_count; i++)
        {
            uint32_t                  err_code;
            ble_hids_inp_rep_init_t * p_rep_init = &p_hids_init->p_inp_rep_array[i];
            ble_gatt_char_props_t     properties;
            
            memset(&properties, 0, sizeof(properties));
            
            properties.read   = true;
            properties.write  = p_rep_init->security_mode.write_perm.sm ? 1 : 0;
            properties.notify = true;
        
            err_code = rep_char_add(p_hids, 
                                    &properties, 
                                    p_rep_init->max_len, 
                                    &p_rep_init->rep_ref,
                                    &p_rep_init->security_mode,
                                    &p_hids->inp_rep_array[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
    }
    
    return NRF_SUCCESS;
}


/**@brief Function for adding output report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t outp_rep_characteristics_add(ble_hids_t *            p_hids,
                                             const ble_hids_init_t * p_hids_init)
{
    if ((p_hids_init->outp_rep_count != 0) && (p_hids_init->p_outp_rep_array != NULL))
    {
        uint8_t i;
        
        for (i = 0; i < p_hids_init->outp_rep_count; i++)
        {
            uint32_t                   err_code;
            ble_hids_outp_rep_init_t * p_rep_init = &p_hids_init->p_outp_rep_array[i];
            ble_gatt_char_props_t      properties;
            
            memset(&properties, 0, sizeof(properties));
            
            properties.read          = true;
            properties.write         = true;
            properties.write_wo_resp = true;
        
            err_code = rep_char_add(p_hids, 
                                    &properties, 
                                    p_rep_init->max_len, 
                                    &p_rep_init->rep_ref,
                                    &p_rep_init->security_mode,
                                    &p_hids->outp_rep_array[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
    }
    
    return NRF_SUCCESS;
}


/**@brief Function for adding feature report characteristics.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t feature_rep_characteristics_add(ble_hids_t *            p_hids,
                                                const ble_hids_init_t * p_hids_init)
{
    if ((p_hids_init->feature_rep_count != 0) && (p_hids_init->p_feature_rep_array != NULL))
    {
        uint8_t i;
        
        for (i = 0; i < p_hids_init->feature_rep_count; i++)
        {
            uint32_t                      err_code;
            ble_hids_feature_rep_init_t * p_rep_init = &p_hids_init->p_feature_rep_array[i];
            ble_gatt_char_props_t         properties;
            
            memset(&properties, 0, sizeof(properties));
            
            properties.read  = true;
            properties.write = true;
        
            err_code = rep_char_add(p_hids, 
                                    &properties, 
                                    p_rep_init->max_len, 
                                    &p_rep_init->rep_ref,
                                    &p_rep_init->security_mode,
                                    &p_hids->feature_rep_array[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
    }
    
    return NRF_SUCCESS;
}


/**@brief Function for adding included services.
 *
 * @param[in]   p_hids        HID Service structure.
 * @param[in]   p_hids_init   Service initialization structure.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t includes_add(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    uint32_t err_code;
    uint8_t  i;
    uint16_t unused_include_handle;
    
    for (i = 0; i < p_hids_init->included_services_count; i++)
    {
        err_code = sd_ble_gatts_include_add(p_hids->service_handle,
                                            p_hids_init->p_included_services_array[i],
                                            &unused_include_handle);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }
    
    return NRF_SUCCESS;
}


uint32_t ble_hids_init(ble_hids_t * p_hids, const ble_hids_init_t * p_hids_init)
{
    uint32_t   err_code;
    ble_uuid_t ble_uuid;
    
    if ((p_hids_init->inp_rep_count > BLE_HIDS_MAX_INPUT_REP) ||
        (p_hids_init->outp_rep_count > BLE_HIDS_MAX_OUTPUT_REP) ||
        (p_hids_init->feature_rep_count > BLE_HIDS_MAX_FEATURE_REP)
    )
    {
        return NRF_ERROR_INVALID_PARAM;
    }
    
    // Initialize service structure.
    p_hids->evt_handler       = p_hids_init->evt_handler;
    p_hids->error_handler     = p_hids_init->error_handler;
    p_hids->inp_rep_count     = p_hids_init->inp_rep_count;
    p_hids->outp_rep_count    = p_hids_init->outp_rep_count;
    p_hids->feature_rep_count = p_hids_init->feature_rep_count;
    p_hids->conn_handle       = BLE_CONN_HANDLE_INVALID;
    
    // Add service.
    BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_HUMAN_INTERFACE_DEVICE_SERVICE);

    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                        &ble_uuid,
                                        &p_hids->service_handle);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }
    
    // Add includes.
    err_code = includes_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }
    
    if (p_hids_init->is_kb || p_hids_init->is_mouse)
    {
        // Add Protocol Mode characteristic.
        err_code = protocol_mode_char_add(p_hids, &p_hids_init->security_mode_protocol);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }

    // Add Input Report characteristics (if any).
    err_code = inp_rep_characteristics_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add Output Report characteristics (if any).
    err_code = outp_rep_characteristics_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add Feature Report characteristic (if any).
    err_code = feature_rep_characteristics_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }
    
    // Add Report Map characteristic.
    err_code = rep_map_char_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    if (p_hids_init->is_kb)
    {
        // Add Boot Keyboard Input Report characteristic.
        err_code = boot_inp_rep_char_add(p_hids,
                                         BLE_UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR,
                                         BOOT_KB_INPUT_REPORT_MAX_SIZE,
                                         &p_hids_init->security_mode_boot_kb_inp_rep,
                                         &p_hids->boot_kb_inp_rep_handles);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        
        // Add Boot Keyboard Output Report characteristic.
        err_code = boot_kb_outp_rep_char_add(p_hids, p_hids_init);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }
    
    if (p_hids_init->is_mouse)
    {
        // Add Boot Mouse Input Report characteristic.
        err_code = boot_inp_rep_char_add(p_hids,
                                         BLE_UUID_BOOT_MOUSE_INPUT_REPORT_CHAR,
                                         BOOT_MOUSE_INPUT_REPORT_MAX_SIZE,
                                         &p_hids_init->security_mode_boot_mouse_inp_rep,
                                         &p_hids->boot_mouse_inp_rep_handles);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    }
    
    // Add HID Information characteristic.
    err_code = hid_information_char_add(p_hids, p_hids_init);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Add HID Control Point characteristic.
    err_code = hid_control_point_char_add(p_hids, &p_hids_init->security_mode_ctrl_point);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }
    
    return NRF_SUCCESS;
}


uint32_t ble_hids_inp_rep_send(ble_hids_t * p_hids,
                               uint8_t      rep_index,
                               uint16_t     len,
                               uint8_t *    p_data)
{
    uint32_t err_code;

    if (rep_index < p_hids->inp_rep_count)
    {
        ble_hids_rep_char_t * p_rep_char = &p_hids->inp_rep_array[rep_index];
        
        if (p_hids->conn_handle != BLE_CONN_HANDLE_INVALID)
        {
            ble_gatts_hvx_params_t hvx_params;
            uint16_t               hvx_len = len;
            
            memset(&hvx_params, 0, sizeof(hvx_params));
            
            hvx_params.handle = p_rep_char->char_handles.value_handle;
            hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
            hvx_params.offset = 0;
            hvx_params.p_len  = &hvx_len;
            hvx_params.p_data = p_data;
            
            err_code = sd_ble_gatts_hvx(p_hids->conn_handle, &hvx_params);
            if ((err_code == NRF_SUCCESS) && (hvx_len != len))
            {
                err_code = NRF_ERROR_DATA_SIZE;
            }
        }
        else
        {
            err_code = NRF_ERROR_INVALID_STATE;
        }
    }
    else
    {
        err_code = NRF_ERROR_INVALID_PARAM;
    }
    
    return err_code;
}


uint32_t ble_hids_boot_kb_inp_rep_send(ble_hids_t * p_hids, uint16_t len, uint8_t * p_data)
{
    uint32_t err_code;

    if (p_hids->conn_handle != BLE_CONN_HANDLE_INVALID)
    {
        ble_gatts_hvx_params_t hvx_params;
        uint16_t               hvx_len = len;
        
        memset(&hvx_params, 0, sizeof(hvx_params));
        
        hvx_params.handle = p_hids->boot_kb_inp_rep_handles.value_handle;
        hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
        hvx_params.offset = 0;
        hvx_params.p_len  = &hvx_len;
        hvx_params.p_data = p_data;
        
        err_code = sd_ble_gatts_hvx(p_hids->conn_handle, &hvx_params);
        if ((err_code == NRF_SUCCESS) && (hvx_len != len))
        {
            err_code = NRF_ERROR_DATA_SIZE;
        }
    }
    else
    {
        err_code = NRF_ERROR_INVALID_STATE;
    }
    
    return err_code;
}


uint32_t ble_hids_boot_mouse_inp_rep_send(ble_hids_t * p_hids,
                                          uint8_t      buttons,
                                          int8_t       x_delta,
                                          int8_t       y_delta,
                                          uint16_t     optional_data_len,
                                          uint8_t *    p_optional_data)
{
    uint32_t err_code;

    if (p_hids->conn_handle != BLE_CONN_HANDLE_INVALID)
    {
        uint16_t hvx_len = BOOT_MOUSE_INPUT_REPORT_MIN_SIZE + optional_data_len;
        
        if (hvx_len <= BOOT_MOUSE_INPUT_REPORT_MAX_SIZE)
        {
            uint8_t                buffer[BOOT_MOUSE_INPUT_REPORT_MAX_SIZE];
            ble_gatts_hvx_params_t hvx_params;

            APP_ERROR_CHECK_BOOL(BOOT_MOUSE_INPUT_REPORT_MIN_SIZE == 3);
            
            // Build buffer
            buffer[0] = buttons;
            buffer[1] = (uint8_t)x_delta;
            buffer[2] = (uint8_t)y_delta;
            
            if (optional_data_len > 0)
            {
                memcpy(&buffer[3], p_optional_data, optional_data_len);
            }
            
            // Pass buffer to stack
            memset(&hvx_params, 0, sizeof(hvx_params));
            
            hvx_params.handle = p_hids->boot_mouse_inp_rep_handles.value_handle;
            hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
            hvx_params.offset = 0;
            hvx_params.p_len  = &hvx_len;
            hvx_params.p_data = buffer;
            
            err_code = sd_ble_gatts_hvx(p_hids->conn_handle, &hvx_params);
            if ((err_code == NRF_SUCCESS) &&
                (hvx_len != BOOT_MOUSE_INPUT_REPORT_MIN_SIZE + optional_data_len)
            )
            {
                err_code = NRF_ERROR_DATA_SIZE;
            }
        }
        else
        {
            err_code = NRF_ERROR_DATA_SIZE;
        }
    }
    else
    {
        err_code = NRF_ERROR_INVALID_STATE;
    }
    
    return err_code;
}


uint32_t ble_hids_outp_rep_get(ble_hids_t * p_hids,
                               uint8_t      rep_index,
                               uint16_t     len,
                               uint8_t      offset,
                               uint8_t *    p_outp_rep)
{
    return sd_ble_gatts_value_get(p_hids->outp_rep_array[rep_index].char_handles.value_handle,
                                  offset,
                                  &len,
                                  p_outp_rep);
}

/**
  @}
*/
