/*
 * Copyright (C) 2010 NXP Semiconductors
 *
 * 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.
 */

/**
 * \file  phFriNfc_Llcp.c
 * \brief NFC LLCP core
 *
 * Project: NFC-FRI
 *
 */

/*include files*/
#include <phNfcTypes.h>
#include <phNfcHalTypes.h>
#include <phLibNfcStatus.h>
#include <phFriNfc_LlcpUtils.h>
#include <phFriNfc_Llcp.h>

NFCSTATUS phFriNfc_Llcp_DecodeTLV( phNfc_sData_t  *psRawData,
                                   uint32_t       *pOffset,
                                   uint8_t        *pType,
                                   phNfc_sData_t  *psValueBuffer )
{
   uint8_t type;
   uint8_t length;
   uint32_t offset = *pOffset;

   /* Check for NULL pointers */
   if ((psRawData == NULL) || (pOffset == NULL) || (pType == NULL) || (psValueBuffer == NULL))
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Check offset */
   if (offset > psRawData->length)
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Check if enough room for Type and Length (with overflow check) */
   if ((offset+2 > psRawData->length) && (offset+2 > offset))
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Get Type and Length from current TLV, and update offset */
   type = psRawData->buffer[offset];
   length = psRawData->buffer[offset+1];
   offset += 2;

   /* Check if enough room for Value with announced Length (with overflow check) */
   if ((offset+length > psRawData->length) && (offset+length > offset))
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Save response, and update offset */
   *pType = type;
   psValueBuffer->buffer = psRawData->buffer + offset;
   psValueBuffer->length = length;
   offset += length;

   /* Save updated offset */
   *pOffset = offset;

   return NFCSTATUS_SUCCESS;
}

NFCSTATUS phFriNfc_Llcp_EncodeTLV( phNfc_sData_t  *psValueBuffer,
                                   uint32_t       *pOffset,
                                   uint8_t        type,
                                   uint8_t        length,
                                   uint8_t        *pValue)
{
   uint32_t offset = *pOffset;
   uint32_t finalOffset = offset + 2 + length; /* 2 stands for Type and Length fields size */
   uint8_t i;

   /* Check for NULL pointers */
   if ((psValueBuffer == NULL) || (pOffset == NULL) || (pValue == NULL))
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Check offset */
   if (offset > psValueBuffer->length)
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Check if enough room for Type, Length and Value (with overflow check) */
   if ((finalOffset > psValueBuffer->length) || (finalOffset < offset))
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Set the TYPE */
   psValueBuffer->buffer[offset] = type;
   offset += 1;

   /* Set the LENGTH */
   psValueBuffer->buffer[offset] = length;
   offset += 1;

   /* Set the VALUE */
   for(i=0;i<length;i++,offset++)
   {
      psValueBuffer->buffer[offset]  = pValue[i];
   }

   /* Save updated offset */
   *pOffset = offset;

   return NFCSTATUS_SUCCESS;
}

NFCSTATUS phFriNfc_Llcp_AppendTLV( phNfc_sData_t  *psValueBuffer,
                                   uint32_t       nTlvOffset,
                                   uint32_t       *pCurrentOffset,
                                   uint8_t        length,
                                   uint8_t        *pValue)
{
   uint32_t offset = *pCurrentOffset;
   uint32_t finalOffset = offset + length;

   /* Check for NULL pointers */
   if ((psValueBuffer == NULL) || (pCurrentOffset == NULL) || (pValue == NULL))
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Check offset */
   if (offset > psValueBuffer->length)
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Check if enough room for Type and Length (with overflow check) */
   if ((finalOffset > psValueBuffer->length) || (finalOffset < offset))
   {
      return PHNFCSTVAL(CID_FRI_NFC_LLCP, NFCSTATUS_INVALID_PARAMETER);
   }

   /* Update the LENGTH */
   psValueBuffer->buffer[nTlvOffset+1] += length;

   /* Set the VALUE */
   memcpy(psValueBuffer->buffer + offset, pValue, length);
   offset += length;

   /* Save updated offset */
   *pCurrentOffset = offset;

   return NFCSTATUS_SUCCESS;
}


