/* request.c - 사용자 요청이 시작되는 곳입니다.
   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 "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 "connection.h"
#include "request.h"

#include "utils.h"
#include "proc_rc.h"
#include "log.h"
#include "ticket.h"

#include "el.h"

/**
   Request R 에 버퍼 S 의 길이 L 만큼 보낸다.

   @param r     Reqeust 구조체
   @param s     버퍼 S
   @param l     보낼 길이
   @return      보낸 길이
 */
int
request_writen (r, s, l)
     request_rec *r;
     char *s;
     rc_size_t l;
{
  rc_status_t rv;
  int sent = l;

  rv = rc_socket_send (r->conn->sock, (const char *) s, &sent);

  return sent;
}

/**
   Request R 에 버퍼 S 의 길이 L 만큼 받는다.

   @param r     Reqeust 구조체
   @param s     버퍼 S
   @param l     받을 길이
   @return      받은 길이
 */
int
request_readn (r, s, l)
     request_rec *r;
     char *s;
     rc_size_t l;
{
  rc_status_t rv;
  int recv = l;

  rv = rc_socket_recv (r->conn->sock, s, &recv);

  return recv;
}

/**
   Request 소켓에서 LEN 길이만큼 읽어서 BUF 에 쓴다.

   @param r     Request 소켓
   @param buf   사용할 버퍼
   @param len   읽을 크기
   @return      읽은 크기
 */
int
request_read_buf (r, buf, len)
     request_rec *r;
     char *buf;
     rc_size_t len;
{
  if (!r || !buf || len < 0)
    return -1;

  return request_readn (r, buf, len);
}


/**
   Request 소켓에서 uint32_t 타입만큼 읽어서 VAR 에 쓰게 된다.  하지만 이것의
   특징은 네트워크 ordering 을 호스트 ordering 으로 바꾸게 된다.

   @param r     Request 소켓
   @param var   쓸 변수 공간
   @return      읽은 크기
 */
int
request_read_uint32 (r, var)
     request_rec *r;
     uint32_t *var;
{
  uint32_t space;
  int recv;

  if (!r || !var)
    return -1;

  recv = request_readn (r, (char *) &space, sizeof (uint32_t));
  if (recv != sizeof (uint32_t))
    return recv;

  *var = ntohl (space);

  return recv;
}

/**
   Request 통신에 1 바이트 char 크기 만큼 상대방에서 보낸다.

   @param r	Request 구조체
   @param c	보낼 char 바이트.  (TODO: char 는 type 은 각각의 유닉스마다
		크기가 다를 수 있기 때문에 나중에 이것에 대한 수정 사항이 발생
		할 수 있을 것이다.
 */
void
request_write_char (r, c)
     request_rec *r;
     char c;
{
  int ret;

  if (!r)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_char: r is NULL.");
      return;
    }

  ret = request_writen (r, &c, sizeof (char));
  if (ret != sizeof (char))
    rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_char: error.");
}

/**
   Request 통신에 2 바이트 unsigned short 크기 만큼 상대방에서 보낸다.

   @param r	Request 구조체
   @param s	보낼 unsigned short 바이트.
 */
void
request_write_ushort (r, s)
     request_rec *r;
     uint16_t s;
{
  int ret;

  if (!r)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_char: r is NULL.");
      return;
    }

  s = htons (s);

  ret = request_writen (r, (char *) &s, sizeof (uint16_t));
  if (ret != sizeof (uint16_t))
    rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_ushort: error.");
}

/**
   Request 통신에 4 바이트 unsigned int 크기 만큼 상대방에서 보낸다.

   @param r	Request 구조체
   @param s	보낼 unsigned int 바이트.
 */
void
request_write_uint (r, i)
     request_rec *r;
     uint32_t i;
{
  int ret;

  if (!r)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_char: r is NULL.");
      return;
    }

  i = htonl (i);

  ret = request_writen (r, (char *) &i, sizeof (uint32_t));
  if (ret != sizeof (uint32_t))
    rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_ushort: error.");
}

/**
   Request 통신에 문자열 S 를 상대방에서 보낸다.

   @param r	Request 구조체
   @param s	보낼 문자열 포인터.  반드시 \0 로 끝나야 한다.
 */
void
request_write_string (r, s)
     request_rec *r;
     char *s;
{
  int ret, sl;

  if (!r || !s)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_char: error.");
      return;
    }

  sl = strlen (s);

  ret = request_writen (r, s, sl);
  if (ret != sl)
    rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_string: error.");
}

/**
   Request 통신에 버퍼 S 를 SL 크기 만큼 상대방에서 보낸다.

   @param r	Request 구조체
   @param s	보낼 버퍼 포인터.
   @param sl	보낼 버퍼 크기.
 */
