/******************************************************************************
* Copyright (c) 2000-2019 Ericsson Telecom AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
*   Eduard Czimbalmos - initial implementation and initial documentation
*   Istvan Ovary
*   Peter Dimitrov
*   Balasko Jeno
*   Gabor Szalai
******************************************************************************/
//
//  File:               HTTPmsg_PT.cc
//  Description:        HTTP test port implementation
//  Rev:                R9B
//  Prodnr:             CNL 113 469


#include "HTTPmsg_PT.hh"

#include <ctype.h>
#include <arpa/inet.h>

#ifdef AS_USE_SSL
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#endif

static bool report_lf=true;

namespace HTTPmsg__PortType {

HTTPmsg__PT::HTTPmsg__PT(const char *par_port_name)
	: HTTPmsg__PT_BASE(par_port_name)
{
    parameter_set(use_connection_ASPs_name(), "yes");
    parameter_set(server_backlog_name(), "1024");
    use_notification_ASPs = false;
    set_ttcn_buffer_usercontrol(true);
    set_handle_half_close(true);
    adding_client_connection = false;
    adding_ssl_connection = false;
    server_use_ssl = false;
#ifdef AS_USE_SSL
    
    set_ssl_use_ssl(true);
#endif
    use_send_failed = false;
    last_msg = NULL;
}

HTTPmsg__PT::~HTTPmsg__PT()
{

}

void HTTPmsg__PT::set_parameter(const char *parameter_name,
	const char *parameter_value)
{
    log_debug("entering HTTPmsg__PT::set_parameter(%s, %s)", parameter_name, parameter_value);
    if(strcasecmp(parameter_name, use_notification_ASPs_name()) == 0) {
        if (strcasecmp(parameter_value,"yes")==0) use_notification_ASPs = true;
        else if (strcasecmp(parameter_value,"no")==0) use_notification_ASPs = false;
        else log_error("Parameter value '%s' not recognized for parameter '%s'", parameter_value, use_notification_ASPs_name());
    } else if(strcasecmp(parameter_name, "report_failed_message") == 0){
        if (strcasecmp(parameter_value,"yes")==0) use_send_failed = true;
        else if (strcasecmp(parameter_value,"no")==0) use_send_failed = false;
        else log_error("Parameter value '%s' not recognized for parameter '%s'", parameter_value, "report_failed_message");
    }
    else if((strcasecmp(parameter_name, use_connection_ASPs_name()) == 0) || !parameter_set(parameter_name ,parameter_value)) {
        log_warning("HTTPmsg__PT::set_parameter(): Unsupported Test Port parameter: %s", parameter_name);
    }
    log_debug("leaving HTTPmsg__PT::set_parameter(%s, %s)", parameter_name, parameter_value);
}

void HTTPmsg__PT::Handle_Fd_Event(int fd,
    boolean is_readable, boolean is_writable, boolean is_error)
{
    log_debug("-------------- entering HTTPmsg__PT::Handle_Fd_Event() - event received on a connection");
    Handle_Socket_Event(fd, is_readable, is_writable, is_error);
    log_debug("leaving HTTPmsg__PT::Handle_Fd_Event()");
}

void HTTPmsg__PT::Handle_Timeout(double time_since_last_call)
{
    log_debug("entering HTTPmsg__PT::Handle_Timeout()");
    Handle_Timeout_Event(time_since_last_call);
    log_debug("leaving HTTPmsg__PT::Handle_Timeout()");
}

void HTTPmsg__PT::user_map(const char *system_port)
{
    log_debug("entering HTTPmsg__PT::user_map(%s)",system_port);
    if(TTCN_Logger::log_this_event(TTCN_DEBUG)) {
        if(!get_socket_debugging())
            log_warning("%s: to switch on HTTP test port debugging, set the '*.%s.http_debugging := \"yes\" in the port's parameters.", get_name(), get_name());
    }
    map_user();
    log_debug("leaving HTTPmsg__PT::user_map()");
}

void HTTPmsg__PT::user_unmap(const char *system_port)
{
  log_debug("entering HTTPmsg__PT::user_unmap(%s)",system_port);

  unmap_user();

  log_debug("leaving HTTPmsg__PT::user_unmap()");
}

void HTTPmsg__PT::user_start()
{
}

void HTTPmsg__PT::user_stop()
{
}

void HTTPmsg__PT::outgoing_send(const HTTPmsg__Types::Close& send_par)
{
    log_debug("entering HTTPmsg__PT::outgoing_send(Close)");

    if(send_par.client__id().ispresent())
        remove_client((int)send_par.client__id()());
    else
        remove_all_clients();

    log_debug("leaving HTTPmsg__PT::outgoing_send(Close)");
}

void HTTPmsg__PT::outgoing_send(const HTTPmsg__Types::Connect& send_par)
{
    log_debug("entering HTTPmsg__PT::outgoing_send(Connect)");

    adding_ssl_connection = send_par.use__ssl();
    adding_client_connection = true;

#ifndef AS_USE_SSL
    if(adding_ssl_connection)
    {
        log_error("%s: HTTP test port is not compiled to support SSL connections. Please check the User's Guide for instructions on compiling the HTTP test port with SSL support.", get_name());
    }
#endif

   int client_id = open_client_connection(send_par.hostname(),int2str((INTEGER)send_par.portnumber()),NULL,NULL);

    adding_ssl_connection = false;
    adding_client_connection = false;

    log_debug("leaving HTTPmsg__PT::outgoing_send(Connect),client_id: %d", client_id);
}

void HTTPmsg__PT::outgoing_send(const HTTPmsg__Types::Listen& send_par)
{
    log_debug("entering HTTPmsg__PT::outgoing_send(Listen)");

    server_use_ssl = send_par.use__ssl();

    if(server_use_ssl)
        {
    #ifndef AS_USE_SSL
            log_error("%s: HTTP test port is not compiled to support SSL connections. Please check the User's Guide for instructions on compiling the HTTP test port with SSL support.", get_name());
    #endif
        }

    if(send_par.local__hostname().ispresent())
		{

       	open_listen_port(send_par.local__hostname()(),int2str((INTEGER)send_par.portnumber()));

		}
    else
    {
        log_debug("using IN_ADDR_ANY as local host name");
        open_listen_port(NULL,int2str((INTEGER)send_par.portnumber()));

    }

    log_debug("leaving HTTPmsg__PT::outgoing_send(Listen)");

}

void HTTPmsg__PT::outgoing_send(const HTTPmsg__Types::Half__close& send_par)
{
    log_debug("entering HTTPmsg__PT::outgoing_send(Half_close)");

    if(send_par.client__id().ispresent())
        send_shutdown((int)send_par.client__id()());
    else
        send_shutdown();

    log_debug("leaving HTTPmsg__PT::outgoing_send(Half_close)");

}

void HTTPmsg__PT::outgoing_send(const HTTPmsg__Types::Shutdown& /*send_par*/)
{
    log_debug("entering HTTPmsg__PT::outgoing_send(Shutdown)");

    close_listen_port();

    log_debug("leaving HTTPmsg__PT::outgoing_send(Shutdown)");
}

void HTTPmsg__PT::outgoing_send(const HTTPmsg__Types::HTTPMessage& send_par)
{
    log_debug("entering HTTPmsg__PT::outgoing_send(HTTPMessage)");

    TTCN_Buffer snd_buf;
    int client_id = -1;
    last_msg = &send_par;
    
    switch(send_par.get_selection())
    {
        case HTTPmsg__Types::HTTPMessage::ALT_request:
        {
            if(send_par.request().client__id().ispresent())
                client_id = send_par.request().client__id()();
            break;
        }
        case HTTPmsg__Types::HTTPMessage::ALT_request__binary:
        {
            if(send_par.request__binary().client__id().ispresent())
                client_id = send_par.request__binary().client__id()();
            break;
        }
        case HTTPmsg__Types::HTTPMessage::ALT_response:
        {
            if(send_par.response().client__id().ispresent())
                client_id = send_par.response().client__id()();
            break;
        }
        case HTTPmsg__Types::HTTPMessage::ALT_response__binary:
        {
            if(send_par.response__binary().client__id().ispresent())
                client_id = send_par.response__binary().client__id()();
            break;
        }
        case HTTPmsg__Types::HTTPMessage::ALT_erronous__msg:
        {
            if(send_par.erronous__msg().client__id().ispresent())
                client_id = send_par.erronous__msg().client__id()();
            break;
        }
        default:
            TTCN_error("Unknown HTTP_Message type to encode and send!");
    }

    f_HTTP_encodeCommon(send_par, snd_buf);

    if(client_id >= 0)
        send_outgoing(snd_buf.get_data(), snd_buf.get_len(), client_id);
    else
        send_outgoing(snd_buf.get_data(), snd_buf.get_len());
    
    last_msg = NULL;
    log_debug("leaving HTTPmsg__PT::outgoing_send(HTTPMessage)");
}

void HTTPmsg__PT::report_unsent(int client_id, int /*msg_length*/, int /*sent_length*/, const unsigned char* /*msg*/, const char* /*error_text*/)
{
  if(use_send_failed  && last_msg){
    HTTPmsg__Types::Send__failed asp;
    asp.msg()=*last_msg;
    asp.already__half__closed()= (get_peer(client_id,true)->tcp_state) == CLOSE_WAIT ;
    
    incoming_message(asp);
  }
}


void HTTPmsg__PT::client_connection_opened(int client_id)
{
    log_debug("entering HTTPmsg__PT::client_connection_opened(%d)", client_id);

    if(use_notification_ASPs)
    {
        HTTPmsg__Types::Connect__result asp;
        asp.client__id() = client_id;
        incoming_message(asp);
    }
    else if(client_id < 0)
        log_error("Cannot connect to server");

    log_debug("leaving HTTPmsg__PT::client_connection_opened()");
}

void HTTPmsg__PT::listen_port_opened(int port_number)
{
  log_debug("entering HTTPmsg__PT::listen_port_opened(%d)", port_number);

  if(use_notification_ASPs)
  {
    HTTPmsg__Types::Listen__result asp;
    asp.portnumber() = port_number;
    incoming_message(asp);
  }
  else if(port_number < 0)
    log_error("Cannot listen at port");

  log_debug("leaving HTTPmsg__PT::listen_port_opened()");
}

void HTTPmsg__PT::message_incoming(const unsigned char* /*msg*/, int /*messageLength*/, int client_id)
{
    log_debug("entering HTTPmsg__PT::message_incoming()");

    TTCN_Buffer* buf_p = get_buffer(client_id);

    while(buf_p->get_read_len() > 0)
    {
        log_debug("HTTPmsg__PT::message_incoming(): decoding next message, len: %d", (int)buf_p->get_read_len());
        if(!HTTP_decode(buf_p, client_id))
            break;
    }

    log_debug("leaving HTTPmsg__PT::message_incoming()");
}

void HTTPmsg__PT::peer_half_closed(int client_id)
{
    log_debug("entering HTTPmsg__PT::peer_half_closed(client_id: %d)", client_id);

    TTCN_Buffer* buf_p = get_buffer(client_id);
    buf_p->rewind();
    while(buf_p->get_read_len() > 0)
    {
        log_debug("HTTPmsg__PT::remove_client(): decoding next message, len: %d", (int)buf_p->get_read_len());
        if(!HTTP_decode(buf_p, client_id,true))
            break;
    }

    HTTPmsg__Types::Half__close asp;
    asp.client__id() = client_id;
    incoming_message(asp);

    log_debug("leaving HTTPmsg__PT::peer_half_closed(client_id: %d)", client_id);
}

void HTTPmsg__PT::peer_disconnected(int client_id)
{
    log_debug("entering HTTPmsg__PT::peer_disconnected(client_id: %d)", client_id);

    if(use_notification_ASPs)
    {
        HTTPmsg__Types::Close asp;
        asp.client__id() = client_id;
        incoming_message(asp);
    }
    else Abstract_Socket::peer_disconnected(client_id);

    log_debug("leaving HTTPmsg__PT::peer_disconnected(client_id: %d)", client_id);
}

//void HTTPmsg__PT::peer_connected(int client_id, sockaddr_in& addr)

void HTTPmsg__PT::peer_connected(int client_id, const char * host, const int port)
{
    log_debug("entering HTTPmsg__PT::peer_connected(%d)", client_id);

    if(use_notification_ASPs)
    {
        HTTPmsg__Types::Client__connected asp;
        asp.hostname() = host;
        asp.portnumber() = port;
        asp.client__id() = client_id;

        incoming_message(asp);
    }
    else Abstract_Socket::peer_connected(client_id, host, port);

    log_debug("leaving HTTPmsg__PT::peer_connected()");
}

bool HTTPmsg__PT::add_user_data(int client_id)
{
    log_debug("entering HTTPmsg__PT::add_user_data(client_id: %d, use_ssl: %s)",
        client_id, (adding_client_connection && adding_ssl_connection) || (server_use_ssl && !adding_ssl_connection) ? "yes" : "no");

    set_server_mode(!adding_client_connection);

    if((adding_client_connection && !adding_ssl_connection) || (!adding_client_connection && !server_use_ssl))
    {
        log_debug("leaving HTTPmsg__PT::add_user_data() with returning Abstract_Socket::add_user_data()");
        return Abstract_Socket::add_user_data(client_id);
    }
    else
    {
#ifdef AS_USE_SSL
        log_debug("leaving HTTPmsg__PT::add_user_data() with returning SSL_Socket::add_user_data()");
        return SSL_Socket::add_user_data(client_id);
#else
        log_error("%s: HTTP test port is not compiled to support SSL connections. Please check the User's Guide for instructions on compiling the HTTP test port with SSL support.", get_name());
#endif
    }

    // Programming error in HTTPmsg__PT::add_user_data()
    return false;
}

bool HTTPmsg__PT::remove_user_data(int client_id)
{
    log_debug("entering HTTPmsg__PT::remove_user_data(client_id: %d", client_id);

#ifdef AS_USE_SSL
    if(get_user_data(client_id))
    {
        // INFO: it is assumed that only SSL_Socket assigns user data to each peer
        log_debug("leaving HTTPmsg__PT::remove_user_data() with returning SSL_Socket::remove_user_data()");
        return SSL_Socket::remove_user_data(client_id);
    }
#endif

    log_debug("leaving HTTPmsg__PT::remove_user_data() with returning Abstract_Socket::remove_user_data()");
    return Abstract_Socket::remove_user_data(client_id);
}

int HTTPmsg__PT::receive_message_on_fd(int client_id)
{
    log_debug("entering HTTPmsg__PT::receive_message_on_fd(client_id: %d)", client_id);

#ifdef AS_USE_SSL
    if(get_user_data(client_id))
    {
        // INFO: it is assumed that only SSL_Socket assigns user data to each peer
        log_debug("leaving HTTPmsg__PT::receive_message_on_fd() with returning SSL_Socket::receive_message_on_fd()");
        return SSL_Socket::receive_message_on_fd(client_id);
    }
#endif

    log_debug("leaving HTTPmsg__PT::receive_message_on_fd() with returning Abstract_Socket::receive_message_on_fd()");
    return Abstract_Socket::receive_message_on_fd(client_id);
}

void HTTPmsg__PT::remove_client(int client_id)
{
    log_debug("entering HTTPmsg__PT::remove_client(client_id: %d)", client_id);

    TTCN_Buffer* buf_p = get_buffer(client_id);

    while(buf_p->get_read_len() > 0)
    {
        log_debug("HTTPmsg__PT::remove_client(): decoding next message, len: %d", (int)buf_p->get_read_len());
        if(!HTTP_decode(buf_p, client_id,true))
            break;
    }

#ifdef AS_USE_SSL
    if(get_user_data(client_id))
    {
        // INFO: it is assumed that only SSL_Socket assigns user data to each peer
        log_debug("leaving HTTPmsg__PT::remove_client() with returning SSL_Socket::remove_client()");
        return SSL_Socket::remove_client(client_id);
    }
#endif

    log_debug("leaving HTTPmsg__PT::remove_client() with returning Abstract_Socket::remove_client()");
    return Abstract_Socket::remove_client(client_id);
}

int HTTPmsg__PT::send_message_on_fd(int client_id, const unsigned char * message_buffer, int length_of_message)
{
    log_debug("entering HTTPmsg__PT::send_message_on_fd(client_id: %d)", client_id);

#ifdef AS_USE_SSL
    if(get_user_data(client_id))
    {
        // INFO: it is assumed that only SSL_Socket assigns user data to each peer
        log_debug("leaving HTTPmsg__PT::send_message_on_fd() with returning SSL_Socket::send_message_on_fd()");
        return SSL_Socket::send_message_on_fd(client_id, message_buffer, length_of_message);
    }
#endif

    log_debug("leaving HTTPmsg__PT::send_message_on_fd() with returning Abstract_Socket::send_message_on_fd()");
    return Abstract_Socket::send_message_on_fd(client_id, message_buffer, length_of_message);
}

int HTTPmsg__PT::send_message_on_nonblocking_fd(int client_id, const unsigned char * message_buffer, int length_of_message)
{
    log_debug("entering HTTPmsg__PT::(client_id: %d)", client_id);

#ifdef AS_USE_SSL
    if(get_user_data(client_id))
    {
        // INFO: it is assumed that only SSL_Socket assigns user data to each peer
        log_debug("leaving HTTPmsg__PT::send_message_on_nonblocking_fd() with returning SSL_Socket::send_message_on_nonblocking_fd()");
        return SSL_Socket::send_message_on_nonblocking_fd(client_id, message_buffer, length_of_message);
    }
#endif

    log_debug("leaving HTTPmsg__PT::send_message_on_nonblocking_fd() with returning Abstract_Socket::send_message_on_nonblocking_fd()");
    return Abstract_Socket::send_message_on_nonblocking_fd(client_id, message_buffer, length_of_message);
}

// HTTP specific functions

// replaced by f_HTTP_encodeCommon:
// void HTTPmsg__PT::HTTP_encode(const HTTPmsg__Types::HTTPMessage& msg, TTCN_Buffer& buf)
// {
//   f_HTTP_encodeCommon( msg, buf);
// }

//Encodes msg type of "HTTPMessage" into buffer
void f_HTTP_encodeCommon(const HTTPmsg__Types::HTTPMessage& msg, TTCN_Buffer& buf)
{
    buf.clear();
    if( msg.get_selection() == HTTPmsg__Types::HTTPMessage::ALT_erronous__msg )
        buf.put_cs(msg.erronous__msg().msg());
    else
    {
        const HTTPmsg__Types::HeaderLines* header = NULL;
        const HTTPmsg__Types::HTTPRequest* request = NULL;
        const HTTPmsg__Types::HTTPResponse* response = NULL;
        const HTTPmsg__Types::HTTPRequest__binary__body* request_binary = NULL;
        const HTTPmsg__Types::HTTPResponse__binary__body* response_binary = NULL;
        const CHARSTRING* body = NULL;
        const OCTETSTRING* body_binary = NULL;

        if(msg.get_selection() == HTTPmsg__Types::HTTPMessage::ALT_request)
        {
            request = &msg.request();
            header = &request->header();
            body = &request->body();
            buf.put_cs(request->method());
            buf.put_c(' ');
            buf.put_cs(request->uri());
            buf.put_cs(" HTTP/");
            buf.put_cs(int2str(request->version__major()));
            buf.put_c('.');
            buf.put_cs(int2str(request->version__minor()));
            buf.put_cs("\r\n");
        }
        else if(msg.get_selection() == HTTPmsg__Types::HTTPMessage::ALT_response)
        {
            response = &msg.response();
            header = &response->header();
            body = &response->body();
            buf.put_cs("HTTP/");
            buf.put_cs(int2str(response->version__major()));
            buf.put_c('.');
            buf.put_cs(int2str(response->version__minor()));
            buf.put_c(' ');
            buf.put_cs(int2str(response->statuscode()));
            buf.put_c(' ');
            buf.put_cs(response->statustext());
            buf.put_cs("\r\n");
        }
        else if(msg.get_selection() == HTTPmsg__Types::HTTPMessage::ALT_request__binary)
        {
            request_binary = &msg.request__binary();
            header = &request_binary->header();
            body_binary = &request_binary->body();
            buf.put_cs(request_binary->method());
            buf.put_c(' ');
            buf.put_cs(request_binary->uri());
            buf.put_cs(" HTTP/");
            buf.put_cs(int2str(request_binary->version__major()));
            buf.put_c('.');
            buf.put_cs(int2str(request_binary->version__minor()));
            buf.put_cs("\r\n");
        }
        else if(msg.get_selection() == HTTPmsg__Types::HTTPMessage::ALT_response__binary)
        {
            response_binary = &msg.response__binary();
            header = &response_binary->header();
            body_binary = &response_binary->body();
            buf.put_cs("HTTP/");
            buf.put_cs(int2str(response_binary->version__major()));
            buf.put_c('.');
            buf.put_cs(int2str(response_binary->version__minor()));
            buf.put_c(' ');
            buf.put_cs(int2str(response_binary->statuscode()));
            buf.put_c(' ');
            buf.put_cs(response_binary->statustext());
            buf.put_cs("\r\n");
        }

        for( int i = 0; i < header->size_of(); i++ )
        {
            buf.put_cs((*header)[i].header__name());
            buf.put_cs(": ");
            buf.put_cs((*header)[i].header__value());
            buf.put_cs("\r\n");
        }

        buf.put_cs("\r\n");

        if(body && body->lengthof() > 0)
        {
            buf.put_cs(*body);
        }
        else if(body_binary && body_binary->lengthof() > 0)
        {
            buf.put_os(*body_binary);
        }
    }
}

bool HTTPmsg__PT::HTTP_decode(TTCN_Buffer* buffer, const int client_id, const bool connection_closed)
{

  //HTTPmsg__Types::HTTPMessage * msg = new HTTPmsg__Types::HTTPMessage();

  HTTPmsg__Types::HTTPMessage msg;

  if(f_HTTP_decodeCommon(buffer, msg, connection_closed, get_socket_debugging(), test_port_type, test_port_name ))
  {
    TTCN_Logger::log(TTCN_DEBUG,"HTTPmsg__PT::HTTP_decode, before calling incoming_message");
    f_setClientId(msg,client_id);
    incoming_message(msg);
    TTCN_Logger::log(TTCN_DEBUG,"HTTPmsg__PT::HTTP_decode, after calling incoming_message");
    return true;
  }
  return false;
}

void f_setClientId( HTTPmsg__Types::HTTPMessage& msg, const int client_id)
{
  switch(msg.get_selection())
  {
    case HTTPmsg__Types::HTTPMessage::ALT_request:
    {
      msg.request().client__id()=client_id;
      break;
    }
    case HTTPmsg__Types::HTTPMessage::ALT_request__binary:
    {
      msg.request__binary().client__id()=client_id;
      break;
    }
    case HTTPmsg__Types::HTTPMessage::ALT_response:
    {
      msg.response().client__id()=client_id;
      break;
    }
    case HTTPmsg__Types::HTTPMessage::ALT_response__binary:
    {
      msg.response__binary().client__id()=client_id;
      break;
    }
    case HTTPmsg__Types::HTTPMessage::ALT_erronous__msg:  //is this case redundant code(?)
    {
      msg.erronous__msg().client__id()=OMIT_VALUE;
      break;
    }
    default:
      break;
  }//switch
  return;
}//f_setClientId

//
// returns with true if the buffer is not empty and it contain valid message
// Postcondition: if buffer contains valid message, msg will contain the first decoded HTTP message, the decoded part will be removed from the buffer
bool f_HTTP_decodeCommon( TTCN_Buffer* buffer, HTTPmsg__Types::HTTPMessage& msg, const bool connection_closed,
    const bool socket_debugging, const char *test_port_type, const char *test_port_name)
{

    TTCN_Logger::log(TTCN_DEBUG, "starting f_HTTP_decodeCommon ");
    if(buffer->get_read_len() <= 0)
        return FALSE;

    buffer->rewind();

    Decoding_Params decoding_params;
    decoding_params.non_persistent_connection = FALSE;
    decoding_params.chunked_body = FALSE;
    decoding_params.content_length = -1;
    decoding_params.error = FALSE;
    decoding_params.isMessage = TRUE;

    if (TTCN_Logger::log_this_event(TTCN_DEBUG))
    {
        if( test_port_name!= NULL)
          TTCN_Logger::log(TTCN_DEBUG, "%s DECODER: <%s>\n", test_port_name,
            (const char*)CHARSTRING(buffer->get_read_len(), (const char*)buffer->get_read_data()));
        else
          TTCN_Logger::log(TTCN_DEBUG, "DECODER: <%s>\n",
            (const char*)CHARSTRING(buffer->get_read_len(), (const char*)buffer->get_read_data()));
    }

    CHARSTRING first;
    bool isResponse;

    // Decoding the first line

    switch(get_line(buffer, first, false))
    {
    case TRUE: // The first line is available
        {
            //HTTPmsg__Types::HTTPMessage msg;
            HTTPmsg__Types::HeaderLines header = NULL_VALUE;
            OCTETSTRING body=OCTETSTRING(0, (const unsigned char*)"");
            const char *cc_first = (const char *)first;
            //fprintf(stderr, "first: %s\n", cc_first);
            int version__major, version__minor, statusCode;

            char* method_name;
            const char* pos = strchr(cc_first, ' ');
            if(pos == NULL)
            {
                TTCN_Logger::log(TTCN_DEBUG, "could not find space in the first line of response: <%s>", cc_first);
                decoding_params.isMessage = FALSE;
                decoding_params.error = TRUE;
                break;
            }
            method_name = (char*)Malloc(pos - cc_first + 1);
            strncpy(method_name, cc_first, pos - cc_first);
            method_name[pos - cc_first] = '\0';

            char* stext = (char*)Malloc(strlen(cc_first));
            stext[0] = '\0';
	    
            TTCN_Logger::log(TTCN_DEBUG, "method_name: <%s>", method_name);
            if(strncasecmp(method_name, "HTTP/", 5) == 0)
            {
                // The first line contains a response like HTTP/1.1 200 OK
                isResponse = true;

                if(sscanf(cc_first, "HTTP/%d.%d %d %[^\r]", &version__major, &version__minor,
                          &statusCode, stext) < 3)
                {
                    decoding_params.isMessage = FALSE;
                    decoding_params.error = TRUE;
            Free(method_name);
            Free(stext);
                    break;
                }
                if (version__minor == 0)
                    decoding_params.non_persistent_connection = TRUE;
            }
            else
            {
                isResponse = false;
                // The first line contains a request
                // like "POST / HTTP/1.0"
                if(sscanf(pos + 1, "%s HTTP/%d.%d",
                          stext, &version__major, &version__minor ) != 3)
                {
                    decoding_params.isMessage = FALSE;
                    decoding_params.error = TRUE;
            Free(method_name);
            Free(stext);
                    break;
                }
            }

            // Additional header lines
            TTCN_Logger::log(TTCN_DEBUG, "Decoding the headers");
            HTTP_decode_header(buffer, header, decoding_params, socket_debugging, isResponse, test_port_type, test_port_name);
            TTCN_Logger::log(TTCN_DEBUG, "Headers decoded. %s headers.", decoding_params.isMessage ? "Valid" : "Invalid");

            if(isResponse && decoding_params.content_length==-1){
              if( (statusCode>99 && statusCode <200) || statusCode==204 || statusCode==304 ) decoding_params.content_length=0;
            }

            if(decoding_params.isMessage)
                HTTP_decode_body(buffer, body, decoding_params, connection_closed, socket_debugging, test_port_type, test_port_name);

            if(decoding_params.isMessage)
            {
                TTCN_Logger::log(TTCN_DEBUG, "Message successfully decoded");
                bool foundBinaryCharacter = false;

                int len = body.lengthof();
                const unsigned char* ptr = (const unsigned char*)body;
                for(int i = 0; i < len && !foundBinaryCharacter; i++)
                {
                    if(!isascii(ptr[i]))
                        foundBinaryCharacter = true;
                }
                if(foundBinaryCharacter)
                    TTCN_Logger::log(TTCN_DEBUG, "Binary data found");
                if(isResponse)
                {
                  if(foundBinaryCharacter)
                  {
                    HTTPmsg__Types::HTTPResponse__binary__body& response_binary = msg.response__binary();
                    response_binary.client__id() = OMIT_VALUE;
                    response_binary.version__major() = version__major;
                    response_binary.version__minor() = version__minor;
                    response_binary.statuscode() = statusCode;
                    if(strlen(stext) > 0)
                        response_binary.statustext() = CHARSTRING(stext);
                    else
                        response_binary.statustext() = "";
                    response_binary.header() = header;
                    response_binary.body() = body;
                  }
                  else
                  {
                    HTTPmsg__Types::HTTPResponse& response = msg.response();
                    response.client__id() = OMIT_VALUE;
                    response.version__major() = version__major;
                    response.version__minor() = version__minor;
                    response.statuscode() = statusCode;
                    if(strlen(stext) > 0)
                        response.statustext() = CHARSTRING(stext);
                    else
                        response.statustext() = "";
                    response.header() = header;
                    response.body() = oct2char(body);
                  }
                }
                else
                {
                  if(foundBinaryCharacter)
                  {
                    HTTPmsg__Types::HTTPRequest__binary__body& request_binary = msg.request__binary();
                    request_binary.client__id() = OMIT_VALUE;
                    request_binary.method() = CHARSTRING(method_name);
                    request_binary.uri() = CHARSTRING(stext);
                    request_binary.version__major() = version__major;
                    request_binary.version__minor() = version__minor;
                    request_binary.header() = header;
                    request_binary.body() = body;
                  }
                  else
                  {
                    HTTPmsg__Types::HTTPRequest& request = msg.request();
                    request.client__id() = OMIT_VALUE;
                    request.method() = CHARSTRING(method_name);
                    request.uri() = CHARSTRING(stext);
                    request.version__major() = version__major;
                    request.version__minor() = version__minor;
                    request.header() = header;
                    request.body() = oct2char(body);
                  }
                }
                //incoming_message(msg); <- outer function calls if necessary
            }
            Free(method_name);
            Free(stext);
        }
        break;
    case BUFFER_CRLF:
    case BUFFER_FAIL:
        decoding_params.error = TRUE;
    case FALSE:
        decoding_params.isMessage = FALSE;
    }

    if(decoding_params.error)
    {

        if(buffer->get_read_len() > 0)
          msg.erronous__msg().msg() = CHARSTRING(buffer->get_read_len(), (const char*)buffer->get_read_data());
        else
          msg.erronous__msg().msg() = "The previous message is erronous.";
        msg.erronous__msg().client__id() = OMIT_VALUE;
        //incoming_message(msg);
        buffer->clear();
        decoding_params.isMessage = TRUE;
    }

    if(decoding_params.isMessage)
    {
        buffer->cut();
    }

    return decoding_params.isMessage;
}

void HTTP_decode_header(TTCN_Buffer* buffer, HTTPmsg__Types::HeaderLines& headers, Decoding_Params& decoding_params,
    const bool socket_debugging, const bool resp, const char *test_port_type, const char *test_port_name)
{
    CHARSTRING cstr;
    const char* separator;
    char* header_name = NULL;
    bool length_received = false;

    for(int i = 0; ; i++)
    {
        switch(get_line(buffer, cstr, true))
        {
        case TRUE:
            {
                char h[cstr.lengthof() + 1];
                strcpy(h, (const char*)cstr);
                separator = strchr(h, ':');
                if(separator)
                {
                    header_name = (char*)Realloc(header_name, separator - h + 1);
                    strncpy(header_name, h, separator - h);
                    header_name[separator - h] = '\0';
                    separator++;
                    while(*separator && isspace(separator[0]))
                        separator++;
                    char* end = h + strlen(h);
                    while(isspace((end - 1)[0]))
                    {
                        end--;
                        *end = '\0';
                    }
                    headers[i] = HTTPmsg__Types::HeaderLine(header_name, separator);
                    HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name, "+Header line: <%s: %s>", header_name, separator);

                    if(!strcasecmp(header_name, "Content-Length"))
                        { sscanf(separator, "%d", &decoding_params.content_length); length_received=true;}
                    else if(!strcasecmp(header_name, "Connection") && !strcasecmp(separator, "close"))
                        decoding_params.non_persistent_connection = TRUE;
                    else if(!strcasecmp(header_name, "Connection") && !strcasecmp(separator, "keep-alive"))
                        decoding_params.non_persistent_connection = FALSE;
                    else if(!strcasecmp(header_name, "Transfer-Encoding") && !strcasecmp(separator, "chunked"))
                        decoding_params.chunked_body = TRUE;

                }
                continue;
            }
        case BUFFER_FAIL:
            HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name, "BUFFER_FAIL in HTTP_decode_header!");
            HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name, "whole bufer now: <%s>", (const char*)buffer->get_data());
            log_to_hexa(buffer);
            decoding_params.error = TRUE;
        case FALSE:
            decoding_params.isMessage = FALSE;
        case BUFFER_CRLF:
            break;
        }
        break;
    }
    if(decoding_params.isMessage && !resp && !length_received && !decoding_params.chunked_body) decoding_params.content_length=0;
    Free(header_name);
}