/* TODO: comment function EncodeMIUX */
void phFriNfc_Llcp_EncodeMIUX(uint16_t miux,
                              uint8_t* pMiuxEncoded)
{
   /* MASK */
   miux = miux & PHFRINFC_LLCP_TLV_MIUX_MASK;

   pMiuxEncoded[0] = miux >> 8;
   pMiuxEncoded[1] = miux & 0xff;
}

/* TODO: comment function EncodeRW */
void phFriNfc_Llcp_EncodeRW(uint8_t *pRw)
{
   /* MASK */
   *pRw = *pRw & PHFRINFC_LLCP_TLV_RW_MASK;
}

/**
 * Initializes a Fifo Cyclic Buffer to point to some allocated memory.
 */
void phFriNfc_Llcp_CyclicFifoInit(P_UTIL_FIFO_BUFFER   pUtilFifo,
                                  const uint8_t        *pBuffStart,
                                  uint32_t             buffLength)
{
   pUtilFifo->pBuffStart = (uint8_t *)pBuffStart;
   pUtilFifo->pBuffEnd   = (uint8_t *)pBuffStart + buffLength-1;
   pUtilFifo->pIn        = (uint8_t *)pBuffStart;
   pUtilFifo->pOut       = (uint8_t *)pBuffStart;
   pUtilFifo->bFull      = FALSE;
}

/**
 * Clears the Fifo Cyclic Buffer - loosing any data that was in it.
 */
void phFriNfc_Llcp_CyclicFifoClear(P_UTIL_FIFO_BUFFER pUtilFifo)
{
   pUtilFifo->pIn = pUtilFifo->pBuffStart;
   pUtilFifo->pOut = pUtilFifo->pBuffStart;
   pUtilFifo->bFull      = FALSE;
}

/**
 * Attempts to write dataLength bytes to the specified Fifo Cyclic Buffer.
 */
uint32_t phFriNfc_Llcp_CyclicFifoWrite(P_UTIL_FIFO_BUFFER   pUtilFifo,
                                       uint8_t              *pData,
                                       uint32_t             dataLength)
{
   uint32_t dataLengthWritten = 0;
   uint8_t * pNext;

   while(dataLengthWritten < dataLength)
   {
      pNext = (uint8_t*)pUtilFifo->pIn+1;

      if(pNext > pUtilFifo->pBuffEnd)
      {
         //Wrap around
         pNext = pUtilFifo->pBuffStart;
      }

      if(pUtilFifo->bFull)
      {
         //Full
         break;
      }

      if(pNext == pUtilFifo->pOut)
      {
         // Trigger Full flag
         pUtilFifo->bFull = TRUE;
      }

      dataLengthWritten++;
      *pNext = *pData++;
      pUtilFifo->pIn = pNext;
   }

   return dataLengthWritten;
}

/**
 * Attempts to read dataLength bytes from the specified  Fifo Cyclic Buffer.
 */
uint32_t phFriNfc_Llcp_CyclicFifoFifoRead(P_UTIL_FIFO_BUFFER   pUtilFifo,
                                          uint8_t              *pBuffer,
                                          uint32_t             dataLength)
{
   uint32_t  dataLengthRead = 0;
   uint8_t * pNext;

   while(dataLengthRead < dataLength)
   {
      if((pUtilFifo->pOut == pUtilFifo->pIn) && (pUtilFifo->bFull == FALSE))
      {
         //No more bytes in buffer
         break;
      }
      else
      {
         dataLengthRead++;

         if(pUtilFifo->pOut == pUtilFifo->pBuffEnd)
         {
            /* Wrap around */
            pNext = pUtilFifo->pBuffStart;
         }
         else
         {
            pNext = (uint8_t*)pUtilFifo->pOut + 1;
         }

         *pBuffer++ = *pNext;

         pUtilFifo->pOut = pNext;

         pUtilFifo->bFull = FALSE;
      }
   }

   return dataLengthRead;
}

