/*
 *  Copyright (C) 2011-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "WebSocket.h"

#include "utils/EndianSwap.h"
#include "utils/HttpParser.h"
#include "utils/StringUtils.h"
#include "utils/log.h"

#include <sstream>
#include <string>

#define MASK_FIN      0x80
#define MASK_RSV1     0x40
#define MASK_RSV2     0x20
#define MASK_RSV3     0x10
#define MASK_RSV      (MASK_RSV1 | MASK_RSV2 | MASK_RSV3)
#define MASK_OPCODE   0x0F
#define MASK_MASK     0x80
#define MASK_LENGTH   0x7F

#define CONTROL_FRAME 0x08

#define LENGTH_MIN    0x2

CWebSocketFrame::CWebSocketFrame(const char* data, uint64_t length)
{
  reset();

  if (data == NULL || length < LENGTH_MIN)
    return;

  m_free = false;
  m_data = data;
  m_lengthFrame = length;

  // Get the FIN flag
  m_final = ((m_data[0] & MASK_FIN) == MASK_FIN);
  // Get the RSV1 - RSV3 flags
  m_extension |= m_data[0] & MASK_RSV1;
  m_extension |= (m_data[0] & MASK_RSV2) << 1;
  m_extension |= (m_data[0] & MASK_RSV3) << 2;
  // Get the opcode
  m_opcode = (WebSocketFrameOpcode)(m_data[0] & MASK_OPCODE);
  if (m_opcode >= WebSocketUnknownFrame)
  {
    CLog::Log(LOGINFO, "WebSocket: Frame with invalid opcode %2X received", m_opcode);
    reset();
    return;
  }
  if ((m_opcode & CONTROL_FRAME) == CONTROL_FRAME && !m_final)
  {
    CLog::Log(LOGINFO, "WebSocket: Fragmented control frame (opcode %2X) received", m_opcode);
    reset();
    return;
  }

  // Get the MASK flag
  m_masked = ((m_data[1] & MASK_MASK) == MASK_MASK);

  // Get the payload length
  m_length = (uint64_t)(m_data[1] & MASK_LENGTH);
  if ((m_length <= 125 && m_lengthFrame  < m_length + LENGTH_MIN) ||
      (m_length == 126 && m_lengthFrame < LENGTH_MIN + 2) ||
      (m_length == 127 && m_lengthFrame < LENGTH_MIN + 8))
  {
    CLog::Log(LOGINFO, "WebSocket: Frame with invalid length received");
    reset();
    return;
  }

  if (IsControlFrame() && (m_length > 125 || !m_final))
  {
    CLog::Log(LOGWARNING, "WebSocket: Invalid control frame received");
    reset();
    return;
  }

  int offset = 0;
  if (m_length == 126)
  {
    m_length = (uint64_t)Endian_SwapBE16(*(const uint16_t *)(m_data + 2));
    offset = 2;
  }
  else if (m_length == 127)
  {
    m_length = Endian_SwapBE64(*(const uint64_t *)(m_data + 2));
    offset = 8;
  }

  if (m_lengthFrame < LENGTH_MIN + offset + m_length)
  {
    CLog::Log(LOGINFO, "WebSocket: Frame with invalid length received");
    reset();
    return;
  }

  // Get the mask
  if (m_masked)
  {
    m_mask = *(const uint32_t *)(m_data + LENGTH_MIN + offset);
    offset += 4;
  }

  if (m_lengthFrame != LENGTH_MIN + offset + m_length)
    m_lengthFrame = LENGTH_MIN + offset + m_length;

  // Get application data
  if (m_length > 0)
    m_applicationData = const_cast<char *>(m_data + LENGTH_MIN + offset);
  else
    m_applicationData = NULL;

  // Unmask the application data if necessary
  if (m_masked)
  {
    for (uint64_t index = 0; index < m_length; index++)
      m_applicationData[index] = m_applicationData[index] ^ ((char *)(&m_mask))[index % 4];
  }

  m_valid = true;
}

CWebSocketFrame::CWebSocketFrame(WebSocketFrameOpcode opcode, const char* data /* = NULL */, uint32_t length /* = 0 */,
                                 bool final /* = true */, bool masked /* = false */, int32_t mask /* = 0 */, int8_t extension /* = 0 */)
{
  reset();

  if (opcode >= WebSocketUnknownFrame)
    return;

  m_free = true;
  m_opcode = opcode;

  m_length = length;

  m_masked = masked;
  m_mask = mask;
  m_final = final;
  m_extension = extension;

  std::string buffer;
  char dataByte = 0;

  // Set the FIN flag
  if (m_final)
    dataByte |= MASK_FIN;

  // Set RSV1 - RSV3 flags
  if (m_extension != 0)
    dataByte |= (m_extension << 4) & MASK_RSV;

  // Set opcode flag
  dataByte |= opcode & MASK_OPCODE;

  buffer.push_back(dataByte);
  dataByte = 0;

  // Set MASK flag
  if (m_masked)
    dataByte |= MASK_MASK;

  // Set payload length
  if (m_length < 126)
  {
    dataByte |= m_length & MASK_LENGTH;
    buffer.push_back(dataByte);
  }
  else if (m_length <= 65535)
  {
    dataByte |= 126 & MASK_LENGTH;
    buffer.push_back(dataByte);

    uint16_t dataLength = Endian_SwapBE16((uint16_t)m_length);
    buffer.append((const char*)&dataLength, 2);
  }
  else
  {
    dataByte |= 127 & MASK_LENGTH;
    buffer.push_back(dataByte);

    uint64_t dataLength = Endian_SwapBE64(m_length);
    buffer.append((const char*)&dataLength, 8);
  }

  uint64_t applicationDataOffset = 0;
  if (data)
  {
    // Set masking key
    if (m_masked)
    {
      buffer.append((char *)&m_mask, sizeof(m_mask));
      applicationDataOffset = buffer.size();

      for (uint64_t index = 0; index < m_length; index++)
        buffer.push_back(data[index] ^ ((char *)(&m_mask))[index % 4]);
    }
    else
    {
      applicationDataOffset = buffer.size();
      buffer.append(data, (unsigned int)length);
    }
  }

  // Get the whole data
  m_lengthFrame = buffer.size();
  m_data = new char[(uint32_t)m_lengthFrame];
  memcpy(const_cast<char *>(m_data), buffer.c_str(), (uint32_t)m_lengthFrame);

  if (data)
  {
    m_applicationData = const_cast<char *>(m_data);
    m_applicationData += applicationDataOffset;
  }

  m_valid = true;
}

