/* ticket.c - Ticket 구조체를 처리하는 파일입니다.
   Copyright (C) 2006 Weongyo Jeong (weongyo@gmail.com)

This file is part of ROVM.

ROVM is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
version.

ROVM is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.  */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "types.h"
#include "mpool.h"
#include "tktree.h"
#include "clstree.h"
#include "sha1.h"
#include "listen.h"
#include "rovm.h"

#include "thread.h"
#include "thread_mutex.h"
#include "thread_cond.h"

#include "mpm_worker_fdqueue.h"
#include "mpm_worker_pod.h"
#include "mpm_worker.h"

#include "utils.h"
#include "connection.h"
#include "request.h"

#include "ticket.h"
#include "log.h"

void *
safe_tktree_lookup (r, sp, key)
     struct rovm *r;
     tktree_t *sp;
     char *key;
{
  void *ret;
  rc_thread_mutex_lock (ROVM_TKMUTEX (r));
  ret = tktree_lookup (sp, key);
  rc_thread_mutex_unlock (ROVM_TKMUTEX (r));
  return ret;
}

void
safe_tktree_remove (r, sp, key)
     struct rovm *r;
     tktree_t *sp;
     char *key;
{
  rc_thread_mutex_lock (ROVM_TKMUTEX (r));
  tktree_remove (sp, key);
  rc_thread_mutex_unlock (ROVM_TKMUTEX (r));
}

int
safe_tktree_insert (r, sp, key, value)
     struct rovm *r;
     tktree_t *sp;
     char *key;
     void *value;
{
  int ret;
  rc_thread_mutex_lock (ROVM_TKMUTEX (r));
  ret = tktree_insert (sp, key, value);
  rc_thread_mutex_unlock (ROVM_TKMUTEX (r));
  return ret;
}

int
safe_tktree_walking (r, sp, fn, data)
     struct rovm *r;
     tktree_t *sp;
     tktree_foreach_fn fn;
     void *data;
{
  int ret;
  rc_thread_mutex_lock (ROVM_TKMUTEX (r));
  ret = tktree_walking (sp, fn, data);
  rc_thread_mutex_unlock (ROVM_TKMUTEX (r));
  return ret;
}

/**
   Ticket Delay Slot 을 깨끗히 초기화합니다.

   @param tk    Ticket 구조
 */
void
ticket_clear_delay_slot (tk)
     struct ticket *tk;
{
  if (!tk)
    return;

  TICKET_DELAYED_SLOT_NELM (tk) = 0;
}

/**
   struct ticket 구조체 변수 TK 를 본격적으로 사용하기 전에 설정해줄 필요성이
   있는 항목에 대해서 설정할 수 있도록 배려한다.

   @param tk    설정할 TK 변수
   @return      정상 완료시 0 을 그렇지 않을 경우 -1 을 반환한다.
 */
int
ticket_pre_config (tk)
     struct ticket *tk;
{
  /* 현재 Ticket 이 사용중임을 나타냅니다.  상태가 `일하는중' 일 경우, 최근 사용
     기록과 상관없이 GC 되지 않습니다.  */
  TICKET_STATUS (tk) = TICKET_IS_WORKING;

  /* 가장 최근 사용된 Ticket 의 시각을 업데이트합니다.  이 값은 Ticket 의 GC 가
     이루어지는데, 많은 영향을 미칩니다.  */
  TICKET_TIME_UPDATE (tk) = rc_time_now ();

  /*
    struct ticket 구조체의 구성 요소인 RET 를 초기화한다.  이러한 초기화가 가능한
    이유는 항상 request `OPCODE' 를 처리하고 난 후, 그에 대한 return 값을 요청한
    client 에게 전송해 주기 때문에 return 값을 유지할 필요가 없기 때문이다.
  */
  STACK_TYPE (TICKET_RET (tk)) = STACK_TYPE_VOID;

  return 0;
}

/**
   Ticket 구조체의 사용이 다 끝났을 호출되는 함수입니다.
 */
