/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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.
 */

#include "P_QUICNetVConnection.h"
#include "P_QUICNet.h"
#include "P_UDPNet.h"
#include "P_UnixNet.h"
#include "iocore/net/NetHandler.h"
#include "iocore/net/UDPPacket.h"
#include "tscore/ink_atomic.h"
#include <quiche.h>

ClassAllocator<QUICPollEvent, false> quicPollEventAllocator("quicPollEvent");

void
QUICPollEvent::init(QUICConnection *con, UDPPacket *packet)
{
  this->con    = con;
  this->packet = packet;
  if (con != nullptr) {
    static_cast<QUICNetVConnection *>(con)->refcount_inc();
  }
}

void
QUICPollEvent::free()
{
  if (this->con != nullptr) {
    ink_assert(static_cast<QUICNetVConnection *>(this->con)->refcount_dec() >= 0);
    this->con = nullptr;
  }

  quicPollEventAllocator.free(this);
}

QUICPollCont::QUICPollCont(Ptr<ProxyMutex> &m) : Continuation(m.get()), net_handler(nullptr)
{
  SET_HANDLER(&QUICPollCont::pollEvent);
}

QUICPollCont::QUICPollCont(Ptr<ProxyMutex> &m, NetHandler *nh) : Continuation(m.get()), net_handler(nh)
{
  SET_HANDLER(&QUICPollCont::pollEvent);
}

QUICPollCont::~QUICPollCont() {}

#if TS_HAS_QUICHE
void
QUICPollCont::_process_packet(QUICPollEvent *e, NetHandler *nh)
{
  UDPPacket          *p  = e->packet;
  QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(e->con);

  vc->read.triggered = 1;
  vc->handle_received_packet(p);

  // Push QUICNetVC into nethandler's enabled list
  int isin = ink_atomic_swap(&vc->read.in_enabled_list, 1);
  if (!isin) {
    nh->read_enable_list.push(vc);
  }

  // Note: We should free QUICPollEvent here since vc could be freed from other thread.
  p->free();
  e->free();
}
#else
void
QUICPollCont::_process_long_header_packet(QUICPollEvent *e, NetHandler *nh)
{
  UDPPacket *p = e->packet;
  // FIXME: VC is nullptr ?
  QUICNetVConnection *vc  = static_cast<QUICNetVConnection *>(e->con);
  uint8_t            *buf = reinterpret_cast<uint8_t *>(p->getIOBlockChain()->buf());

  QUICPacketType ptype;
  QUICLongHeaderPacketR::type(ptype, buf, 1);
  if (ptype == QUICPacketType::INITIAL && !vc->read.triggered) {
    SCOPED_MUTEX_LOCK(lock, vc->mutex, this_ethread());
    vc->read.triggered = 1;
    vc->handle_received_packet(p);
    vc->handleEvent(QUIC_EVENT_PACKET_READ_READY, nullptr);
    e->free();

    return;
  }

  if (vc) {
    SCOPED_MUTEX_LOCK(lock, vc->mutex, this_ethread());
    vc->read.triggered = 1;
    vc->handle_received_packet(p);
  } else {
    this->_longInQueue.push(p);
  }

  // Push QUICNetVC into nethandler's enabled list
  if (vc != nullptr) {
    int isin = ink_atomic_swap(&vc->read.in_enabled_list, 1);
    if (!isin) {
      nh->read_enable_list.push(vc);
    }
  }

  // Note: We should free QUICPollEvent here since vc could be freed from other thread.
  e->free();
}

void
QUICPollCont::_process_short_header_packet(QUICPollEvent *e, NetHandler *nh)
{
  UDPPacket          *p  = e->packet;
  QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(e->con);

  vc->read.triggered = 1;
  vc->handle_received_packet(p);

  // Push QUICNetVC into nethandler's enabled list
  int isin = ink_atomic_swap(&vc->read.in_enabled_list, 1);
  if (!isin) {
    nh->read_enable_list.push(vc);
  }

  // Note: We should free QUICPollEvent here since vc could be freed from other thread.
  e->free();
}
#endif

//
// QUICPollCont continuation which traverse the inQueue(ASLL)
// and create new QUICNetVC for Initial Packet,
// and push the triggered QUICNetVC into enable list.
//
int
QUICPollCont::pollEvent(int, Event *)
{
  ink_assert(this->mutex->thread_holding == this_thread());
  QUICPollEvent *e;
  NetHandler    *nh = get_NetHandler(this->mutex->thread_holding);

  // Process the ASLL
  SList(QUICPollEvent, alink) aq(inQueue.popall());
  Queue<QUICPollEvent> result;
  while ((e = aq.pop())) {
    QUICNetVConnection *qvc = static_cast<QUICNetVConnection *>(e->con);
    UDPPacket          *p   = e->packet;
    if (qvc != nullptr && qvc->in_closed_queue) {
      p->free();
      e->free();
      continue;
    }
    result.push(e);
  }

  while ((e = result.pop())) {
#if TS_HAS_QUICHE
    this->_process_packet(e, nh);
#else
    uint8_t *buf;
    buf = reinterpret_cast<uint8_t *>(e->packet->getIOBlockChain()->buf());
    if (QUICInvariants::is_long_header(buf)) {
      // Long Header Packet with Connection ID, has a valid type value.
      this->_process_long_header_packet(e, nh);
    } else {
      // Short Header Packet with Connection ID, has a valid type value.
      this->_process_short_header_packet(e, nh);
    }
#endif
  }

  return EVENT_CONT;
}

void
initialize_thread_for_quic_net(EThread *thread)
{
  NetHandler   *nh     = get_NetHandler(thread);
  QUICPollCont *quicpc = get_QUICPollCont(thread);

  new (reinterpret_cast<ink_dummy_for_new *>(quicpc)) QUICPollCont(thread->mutex, nh);

  thread->schedule_every(quicpc, -HRTIME_MSECONDS(UDP_PERIOD));
}