void HTTP_decode_body(TTCN_Buffer* buffer, OCTETSTRING& body, Decoding_Params& decoding_params, const bool connection_closed,
    const bool socket_debugging, const char *test_port_type, const char *test_port_name)
{
    if(buffer->get_read_len() > 0)
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "Decoding body, buffer length: %d", buffer->get_read_len());

    if (decoding_params.chunked_body)
    {
        HTTP_decode_chunked_body(buffer, body, decoding_params, socket_debugging, test_port_type, test_port_name);
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "--------- After chunked body decoding:");
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "--------- non_persistent_connection: %s",  decoding_params.non_persistent_connection ? "yes" : "no");
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "--------- chunked_body: %s",  decoding_params.chunked_body ? "yes" : "no");
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "--------- content_length: %d",  decoding_params.content_length);
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "--------- error: %s",  decoding_params.error ? "yes" : "no");
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "--------- isMessage: %s",  decoding_params.isMessage ? "yes" : "no");

    }
    else if(decoding_params.content_length >= 0)
    {
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "lengthof body: %d, content_length given: %d", buffer->get_read_len(), decoding_params.content_length);
        if(buffer->get_read_len() >= (unsigned)decoding_params.content_length)
        {
            body = OCTETSTRING(decoding_params.content_length, buffer->get_read_data());
            buffer->set_pos(buffer->get_pos() + decoding_params.content_length);
        }
        else
        {
            decoding_params.isMessage = FALSE;
            HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "The decoder's body length %d is less than the Content_length in the message header %d; The HTTP port is waiting for additional data.", buffer->get_read_len(), decoding_params.content_length);
            buffer->set_pos(buffer->get_pos() + buffer->get_read_len());
        }
    }
    else if(connection_closed)
    {
       /* if(buffer->get_read_len() >= 0)*/ // Always true
        {
            body = OCTETSTRING(buffer->get_read_len(), buffer->get_read_data());
            buffer->set_pos(buffer->get_pos() + buffer->get_read_len());
        }
    } else {
            decoding_params.isMessage = FALSE;
            HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "The HTTP port is waiting for additional data.");
            buffer->set_pos(buffer->get_pos() + buffer->get_read_len());
    }
}