int
ticket_post_config (tk)
     struct ticket *tk;
{
  /* 현재 Ticket 이 휴식중임을 나타냅니다.  상태가 `휴식중' 일 경우, 가장 최근 사용
     시간과 더불어 이 Ticket 이 GC 되어 질 수 있음을 의미합니다.  */
  TICKET_STATUS (tk) = TICKET_IS_RELAX;

  /* 가장 최근 사용된 Ticket 의 시각을 업데이트합니다.  이 값은 Ticket 의 GC 가
     이루어지는데, 많은 영향을 미칩니다.

     마지막 과정에서 한번 더 update 를 해줍니다.  함수 실행이 매우 오래 걸린 경우,
     ticket_pre_config () 실행 후 많은 시간이 흘렀을 가능성이 있으며, 이로 인해
     Ticket 구조체가 GC 되어 버리는 불상사를 막기 위해서 입니다.
  */
  TICKET_TIME_UPDATE (tk) = rc_time_now ();

  return 0;
}

/**
   클래스의 method 가 실행되기 이전에, 한번 호출되는 함수로써, struct ticket 구조체의
   구성 요소를 현재 함수 호출에 맞게 설정해 주는 역활을 합니다.  현재 설정되어지는
   값으로는 아래와 같은 것이 있습니다.

     - Local 변수

   @param tk    Ticket 구조체
   @param m     호출이 되는 메쏘드 포인터
   @param argc  메쏘드의 argument count.
   @param argv  메쏘드의 argument value.
   @return      만약 실행 과정에서 오류가 발생하였을 경우 -1 을, 그렇지 않다면
                0 을 반환한다.
 */
int
ticket_beforecall (tk, m, argc, argv)
     struct ticket *tk;
     RvMethod *m;
     u2 argc, *argv;
{
  if (ticket_pre_config (tk))
    return -1;

  TICKET_LOCAL (tk) = TICKET_SP (tk) - ((int) (argc - 1));
  TICKET_MAXLOCAL (tk) = TICKET_SP (tk) + 1;

  return 0;
}

/**
   구조체 ticket 을 생성한다.

   @return 새롭게 생성된 ticket 를 반환한다.
 */
struct ticket *
create_ticket (r)
     struct rovm *r;
{
  struct ticket *t;

  t = (struct ticket *) rc_calloc (1, sizeof (struct ticket));
  if (!t)
    return NULL;

  TICKET_TIME_INIT (t) = rc_time_now ();

  /* 스택 공간을 초기화합니다.  */
  TICKET_MAX_STACK_SLOT (t) = CONF_DEFAULT_STACK_SLOT (ROVM_CONF (r));
  TICKET_STACK (t) = (rovm_stack_t *) rc_calloc (1, sizeof (rovm_stack_t) * TICKET_MAX_STACK_SLOT (t));
  if (!TICKET_STACK (t))
    return NULL;

  /* Stack 포인터 및 Local 변수 포인터를 초기화합니다.  */
  TICKET_SP (t) = &TICKET_STACK_IDX (t, -1);
  TICKET_LOCAL (t) = TICKET_SP (t);
  TICKET_MAXLOCAL (t) = TICKET_SP (t);
  TICKET_MAX_SP (t) = &TICKET_STACK_IDX (t, TICKET_MAX_STACK_SLOT (t));
  TICKET_MIN_SP (t) = &TICKET_STACK_IDX (t, -1);

  /* Delayed Slot 을 초기화한다.  */
  TICKET_DELAYED_SLOT (t) = (void **) rc_calloc (DEFAULT_DELAYED_SLOT, sizeof (void *));
  TICKET_DELAYED_SLOT_MAX (t) = DEFAULT_DELAYED_SLOT;
  TICKET_DELAYED_SLOT_NELM (t) = 0;

  if (ticket_pre_config (t))
    return NULL;

  /* ticket_pre_config () 함수에서 ticket 의 상태가 working 으로 변하기
     때문에, 이를 relax 로 변경해 줍니다.  */
  TICKET_STATUS (t) = TICKET_IS_RELAX;