void
request_write_buf (r, s, sl)
     request_rec *r;
     char *s;
     int sl;
{
  int ret;

  if (!r || !s || sl < 0)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_char: error.");
      return;
    }

  ret = request_writen (r, s, sl);
  if (ret != sl)
    rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_write_buf: error.");
}

/**
   Request 구조체 R 에 존재하는 오류 메세지의 갯수를 계산하여 상대방에게 전달한다.
   이 메세지 크기는 2 바이트 크기이며, type 은 unsigned short 이다.

   @param r	Request Info Structure
 */
void
request_send_errorcount (r)
     request_rec *r;
{
  uint16_t count = 0;
  struct request_errors *e;

  if (!r)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "request_send_errorsize: r is NULL");
      return;
    }

  e = REQUEST_ERRORS (r);
  while (e)
    {
      count++;
      e = ERRORS_NEXT (e);
    }

  request_write_ushort (r, count);
}

/**
   Request 구조체 R 에 들어있는 오류 메세지들을 각각 상대방에게 전달한다.

   @param r	Request Info Structure
 */
void
request_send_eacherror (r)
     request_rec *r;
{
  struct request_errors *e;

  if (!r)
    {
      rovm_rlog (r, ROVMLOG_ERR, ROVMLOG_MARK, "request_send_eacherror: r is NULL");
      return;
    }

  e = REQUEST_ERRORS (r);

  while (e)
    {
      unsigned short sl;

      if (!ERRORS_STRING (e))
	{
	  rovm_rlog (r, ROVMLOG_ERR, ROVMLOG_MARK, "request_send_eacherror: str is NULL");
	  break;
	}

      sl = (unsigned short) strlen (ERRORS_STRING (e));

      request_write_char (r, ERRORS_LEVEL (e));
      request_write_ushort (r, sl);
      request_write_string (r, ERRORS_STRING (e));

      e = ERRORS_NEXT (e);
    }
}

/**
   Error 메세지를 원격 호스트에 보낸다.  오류 내용은 R 에 기록되어 
   있다고 가정을 한다.

   @param r	Request Info Structure
 */
void
request_send_error (r)
     request_rec *r;
{
  request_write_char (r, COMMAND_ERROR);
  request_send_errorcount (r);
  request_send_eacherror (r);
}

/**
   Connection R 에 struct connection_errors 구조체를 할당하여 오류 메세지를 기록한다.
   이 함수를 직접적으로 사용하지 마세요.  대신 connection_add_error () 함수를 사용하시기
   바랍니다.

   @param r		Connection 정보 구조체
   @param level		현재 오류 메세지의 위험도 등급
   @param errstr	vprintf 포맷 output 이 완료된 메세지.
 */
static int
request_add_error_aline (r, level, errstr)
     request_rec *r;
     int level;
     char *errstr;
{
  struct request_errors *e;

  if (!r || level < 0 || !errstr)
    return -1;

  e = (struct request_errors *) mp_alloc (REQUEST_POOL (r), sizeof (struct request_errors));
  if (!e)
    return -1;

  ERRORS_LEVEL (e) = level;
  ERRORS_STRING (e) = mp_strdup (REQUEST_POOL (r), errstr);
  ERRORS_NEXT (e) = REQUEST_ERRORS (r);

  REQUEST_ERRORS (r) = e;

  return 0;
}

/**
   connection_add_error () 함수의 실제 수행 함수.

   @param r	Connection 정보 구조체
   @param level 현재 오류 메세지의 위험도 등급
   @param file	메세지가 발생한 파일 이름
   @param line	메세지가 발생한 파일의 라인 번호
   @param fmt	vprintf 관련 포맷
   @param args	vprintf argument format.
 */
int 
request_add_error_core (r, level, file, line, fmt, args)
     request_rec *r;
     int level;
     const char *file;
     int line;
     const char *fmt;
     va_list args;
{
  char errstr[MAX_STRING_LEN];
  size_t len;
  int save_errno = errno;
  
  if (r == NULL)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "connection_add_error_core: r is NULL");
      return -1;
    }
  
  len = snprintf (errstr, sizeof (errstr), "[%s] ", get_current_timestr ());
  
  if (file && (level & ERRLOG_LEVELMASK) == ERRLOG_DEBUG) 
    len += snprintf (errstr + len, sizeof(errstr) - len, "%s(%d): ", file, line);
  
  if ((level & ERRLOG_WITHERRNO) && (save_errno != 0)) 
    len += snprintf (errstr + len, sizeof(errstr) - len, "(%d)%s: ", save_errno, strerror (save_errno));
  
  len += vsnprintf (errstr + len, sizeof (errstr) - len, fmt, args);
  
  /* 위에서 구성한 오류 메세지를 실질적으로 추가한다.  */
  if (request_add_error_aline (r, level, errstr))
    return -1;

  return 0;
}