CWebSocketFrame::~CWebSocketFrame()
{
  if (!m_valid)
    return;

  if (m_free && m_data != NULL)
  {
    delete[] m_data;
    m_data = NULL;
  }
}

void CWebSocketFrame::reset()
{
  m_free = false;
  m_data = NULL;
  m_lengthFrame = 0;
  m_length = 0;
  m_valid = false;
  m_final = false;
  m_extension = 0;
  m_opcode = WebSocketUnknownFrame;
  m_masked = false;
  m_mask = 0;
  m_applicationData = NULL;
}

CWebSocketMessage::CWebSocketMessage()
{
  Clear();
}

CWebSocketMessage::~CWebSocketMessage()
{
  for (unsigned int index = 0; index < m_frames.size(); index++)
    delete m_frames[index];

  m_frames.clear();
}

bool CWebSocketMessage::AddFrame(const CWebSocketFrame *frame)
{
  if (!frame->IsValid() || m_complete)
    return false;

  if (frame->IsFinal())
    m_complete = true;
  else
    m_fragmented = true;

  m_frames.push_back(frame);

  return true;
}

void CWebSocketMessage::Clear()
{
  m_fragmented = false;
  m_complete = false;

  m_frames.clear();
}

const CWebSocketMessage* CWebSocket::Handle(const char* &buffer, size_t &length, bool &send)
{
  send = false;

  while (length > 0)
  {
    switch (m_state)
    {
      case WebSocketStateConnected:
      {
        CWebSocketFrame *frame = GetFrame(buffer, length);
        if (!frame->IsValid())
        {
          CLog::Log(LOGINFO, "WebSocket: Invalid frame received");
          delete frame;
          return NULL;
        }

        // adjust the length and the buffer values
        length -= (size_t)frame->GetFrameLength();
        buffer += frame->GetFrameLength();

        if (frame->IsControlFrame())
        {
          if (!frame->IsFinal())
          {
            delete frame;
            return NULL;
          }

          CWebSocketMessage *msg = NULL;
          switch (frame->GetOpcode())
          {
            case WebSocketPing:
              msg = GetMessage();
              if (msg != NULL)
                msg->AddFrame(Pong(frame->GetApplicationData()));
              break;

            case WebSocketConnectionClose:
              CLog::Log(LOGINFO, "WebSocket: connection closed by client");

              msg = GetMessage();
              if (msg != NULL)
                msg->AddFrame(Close());

              m_state = WebSocketStateClosed;
              break;

            case WebSocketContinuationFrame:
            case WebSocketTextFrame:
            case WebSocketBinaryFrame:
            case WebSocketPong:
            case WebSocketUnknownFrame:
            default:
              break;
          }

          delete frame;

          if (msg != NULL)
            send = true;

          return msg;
        }

        if (m_message == NULL && (m_message = GetMessage()) == NULL)
        {
          CLog::Log(LOGINFO, "WebSocket: Could not allocate a new websocket message");
          delete frame;
          return NULL;
        }

        m_message->AddFrame(frame);
        if (!m_message->IsComplete())
        {
          if (length > 0)
            continue;
          else
            return NULL;
        }

        CWebSocketMessage *msg = m_message;
        m_message = NULL;
        return msg;
      }

      case WebSocketStateClosing:
      {
        CWebSocketFrame *frame = GetFrame(buffer, length);

        if (frame->IsValid())
        {
          // adjust the length and the buffer values
          length -= (size_t)frame->GetFrameLength();
          buffer += frame->GetFrameLength();
        }

        if (!frame->IsValid() || frame->GetOpcode() == WebSocketConnectionClose)
        {
          CLog::Log(LOGINFO, "WebSocket: Invalid or unexpected frame received (only closing handshake expected)");
          delete frame;
          return NULL;
        }

        m_state = WebSocketStateClosed;
        return NULL;
      }

      case WebSocketStateNotConnected:
      case WebSocketStateClosed:
      case WebSocketStateHandshaking:
      default:
        CLog::Log(LOGINFO, "WebSocket: No frame expected in the current state");
        return NULL;
    }
  }

  return NULL;
}

const CWebSocketMessage* CWebSocket::Send(WebSocketFrameOpcode opcode, const char* data /* = NULL */, uint32_t length /* = 0 */)
{
  CWebSocketFrame *frame = GetFrame(opcode, data, length);
  if (frame == NULL || !frame->IsValid())
  {
    CLog::Log(LOGINFO, "WebSocket: Trying to send an invalid frame");
    return NULL;
  }

  CWebSocketMessage *msg = GetMessage();
  if (msg == NULL)
  {
    CLog::Log(LOGINFO, "WebSocket: Could not allocate a message");
    return NULL;
  }

  msg->AddFrame(frame);
  if (msg->IsComplete())
    return msg;

  return NULL;
}