  return t;
}

/**
   구조체 ticket 가 사용하는 모든 자원을 free 한다.

   @param tk    파괴하고자 하는 tk 구조체 포인터
 */
void
destroy_ticket (tk)
     struct ticket *tk;
{
  if (!tk)
    return;

  rc_free (TICKET_DELAYED_SLOT (tk));
  rc_free (TICKET_STACK (tk));
  rc_free (tk);
}

/**
   Ticket ID 와 Ticket 을 모두 제거한다.

   @param r     ROVM 구조체
   @param tid   제거할 Ticket ID.  Ticket ID 에 연결된 Ticket 또한 함께
                제거된다.
   @note        destroy_ticket () 함수와 단순히 `s' 글자 한자 차이이므로 
                사용시 주의가 필요하다.
 */
int
destroy_tickets (r, tid)
     struct rovm *r;
     char *tid;
{
  safe_tktree_remove (r, ROVM_TKTREE (r), tid);

  return 0;
}

/**
   TICKETID 를 새롭게 생성한 후, 그것을 반환한다.

   @param r     Request 구조체
   @return      새롭게 생성된 ticketid 를 반환한다.
 */
char *
create_ticketid (r)
     request_rec *r;
{
  char *tbuf;

  tbuf = (char *) rc_malloc (TICKETID_SIZE);
  if (!tbuf)
    return NULL;

  if (gen_unique_ticketid (tbuf))
    {
      rc_free (tbuf);
      return NULL;
    }

  return tbuf;
}

/**
   Unique Ticket ID 을 생성하여 발행한다.  이를 생성하기 위해 사용하는 것으로는
   PID 와 현재 시각을 이용하며, SHA1 를 이용하여 unique key 를 생성한다.

   @param r	채널 구조체.
   @param tbuf	발급된 ticket 버퍼가 들어 가야 하는 곳.  이 버퍼의 크기는
                반드시 TICKET_SIZE 크기여야 한다.   
 */
int
gen_unique_ticketid (tbuf)
     char *tbuf;
{
  pid_t pid;
  unsigned short counter;
#define MAX_TICKETSTR	256
  char tmpbuf[MAX_TICKETSTR];

  /* 현재 Unique Ticket 을 만들기 위해서 PID 와 COUNTER 만 사용하고 있다.
     만약 좀 더 세부적으로 구현하기 위해서는 아래와 같은 항목을 추가할 수 있을 것이다.

       - Channel 이 open 된 시각.  (Request 가 들어온 시각)
       - Client IP 주소값  */
  pid = getpid ();
  counter = (unsigned short) (rc_time_usec (rc_time_now ()) / 10);

  /* PID 및 COUNTER 에 의한 문자열 길이가 MAX_TICKETSTR 크기를 넘지 않는다고 
     가정을 한다.  */
  snprintf (tmpbuf, MAX_TICKETSTR, "%d%d", pid, counter);

  rovm_sha1 ((const char *) tmpbuf, strlen (tmpbuf), tbuf);

  return 0;
}

/**
   사용자가 요청한 Ticket ID 를 이용하여 그에 대한 ticket 구조체를 얻어온다.

   @param r     Request 구조체
   @param tbuf  Ticket ID 가 들어있는 버퍼
   @return      TKTREE 에서 찾은 ticket 구조체.  만약 존재하지 않는다면 
                NULL 을 반환한다.
 */
struct ticket *
lookup_ticket (r, tbuf)
     request_rec *r;
     char *tbuf;
{
  struct rovm *rovm = REQUEST_ROVM (r);

  return (struct ticket *) safe_tktree_lookup (rovm, ROVM_TKTREE (rovm), tbuf);
}

/**
   생성된 TBUF 를 전체 ticket tree 구조체에 등록한다.  이렇게 등록된 TBUF 는 나중에 
   이에 해당하는 request 가 왔을 때, 해당 정보를 제공하게 된다.

   즉, 웹 서버의 Session 과 비슷한 역활을 한다고 할 수 있을 것이다.

   @param r	채널 구조체.
   @param tbuf	발급된 ticket 버퍼가 들어 있는 곳.  이 ticket 은 
                gen_unique_ticket () 함수에 의해 생성 되었다.

   @see gen_unique_ticketid
 */