void HTTP_decode_chunked_body(TTCN_Buffer* buffer, OCTETSTRING& body, Decoding_Params& decoding_params,
    const bool socket_debugging, const char *test_port_type, const char *test_port_name)
{
    OCTETSTRING chunk;
    CHARSTRING line;
    unsigned int chunk_size = 1;

    while(chunk_size > 0)
    {
        switch(get_line(buffer, line, false))
        {
            case TRUE:
                HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "line: <%s>", (const char*)line);
                if(sscanf((const char *)line, "%x", &chunk_size) != 1)
                {
                    HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "No chunksize found");
                    body = body + OCTETSTRING(line.lengthof(), (const unsigned char*)(const char*)line);
                    chunk_size = 0;
                    decoding_params.error = TRUE;
                }
                else
                {
                    if(chunk_size == 0)
                    {
                        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name, "chunk_size 0 -> closing chunk");
                        if(get_line(buffer, line, false) == BUFFER_CRLF)
                            HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "Trailing \\r\\n ok!");
                        else
                            TTCN_Logger::log(TTCN_WARNING,"Trailing \\r\\n after the closing chunk is not present, instead it is <%s>!", (const char*)line);
                    }
/*                    else if(chunk_size < 0) // the chunk_size is unsigned, never true
                    {
                        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "chunk_size less than 0");
                        decoding_params.error = TRUE;
                        chunk_size = 0;
                    }*/
                    else // chunk_size > 0
                    {
                        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "processing next chunk, size: %d", chunk_size);
                        if(buffer->get_read_len() < chunk_size)
                        {
                            HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "chunk size is greater than the buffer length, more data is needed");
                            decoding_params.isMessage = FALSE;
                            chunk_size = 0;
                        }
                    }
                }
                break;
            case FALSE:
                HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "buffer does not contain a whole line, more data is needed");
                decoding_params.isMessage = FALSE;
                chunk_size = 0;
                break;
            case BUFFER_CRLF:
                HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "beginning CRLF removed");
                continue;
            case BUFFER_FAIL:
                HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "BUFFER_FAIL");
                decoding_params.error = FALSE;
                chunk_size = 0;
                break;
            default:
                decoding_params.isMessage = FALSE;
                chunk_size = 0;
                HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "more data is needed");
        }

        body = body + OCTETSTRING(chunk_size, buffer->get_read_data());
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "pull %d bytes from %d", chunk_size, buffer->get_read_len());
        buffer->set_pos(buffer->get_pos() + chunk_size);
        // hack
        if(buffer->get_read_len() && buffer->get_read_data()[0] == '\n')  // don't read from the buffer if there is nothing in it.
        {
            HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,"hack: adjusted buffer position after the '\\n'");
            buffer->set_pos(buffer->get_pos() + 1);
        }
        HTTPmsg__Types::log_debug(socket_debugging, test_port_type, test_port_name,  "remaining data: <%s>, len: %d", (const char *)CHARSTRING(buffer->get_read_len(), (const char*)buffer->get_read_data()), buffer->get_read_len());
    }
}