/**
   Connection 을 통해 발생한 오류 메세지를 상대방에게 전달하기 위해 사용되는 함수.
   이 함수를 이용하여 상대방에게 오류 메세지를 전달할 수 있다.

   @param r	Connection 정보 구조체
   @param level 현재 오류 메세지의 위험도 등급
   @param file	메세지가 발생한 파일 이름
   @param line	메세지가 발생한 파일의 라인 번호
   @param fmt	vprintf 관련 포맷
 */
int 
request_add_error (request_rec *r,
		   int level,
		   const char *file, 
		   int line, 
		   const char *fmt, ...)
{
  va_list args;

  va_start (args, fmt);
  if (request_add_error_core (r, level, file, line, fmt, args))
    {
      va_end (args);
      return -1;
    }

  va_end (args);
  return 0;
}

/**
   하나의 Request 를 생성한다.

   @param c     Connection 구조체.
 */
request_rec *
init_request (c)
     conn_rec *c;
{
  rc_pool_t *p;
  request_rec *r;

  mp_create (&p, c->pool);
  mp_tag (p, "subrequest");
  r = mp_calloc (p, sizeof (request_rec));
  REQUEST_POOL (r) = p;

  r->conn = c;
  
  return r;
}

/**
   Request 를 마무리할 때 호출됩니다.
 */
void
finish_request (r)
     request_rec *r;
{
  /* 아직 할 일이 없습니다.  */
}

extern const char *rovm_stack_name[MAX_STACK_TYPE];
extern int rovm_stack_size[MAX_STACK_TYPE];

/**
   Opcode 들을 처리한 후 설정된 가상 머신의 반환값을 client 측에 전송한다.
 */
int
request_send_return_core (r, tk)
     request_rec *r;
     struct ticket *tk;
{
  char type;
  request_write_char (r, COMMAND_RETURN);

  type = STACK_TYPE (TICKET_RET (tk));

  request_write_char (r, type);

  switch (type)
    {
    case STACK_TYPE_OBJREF:
      /* ObjRef 에 대한 정보는 client 측에 전달되지 않습니다.  */
    case STACK_TYPE_VOID:
      break;
    case STACK_TYPE_ARRAYREF:
      {
        size_t i, arrlen;
        RvObject *obj = GET_STACK_VALUE (OBJREF, TICKET_RET (tk));
        RvValue *entry;

        if (rc_getinfo_array (obj, &entry, &arrlen))
          return -1;

        request_write_uint (r, arrlen);

        for (i=0; i<arrlen; i++)
          {
            RvValue *e = entry + i;

            request_write_char (r, STACK_TYPE (e));

            switch (STACK_TYPE (e))
              {
              case STACK_TYPE_VOID:
                break;
              case STACK_TYPE_STRINGREF:
                {
                  int strlen;
                  char *str;

                  if (rc_getinfo_string (GET_STACK_VALUE (OBJREF, e), &str, &strlen))
                    return -1;

                  request_write_uint (r, strlen);
                  request_write_buf (r, str, strlen);
                  break;
                }
              case STACK_TYPE_INT:
                {
                  int *p = (int *) &GET_STACK_VALUE (INT, e);
                  request_write_buf (r, &p[i], rovm_stack_size[(int) STACK_TYPE (e)]);
                  break;
                }
              default:
                rovm_rlog (r, ROVMLOG_ERR, ROVMLOG_MARK, 
                           "Can't process ROVM's array %s return value", 
                           rovm_stack_name[(int) STACK_TYPE (e)]);
                break;
              }
          }
        break;
      }
    case STACK_TYPE_BOOLEAN:
      request_write_char (r, (char) GET_STACK_VALUE (BOOLEAN, TICKET_RET (tk)));
      break;
    case STACK_TYPE_INT:
      request_write_uint (r, (uint32_t) GET_STACK_VALUE (INT, TICKET_RET (tk)));
      break;
    case STACK_TYPE_STRINGREF:
      {
        int strlen;
        char *str;
        
        if (rc_getinfo_string (GET_STACK_VALUE (OBJREF, TICKET_RET (tk)), &str, &strlen))
          return -1;
        
        request_write_uint (r, strlen);
        request_write_buf (r, str, strlen);
        break;
      }
    default:
      rovm_rlog (r, ROVMLOG_ERR, ROVMLOG_MARK, "Can't process ROVM's %s return value", rovm_stack_name[(int) type]);
      /* COMMAND_RETURN 문법이 깨지면 안되기 때문에, dummy 로 STACK_TYPE_VOID 를 
         전송하여 보낸다.  */
      request_write_char (r, STACK_TYPE_VOID);
      break;
    }

