/*  -
   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"

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

   @param tk    설정할 TK 변수
   @return      정상 완료시 0 을 그렇지 않을 경우 -1 을 반환한다.
 */
int
ticket_pre_config (tk)
     struct ticket *tk;
{
  /*
    struct ticket 구조체의 구성 요소인 RET 를 초기화한다.  이러한 초기화가 가능한
    이유는 항상 request `OPCODE' 를 처리하고 난 후, 그에 대한 return 값을 요청한
    client 에게 전송해 주기 때문에 return 값을 유지할 필요가 없기 때문이다.
  */
  STACK_TYPE (TICKET_RET (tk)) = STACK_TYPE_VOID;

  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;
{
  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 (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;

  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);

  if (ticket_pre_config (t))
    return NULL;

  return t;
}

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

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

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

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

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

  tktree_remove (ROVM_TKTREE (rovm), 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 = CONN_TS (REQUEST_CONN (r))->arg;

  return (struct ticket *) tktree_lookup (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 = CONN_TS (REQUEST_CONN (r))->arg;

  if (!tktree_lookup (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 과정이므로 이에 대한 serialize (한줄 세우기) 과정이 필요하다. */
      rc_thread_mutex_lock (ROVM_MUTEX (rovm));

      if (tktree_insert (ROVM_TKTREE (rovm), tbuf, (void *) tk))
        {
          rc_thread_mutex_unlock (ROVM_MUTEX (rovm));
          return -1;
        }

      rc_thread_mutex_unlock (ROVM_MUTEX (rovm));
    }

  return ret;
}