int
register_ticketid (r, tbuf)
     request_rec *r;
     char *tbuf;
{
  int ret = 0;
  struct rovm *rovm = REQUEST_ROVM (r);

  if (!safe_tktree_lookup (rovm, ROVM_TKTREE (rovm), tbuf))
    {
      struct ticket *tk = create_ticket (rovm);

      if (!tk)
        {
          request_add_error (r, ERRLOG_ERR, ERRLOG_MARK, "Failed to create ticket.");
          return -1;
        }

      /* Insert 과 Remove 과정은 Locking 메카니즘이 필요합니다. */
      if (safe_tktree_insert (rovm, ROVM_TKTREE (rovm), tbuf, (void *) tk))
        return -1;
    }

  return ret;
}

#define DEFAULT_TICKET_COLLECTOR_SLOT   1024
struct ticket_collector
{
  struct rovm *r;

  int cur_slot;
  int max_slot;
  tktree_node_t *slot;
};

/**
   Ticket Tree 의 walking 을 수행할 때, 호출되는 callback 함수입니다.
   이 부분에서 사용된지 오래된 Ticket Tree 에 대한 제거 작업이 이루어지게 됩니다.

   만약 반환값이 -1 일 경우, walking 이 멈추게 됨으로 이 점을 유의하면서 코딩을
   해야 한다.

   @param node  해당 node 에 대한 포인터 정보.
 */
int
rovm_ticket_walking_cb (tktree_node_t node, void *arg)
{
  struct ticket_collector *tc = (struct ticket_collector *) arg;
  struct ticket *tk = (struct ticket *) node->value;
  rc_time_t timec = rc_time_now ();

  if (TICKET_STATUS (tk) == TICKET_IS_WORKING)
    return 0;

  if (rc_time_sec (timec - TICKET_TIME_UPDATE (tk)) < DEFAULT_TICKET_EXPIRE_TIME)
    return 0;

  /* 여기에 도달했다는 것은 expire 되어야 하는 Ticket 이 존재한다는 이야기 이다.  */

  /* 티켓이 더 이상 유효하지 않음을 기록합니다.  */
  TICKET_STATUS (tk) = TICKET_IS_ON_GCSLOT;

  if (tc->cur_slot >= tc->max_slot)
    {
      tc->max_slot += DEFAULT_TICKET_COLLECTOR_SLOT;
      tc->slot = (tktree_node_t *) rc_realloc (tc->slot, sizeof (tktree_node_t) * tc->max_slot);
    }

  /* 바로 제거는 못하니까, tc slot 에 쌓아둔다.  바로 제거할 경우, walking 중에 tree 가
     꼬일 수 있습니다.  */
  tc->slot[tc->cur_slot++] = node;

  return 0;
}

/**
   Ticket 구조체에 대한 Garbage Collection 이 이루어질 때 호출되는 함수로써
   각각의 tree node 를 walking 하면서 일정 시간이 지난 Ticket 구조체들을
   제거합니다.

   @param r     ROVM 구조체
   @return      성공시 0, 실패시 -1 을 반환합니다.
 */
int
ticket_collect (r)
     struct rovm *r;
{
  int i;
  struct ticket_collector tc;

  tc.r = r;
  tc.cur_slot = 0;
  tc.max_slot = DEFAULT_TICKET_COLLECTOR_SLOT;
  tc.slot = (tktree_node_t *) rc_malloc (sizeof (tktree_node_t) * tc.max_slot);

  /* 체크 */
  safe_tktree_walking (r, ROVM_TKTREE (r), rovm_ticket_walking_cb, (void *) &tc);

  /* 제거 */
  for (i=0; i<tc.cur_slot; i++)
    destroy_tickets (r, (char *) (tc.slot[i]->key));

  rc_free (tc.slot);

  return 0;
}