  return 0;
}

/**
   Opcode 를 실행한 결과값이 return 값을 포함하고 있다면, 이를 반환하게 됩니다.

   @param r     ROVM 구조체
   @param tk    Ticket 구조체
 */
int
request_send_return (r, tk)
     request_rec *r;
     struct ticket *tk;
{
  int rv;

  if ((rv = request_send_return_core (r, tk)))
    rovm_rlog (r, ROVMLOG_ERR, ROVMLOG_MARK, 
               "Error occured during processing return value");
  
  /* 모든 return 값을 보냈다면, return 값에 할당되어 있는 메모리 (대부분 GC 메모리
     일 것입니다.) 가 해제될 수 있도록 해야 합니다.  */
  SET_STACK_TYPE (TICKET_RET (tk), STACK_TYPE_VOID);
  ticket_clear_delay_slot (tk);

  return rv;
}

/**
   연결된 Connection 에 대한 실제 처리가 이루어지는 부분입니다.  현재까지의
   모든 정보는 변수 C 를 통해서 접근할 수 있습니다.  여기서부터 읽게 되는
   socket 은 가장 첫부분부터 읽게 됩니다.

   @param c     Connection 이 기록된 구조체.
 */
int
rc_process_request (c)
     conn_rec *c;
{
  int ret;
  char rc;
  request_rec *r;

  r = init_request (c);
  if (!r)
    return -1;

  ret = request_readn (r, &rc, 1);
  if (ret != 1)
    {
      rovm_rlog (r, ROVMLOG_NOTICE, ROVMLOG_MARK, "Connection hang up.");
      return -1;
    }

  /* 모든 네트워크 관련 일은 이 함수내에서 끝내는 것이 좋을 것 같습니다.  그렇지 않을 경우,
     소스에서 네트워크 부분이 상당히 지저분하게 될 것 같아서 입니다.  */
  switch (rc)
    {
    case COMMAND_REQ:
      {
	char *rbuf;

        rbuf = proc_rc_req (r);
        if (!rbuf)
	  {
	    request_send_error (r);
	    goto done;
	  }

	request_write_char (r, COMMAND_TICKET);
	request_write_buf (r, rbuf, TICKETID_SIZE);
	break;
      }
    case COMMAND_REQEND:
      {
        char rbuf[TICKETID_SIZE];

        /* TICKET ID 읽기*/
        ret = request_read_buf (r, rbuf, TICKETID_SIZE);
        if (ret != TICKETID_SIZE)
          return -1;

        if (proc_rc_reqend (r, rbuf))
          {
            request_send_error (r);
	    goto done;
          }

        request_write_char (r, COMMAND_OK);
        break;
      }
    case COMMAND_OPCODE:
      {
        uint32_t oplen;
        char rbuf[TICKETID_SIZE];
        rc_opcode_t *opcode;
        struct ticket *tk;

        /* TICKET ID 읽기*/
        ret = request_read_buf (r, rbuf, TICKETID_SIZE);
        if (ret != TICKETID_SIZE)
          return -1;

        /* OPCODE 길이 읽기 */
        ret = request_read_uint32 (r, &oplen);
        if (ret != sizeof (uint32_t))
          return -1;

        /* OPCODE 읽기 */
        opcode = (rc_opcode_t *) mp_alloc (REQUEST_POOL (r), oplen);
        ret = request_read_buf (r, (char *) opcode, oplen);
        if (ret != oplen)
          return -1;

        /* 처리 */
        tk = proc_rc_opcode (r, rbuf, opcode, (rc_size_t) oplen);
        if (!tk)
          {
            request_send_error (r);
	    goto done;
          }

        request_send_return (r, tk);
        
        /* 이제 Ticket 을 모두 다 사용하였기 때문에, post_config 를 호출해
           줍니다.  이 함수가 실행 된 이루로 Ticket 구조체는 일정 시간 흘른 후,
           GC 되어 질 수 있습니다.  */
        ticket_post_config (tk);
        break;
      }
    case COMMAND_GETSTACK:
      {
        char rbuf[TICKETID_SIZE];

        /* TICKET ID 읽기*/
        ret = request_read_buf (r, rbuf, TICKETID_SIZE);
        if (ret != TICKETID_SIZE)
          return -1;

        proc_rc_getstack (r, rbuf);
        request_send_error (r);
        break;
      }
    default:
      /* proc_rc_unknown () 함수는 예외가 있을 수 없다.  */
      proc_rc_unknown (r, rc);
      request_send_error (r);
      break;
    }

  /* 여기를 지나가면 자식 프로세스는 자동으로 종료되게 되어 있다.  */
 done:
  finish_request (r);

  return 0;
}