/**
 * Returns the number of bytes currently stored in Fifo Cyclic Buffer.
 */
uint32_t phFriNfc_Llcp_CyclicFifoUsage(P_UTIL_FIFO_BUFFER pUtilFifo)
{
   uint32_t dataLength;
   uint8_t * pIn        = (uint8_t *)pUtilFifo->pIn;
   uint8_t * pOut       = (uint8_t *)pUtilFifo->pOut;

   if (pUtilFifo->bFull)
   {
      dataLength = pUtilFifo->pBuffEnd - pUtilFifo->pBuffStart + 1;
   }
   else
   {
      if(pIn >= pOut)
      {
         dataLength = pIn - pOut;
      }
      else
      {
         dataLength = pUtilFifo->pBuffEnd - pOut;
         dataLength += (pIn+1) - pUtilFifo->pBuffStart;
      }
   }

   return dataLength;
}


/**
 * Returns the available room for writing in Fifo Cyclic Buffer.
 */
uint32_t phFriNfc_Llcp_CyclicFifoAvailable(P_UTIL_FIFO_BUFFER pUtilFifo)
{
   uint32_t dataLength;
   uint32_t  size;
   uint8_t * pIn         = (uint8_t *)pUtilFifo->pIn;
   uint8_t * pOut        = (uint8_t *)pUtilFifo->pOut;

   if (pUtilFifo->bFull)
   {
      dataLength = 0;
   }
   else
   {
      if(pIn >= pOut)
      {
         size = pUtilFifo->pBuffEnd - pUtilFifo->pBuffStart + 1;
         dataLength = size - (pIn - pOut);
      }
      else
      {
         dataLength = pOut - pIn;
      }
   }

   return dataLength;
}



uint32_t phFriNfc_Llcp_Header2Buffer( phFriNfc_Llcp_sPacketHeader_t *psHeader, uint8_t *pBuffer, uint32_t nOffset )
{
   uint32_t nOriginalOffset = nOffset;
   pBuffer[nOffset++] = (uint8_t)((psHeader->dsap << 2)  | (psHeader->ptype >> 2));
   pBuffer[nOffset++] = (uint8_t)((psHeader->ptype << 6) | psHeader->ssap);
   return nOffset - nOriginalOffset;
}

uint32_t phFriNfc_Llcp_Sequence2Buffer( phFriNfc_Llcp_sPacketSequence_t *psSequence, uint8_t *pBuffer, uint32_t nOffset )
{
   uint32_t nOriginalOffset = nOffset;
   pBuffer[nOffset++] = (uint8_t)((psSequence->ns << 4) | (psSequence->nr));
   return nOffset - nOriginalOffset;
}

uint32_t phFriNfc_Llcp_Buffer2Header( uint8_t *pBuffer, uint32_t nOffset, phFriNfc_Llcp_sPacketHeader_t *psHeader )
{
   psHeader->dsap  = (pBuffer[nOffset] & 0xFC) >> 2;
   psHeader->ptype = ((pBuffer[nOffset]  & 0x03) << 2) | ((pBuffer[nOffset+1] & 0xC0) >> 6);
   psHeader->ssap  = pBuffer[nOffset+1] & 0x3F;
   return PHFRINFC_LLCP_PACKET_HEADER_SIZE;
}

uint32_t phFriNfc_Llcp_Buffer2Sequence( uint8_t *pBuffer, uint32_t nOffset, phFriNfc_Llcp_sPacketSequence_t *psSequence )
{
   psSequence->ns = pBuffer[nOffset] >> 4;
   psSequence->nr = pBuffer[nOffset] & 0x0F;
   return PHFRINFC_LLCP_PACKET_SEQUENCE_SIZE;
}