int get_line(TTCN_Buffer* buffer, CHARSTRING& to, const bool concatenate_header_lines)
{
    unsigned int i = 0;
    const unsigned char *cc_to = buffer->get_read_data();

    if(!buffer->get_read_len())
        return FALSE;

    while(1)
    {
        for( ; i < buffer->get_read_len() && cc_to[i] != '\0' && cc_to[i] != '\r' && cc_to[i] != '\n'; i++);

        if(i >= buffer->get_read_len())
        {
            to = CHARSTRING("");
            return FALSE;
        }
        else
        {
            if(cc_to[i] == '\n') {//
              if(report_lf){
                switch(HTTPmsg__Types::crlf__mode){
                  case HTTPmsg__Types::strict__crlf__mode::ERROR_:
                    return BUFFER_FAIL;
                    break;
                  case HTTPmsg__Types::strict__crlf__mode::WARNING__ONCE:
                    report_lf=false;
                    // no break
                  case HTTPmsg__Types::strict__crlf__mode::WARNING:
                    TTCN_warning("Missing '\\r'.");
                    break;
                  default:
                    break;
                }
              }
              if(i > 0 && (i + 1) < buffer->get_read_len() && concatenate_header_lines && (cc_to[i+1] == ' ' || cc_to[i+1] == '\t'))
                    i += 1;
                  else
                  {
                      to = CHARSTRING(i, (const char*)cc_to);
                      buffer->set_pos(buffer->get_pos() + i + 1);
                      return i == 0 ? BUFFER_CRLF : TRUE;
                  }

            } else
            {
                if((i + 1) < buffer->get_read_len() && cc_to[i + 1] != '\n')
                  return BUFFER_FAIL;
                else if(i > 0 && (i + 2) < buffer->get_read_len() && concatenate_header_lines && (cc_to[i+2] == ' ' || cc_to[i+2] == '\t'))
                  i += 2;
                else
                {
                    to = CHARSTRING(i, (const char*)cc_to);
                    buffer->set_pos(buffer->get_pos() + i + 2);
                    return i == 0 ? BUFFER_CRLF : TRUE;
                }
            }
        }
    }
}

