/* Copyright (c) 2009-2019 Arm Limited
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*************************************************************************************************/
/*!
 *  \brief Dynamic ATT services and attributes.
 */
/*************************************************************************************************/

#include <string.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "wsf_math.h"
#include "util/bstream.h"
#include "att_api.h"
#include "att_main.h"
#include "dm_api.h"

/**************************************************************************************************
  Macros
**************************************************************************************************/

/* Configurable number of bytes in the Dynamic Service and Attribute Heap */
#ifndef ATTS_DYN_HEAP_SIZE
#define ATTS_DYN_HEAP_SIZE                  1280
#endif

/* Configurable memory byte alignment Dynamic Service and Attribute Heap */
#ifndef ATTS_DYN_ALIGNMENT
#define ATTS_DYN_ALIGNMENT                  4
#endif

/**************************************************************************************************
  Data Types
**************************************************************************************************/

/* Dynamic service and attributes control block */
typedef struct
{
  uint8_t numServices;
  uint8_t *pNextBuffer;
} attsDynCb_t;

/* Dynamic service group control block */
typedef struct
{
  attsGroup_t group;
  uint16_t currentAttr;
} attsDynGroupCb_t;

/**************************************************************************************************
  Local Variables
**************************************************************************************************/

/* Dynamic Service and Attribute Heap */
static uint8_t attsDynHeap[ATTS_DYN_HEAP_SIZE];

/* Dynamic Service and Attribute control block */
static attsDynCb_t attsDynCb;

/*************************************************************************************************/
/*!
 *  \brief  Allocate memory from the Dynamic Service and Attribute Heap.
 *
 *  \param  size    Size of buffer to allocate in bytes
 *
 *  \return Allocated buffer or NULL if failed to allocate a buffer.
 */
/*************************************************************************************************/
static void *attsDynAlloc(uint16_t size)
{
  uint8_t *pMem = attsDynCb.pNextBuffer;

  /* Verify enough space in heap for buffer */
  if (pMem + size <= attsDynHeap + ATTS_DYN_HEAP_SIZE)
  {
#if ATTS_DYN_ALIGNMENT > 1
    /* Increase size if size not a multiple of the memory alignment */
    if (size % ATTS_DYN_ALIGNMENT)
    {
      size += ATTS_DYN_ALIGNMENT - (size % ATTS_DYN_ALIGNMENT);
    }
#endif

    /* Set the next buffer location */
    attsDynCb.pNextBuffer += size;

    return pMem;
  }

  /* Out of heap */
  WSF_ASSERT(FALSE);
  return NULL;
}

/*************************************************************************************************/
/*!
 *  \brief  Initialize the Dynamic ATT Service subsystem.
 *
 *  \param  None
 *
 *  \return None.
 */
/*************************************************************************************************/
void AttsDynInit()
{
  attsDynCb.numServices = 0;
  attsDynCb.pNextBuffer = attsDynHeap;
}

/*************************************************************************************************/
/*!
 *  \brief  Dynamically create an ATT Service at runtime.
 *
 *  \param  startHandle       Starting attribute handle in the service
 *  \param  endHandle         Last attribute handle in the service
 *
 *  \return Service Handle.
 *
 *  \note   It is recommended this function only be used when no connections are open and the
 *          device is not in a connectable mode.
 */
/*************************************************************************************************/
void *AttsDynCreateGroup(uint16_t startHandle, uint16_t endHandle)
{
  attsDynGroupCb_t *pGroup = NULL;

  /* Allocate memory for the service control block */
  pGroup = attsDynAlloc(sizeof(attsDynGroupCb_t));
  WSF_ASSERT(pGroup);

  if (pGroup != NULL)
  {

    /* Initialize the service group */
    pGroup->group.startHandle = startHandle;
    pGroup->group.endHandle = endHandle;
    pGroup->group.readCback = NULL;
    pGroup->group.writeCback = NULL;

    /* Allocate memory for the attributes */
    pGroup->group.pAttr = attsDynAlloc(sizeof(attsAttr_t) * (endHandle - startHandle + 1));
    WSF_ASSERT(pGroup->group.pAttr);

    pGroup->currentAttr = 0;
    attsDynCb.numServices++;
  }

  return pGroup;
}

/*************************************************************************************************/
/*!
 *  \brief  Dynamically delete an ATT Service at runtime.
 *
 *  \param  pSvcHandle      Service handle returned by AttsDynCreateGroup
 *
 *  \return None.
 *
 *  \note   It is recommended this function only be used when no connections are open and the
 *          device is not in a connectable mode.
 */