void log_to_hexa(TTCN_Buffer* buffer)
{
    int len = buffer->get_read_len();
    const unsigned char* ptr = buffer->get_read_data();
    for(int i = buffer->get_pos(); i < len; i++)
    {
        TTCN_Logger::log_event(" %02X", ptr[i]);
    }
}


const char* HTTPmsg__PT::local_port_name()              { return "";}
const char* HTTPmsg__PT::remote_address_name()          { return "";}
const char* HTTPmsg__PT::local_address_name()           { return "";}
const char* HTTPmsg__PT::remote_port_name()             { return "";}
const char* HTTPmsg__PT::use_notification_ASPs_name()   { return "use_notification_ASPs";}
const char* HTTPmsg__PT::halt_on_connection_reset_name(){ return "";}
const char* HTTPmsg__PT::server_mode_name()             { return "";}
const char* HTTPmsg__PT::socket_debugging_name()        { return "http_debugging";}
const char* HTTPmsg__PT::nagling_name()                 { return "";}
const char* HTTPmsg__PT::server_backlog_name()          { return "server_backlog";}
const char* HTTPmsg__PT::ssl_use_ssl_name()                { return "";}
const char* HTTPmsg__PT::ssl_use_session_resumption_name() { return "";}
const char* HTTPmsg__PT::ssl_private_key_file_name()       { return "KEYFILE";}
const char* HTTPmsg__PT::ssl_trustedCAlist_file_name()     { return "TRUSTEDCALIST_FILE";}
const char* HTTPmsg__PT::ssl_certificate_file_name()       { return "CERTIFICATEFILE";}
const char* HTTPmsg__PT::ssl_password_name()               { return "PASSWORD";}
const char* HTTPmsg__PT::ssl_verifycertificate_name()      { return "VERIFYCERTIFICATE";}


} //eof namespace "HTTPmsg__PortType"

namespace HTTPmsg__Types {

using namespace HTTPmsg__PortType;

//=========================================================================
//==== Working Functions independent from sending and receiving:===
//=========================================================================

//from AbstractSocket
void log_debug(const bool socket_debugging, const char *test_port_type, const char *test_port_name, const char *fmt, ...)
{
  if (socket_debugging) {
    TTCN_Logger::begin_event(TTCN_DEBUG);
    if ((test_port_type!=NULL && test_port_name!=NULL)&&(strlen(test_port_type)!=0 && strlen(test_port_name)!=0))
       TTCN_Logger::log_event("%s test port (%s): ", test_port_type, test_port_name);
    va_list args;
    va_start(args, fmt);
    TTCN_Logger::log_event_va_list(fmt, args);
    va_end(args);
    TTCN_Logger::end_event();
  }
}

void log_warning(const char *test_port_type, const char *test_port_name, const char *fmt, ...)
{
  TTCN_Logger::begin_event(TTCN_WARNING);
  if (test_port_type!=NULL && test_port_name!=NULL)
       TTCN_Logger::log_event("%s test port (%s): ", test_port_type, test_port_name);
  va_list args;
  va_start(args, fmt);
  TTCN_Logger::log_event_va_list(fmt, args);
  va_end(args);
  TTCN_Logger::end_event();
}

//=========================================================================
//==== Encoder-decoder Functions independent from sending and receiving:===
//=========================================================================

/*********************************************************
* Function: enc__HTTPMessage
*
* Purpose:
*    To encode msg type of HTTPMessage into OCTETSTRING separated from sending functionality
*    It is for users using this test port as a protocol module
*
* References:
*   RFC2616
*
* Precondition:
*  msg is filled in properly
* Postcondition:
*
*
* Parameters:
*  msg - the HTTP Message to be encoded
*
* Return Value:
*   OCTETSTRING - the encoded message
* Detailed Comments:
*   -
*
*********************************************************/
OCTETSTRING enc__HTTPMessage( const HTTPmsg__Types::HTTPMessage& msg ) {
  TTCN_Buffer buf;
  buf.clear();
  HTTPmsg__PortType::f_HTTP_encodeCommon( msg, buf);
  return OCTETSTRING(buf.get_len(), buf.get_data());
}
/*********************************************************
* Function: dec__HTTPMessage
*
* Purpose:
*    To decode msg type of OCTETSTRING into HTTPMessage separated from receiving functionality
*    It is for users using this test port as a protocol module
*
* References:
*   RFC2616
*
* Precondition:
*  stream is filled in properly
* Postcondition:
*  -
*
* Parameters:
*  stream - the message to be decoded
*  msg    - reference to the record type of HTTPMessage which will contain the decoded value if the return value less than the length of the original stream
* Return Value:
*   integer - the length of the remaining data which is not decoded yet.
* Detailed Comments:
*   If the full stream is decoded, the return value is zero
*   If nothing is decoded (decoding failed) the return value equals to the original length of the stream
*
*********************************************************/

INTEGER dec__HTTPMessage(OCTETSTRING const& stream, HTTPMessage& msg, const BOOLEAN& socket_debugging =  dec__HTTPMessage_socket__debugging_defval )
{
  TTCN_Logger::log(TTCN_DEBUG, "starting HTTPmsg__Types::dec__HTTPMessage");
  TTCN_Buffer *buf_p = new TTCN_Buffer() ;
  buf_p->put_os(stream);

  int buf_len = buf_p->get_read_len();
  if( buf_len > 0)
  {
      if(f_HTTP_decodeCommon(buf_p, msg, true, socket_debugging, NULL, NULL))
      {
        log_debug(socket_debugging,"","","dec__HTTPMessage, after decoding:\nbuf_len: %d\nget_len: %d\nget_read_len:%d",
            buf_len,
            buf_p->get_len(),
            buf_p->get_read_len());
        buf_len = buf_p->get_read_len(); //remaining data length
      }
      else
        buf_len = -1;


  } else buf_len = -1;
  delete buf_p;
  return buf_len;
}

}//namespace