/*************************************************************************************************/
void AttsDynDeleteGroup(void *pSvcHandle)
{
  attsDynGroupCb_t *pGroup = pSvcHandle;

  WSF_ASSERT(attsDynCb.numServices);
  WSF_ASSERT(pGroup);

  /* Remove the group */
  AttsRemoveGroup(pGroup->group.startHandle);

  attsDynCb.numServices--;

  /* If there are no more dynamic groups, initialize the subsystem to free the memory in the heap */
  if (attsDynCb.numServices == 0)
  {
    AttsDynInit();
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Register callback functions for a dynamic ATT Service at runtime.
 *
 *  \param  pSvcHandle    Service handle returned by AttsDynCreateGroup
 *  \param  readCback     Read callback function.
 *  \param  writeCback    Write callback function.
 *
 *  \return None.
 *
 *  \note   It is recommended this function only be used when no connections are open and the
 *          device is not in a connectable mode.
 */
/*************************************************************************************************/
void AttsDynRegister(void *pSvcHandle, attsReadCback_t readCback, attsWriteCback_t writeCback)
{
  attsDynGroupCb_t *pGroup = pSvcHandle;

  WSF_ASSERT(pGroup);

  pGroup->group.readCback = readCback;
  pGroup->group.writeCback = writeCback;
}

/*************************************************************************************************/
/*!
 *  \brief  Dynamically add an attribute to a dynamic ATT Services at app initialization.
 *
 *  \param  pSvcHandle    Service handle returned by AttsDynCreateGroup
 *  \param  pUuid         Constant UUID
 *  \param  pValue        Initial value of attribute (copied into attribute memory)
 *  \param  len           Length of pValue in bytes
 *  \param  maxLen        Maximum length of the attribute in bytes
 *  \param  settings      Attribute settings
 *  \param  permissions   Attribute permissions
 *
 *  \return None.
 *
 *  \note   It is recommended this function only be used when no connections are open and the
 *          device is not in a connectable mode.
 */
/*************************************************************************************************/
void AttsDynAddAttr(void *pSvcHandle, const uint8_t *pUuid, const uint8_t *pValue, uint16_t len,
                    const uint16_t maxLen, uint8_t settings, uint8_t permissions)
{
  attsAttr_t *pAttr;
  attsDynGroupCb_t *pGroup = pSvcHandle;
  uint16_t handle = pGroup->group.startHandle + pGroup->currentAttr++;

  /* Verify inputs */
  WSF_ASSERT(handle <= pGroup->group.endHandle);
  WSF_ASSERT(pUuid);
  WSF_ASSERT(len <= maxLen);

  pAttr = pGroup->group.pAttr + (handle - pGroup->group.startHandle);


  /* Allocate a buffer for the length of the attribute */
  pAttr->pLen = attsDynAlloc(sizeof(uint16_t));
  WSF_ASSERT(pAttr->pLen);

  if (pAttr->pLen == NULL)
  {
    return;
  }

  /* Allocate a buffer for the value of the attribute */
  pAttr->pValue = attsDynAlloc(maxLen);
  WSF_ASSERT(pAttr->pValue);

  if (pAttr->pValue == NULL)
  {
    return;
  }

  /* Set the attribute values */
  pAttr->pUuid = pUuid;
  pAttr->maxLen = maxLen;
  pAttr->settings = settings;
  pAttr->permissions = permissions;



  if (pValue)
  {
    /* Copy the initial value */
    memcpy(pAttr->pValue, pValue, len);
    *pAttr->pLen = len;
  }
  else
  {
    /* No initial value, zero value and length */
    memset(pAttr->pValue, 0, maxLen);
    *pAttr->pLen = 0;
  }

  /* Add the service when the last attribute has been added */
  if (handle == pGroup->group.endHandle)
  {
    AttsAddGroup(&pGroup->group);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Dynamically add an attribute with a constant value to a dynamic ATT Services.
 *
 *  \param  pSvcHandle    Service handle returned by AttsDynCreateGroup
 *  \param  pUuid         Constant UUID
 *  \param  pValue        Pointer to constant attribute memory
 *  \param  len           Length of pValue in bytes
 *  \param  settings      Attribute settings
 *  \param  permissions   Attribute permissions
 *
 *  \return None.
 *
 *  \note   It is recommended this function only be used when no connections are open and the
 *          device is not in a connectable mode.
 */
/*************************************************************************************************/
void AttsDynAddAttrConst(void *pSvcHandle, const uint8_t *pUuid, const uint8_t *pValue,
                         const uint16_t len, uint8_t settings, uint8_t permissions)
{
  attsAttr_t *pAttr;
  attsDynGroupCb_t *pGroup = pSvcHandle;
  uint16_t handle = pGroup->group.startHandle + pGroup->currentAttr++;

  /* Verify inputs */
  WSF_ASSERT(handle <= pGroup->group.endHandle);
  WSF_ASSERT(pValue);
  WSF_ASSERT(pUuid);

  pAttr = pGroup->group.pAttr + (handle - pGroup->group.startHandle);

  /* Allocate a buffer for the length of the attribute */
  pAttr->pLen = attsDynAlloc(sizeof(uint16_t));
  WSF_ASSERT(pAttr->pLen);

  if (pAttr->pLen == NULL)
  {
    return;
  }

  /* Set the attribute values */
  *pAttr->pLen = len;
  pAttr->pUuid = pUuid;
  pAttr->pValue = (uint8_t *) pValue;
  pAttr->maxLen = len;
  pAttr->settings = settings;
  pAttr->permissions = permissions;

  /* Add the service when the last attribute has been added */
  if (handle == pGroup->group.endHandle)
  {
    AttsAddGroup(&pGroup->group);
  }
}
