/*
 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
 *
 * All rights reserved.
 */
//
// connection.cpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2006 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifdef HTTP_WITH_SSL

#include <vector>

#include "SslConnection.h"
#include "ConnectionManager.h"
#include "Server.h"

namespace Wt {
  LOGGER("wthttp/async");
}

namespace http {
namespace server {

SslConnection::SslConnection(asio::io_service& io_service, Server *server,
    asio::ssl::context& context,
    ConnectionManager& manager, RequestHandler& handler)
  : Connection(io_service, server, manager, handler),
    socket_(new asio::ssl::stream<asio::ip::tcp::socket>(io_service, context)),
    sslShutdownTimer_(io_service)
{
  // avoid CRIME attack, get A rating on SSL analysis tools
#ifdef SSL_OP_NO_COMPRESSION
  SSL_set_options(socket_->native_handle(), SSL_OP_NO_COMPRESSION);
#endif
}

asio::ip::tcp::socket& SslConnection::socket()
{
  return socket_->next_layer();
}

void SslConnection::start()
{
  std::shared_ptr<SslConnection> sft
    = std::static_pointer_cast<SslConnection>(shared_from_this());

  socket_->async_handshake(asio::ssl::stream_base::server,
      [sft](const Wt::AsioWrapper::error_code& err) {
        asio::dispatch(sft->strand_,
                        std::bind(&SslConnection::handleHandshake,
                                  sft,
                                  err));
      });
}

void SslConnection::handleHandshake(const Wt::AsioWrapper::error_code& error)
{
  SSL* ssl = socket_->native_handle();

  if (!error) {
    Connection::start();
    // ssl handle must be registered after calling start(), since start()
    // resets the structs
    registerSslHandle(ssl);
  } else {
    long sslState = SSL_get_verify_result(ssl);
    if (sslState != X509_V_OK) {
      LOG_INFO("OpenSSL error: "
               << X509_verify_cert_error_string(sslState));
    }

    LOG_INFO("SSL handshake error: " << error.message());
    ConnectionManager_.stop(shared_from_this());
  }
}

void SslConnection::stop()
{
  if (!socket_) {
    return;
  }

  LOG_DEBUG(native() << ": stop()");
  finishReply();
  LOG_DEBUG(native() << ": SSL shutdown");

  Connection::stop();

  if (socketTransferRequested_) {
    return;
  }

  std::shared_ptr<SslConnection> sft
    = std::static_pointer_cast<SslConnection>(shared_from_this());

  sslShutdownTimer_.expires_after(std::chrono::seconds(1));
  sslShutdownTimer_.async_wait
    ([sft](const Wt::AsioWrapper::error_code& e) {
       asio::dispatch(sft->strand_,
                      std::bind(&SslConnection::stopNextLayer,
                                sft, e));
     });

  socket_->async_shutdown
    ([sft](const Wt::AsioWrapper::error_code& e) {
        asio::dispatch(sft->strand_,
                       std::bind(&SslConnection::stopNextLayer,
                                 sft, e));
      });
}

void SslConnection::stopNextLayer(const Wt::AsioWrapper::error_code& ec)
{
  // We may get here either because of sslShutdownTimer_ timing out, or because
  // shutdown is complete. In both cases, closing next layer socket is the thing to do.
  // In case of timeout, we will get here twice.
  sslShutdownTimer_.cancel();
  if (ec) {
    LOG_DEBUG(native() << ": ssl_shutdown failed:"
      << ec.message());
  }

  if (socket().is_open()) {
    Wt::AsioWrapper::error_code ignored_ec;
    LOG_DEBUG(native() << ": socket shutdown");
    socket().shutdown(asio::ip::tcp::socket::shutdown_both,
                      ignored_ec);
    socket().cancel(ignored_ec);
    LOG_DEBUG(native() << "closing socket");
    socket().close(ignored_ec);
  }
}

void SslConnection::startAsyncReadRequest(Buffer& buffer, int timeout)
{
  if (state_ & Reading) {
    stop();
    return;
  }

  setReadTimeout(timeout);

  std::shared_ptr<SslConnection> sft
    = std::static_pointer_cast<SslConnection>(shared_from_this());
  socket_->async_read_some
    (asio::buffer(buffer),
     [sft](const Wt::AsioWrapper::error_code& err, std::size_t bytes_transferred) {
        asio::dispatch(sft->strand_,
                      std::bind(&SslConnection::handleReadRequestSsl,
                                sft,
                                err,
                                bytes_transferred));
      });
}

void SslConnection::handleReadRequestSsl(const Wt::AsioWrapper::error_code& e,
                                         std::size_t bytes_transferred)
{
  // Asio SSL does not perform a write until the read handler
  // has returned. In the normal case, a read handler does not
  // return in case of a recursive event loop, so the SSL write
  // deadlocks a session. Hence, post the processing of the data
  // read, so that the read handler can return here immediately.
  asio::post(strand_,
             std::bind(&SslConnection::handleReadRequest,
                       shared_from_this(),
                       e,
                       bytes_transferred));
}

void SslConnection::startAsyncReadBody(ReplyPtr reply,
                                       Buffer& buffer, int timeout)
{
  if (state_ & Reading) {
    LOG_DEBUG(native() << ": state_ = "
              << (state_ & Reading ? "reading " : "")
              << (state_ & Writing ? "writing " : ""));
    stop();
    return;
  }

  setReadTimeout(timeout);

  std::shared_ptr<SslConnection> sft
    = std::static_pointer_cast<SslConnection>(shared_from_this());
  socket_->async_read_some
    (asio::buffer(buffer),
     [sft, reply](const Wt::AsioWrapper::error_code& err, std::size_t bytes_transferred) {
        asio::dispatch(sft->strand_,
                      std::bind(&SslConnection::handleReadBodySsl,
                                sft,
                                reply,
                                err,
                                bytes_transferred));
      });
}

void SslConnection::handleReadBodySsl(ReplyPtr reply,
                                      const Wt::AsioWrapper::error_code& e,
                                      std::size_t bytes_transferred)
{
  // See handleReadRequestSsl for explanation
  std::shared_ptr<SslConnection> sft
    = std::static_pointer_cast<SslConnection>(shared_from_this());
  post(strand_,
       std::bind(&SslConnection::handleReadBody0,
                 sft,
                 reply,
                 e,
                 bytes_transferred));
}

void SslConnection
::startAsyncWriteResponse(ReplyPtr reply,
                          const std::vector<asio::const_buffer>& buffers,
                          int timeout)
{
  if (state_ & Writing) {
    LOG_DEBUG(native() << ": state_ = "
              << (state_ & Reading ? "reading " : "")
              << (state_ & Writing ? "writing " : ""));
    stop();
    return;
  }

  setWriteTimeout(timeout);

  std::shared_ptr<SslConnection> sft
    = std::static_pointer_cast<SslConnection>(shared_from_this());
  asio::async_write
    (*socket_, buffers,
     [sft, reply](const Wt::AsioWrapper::error_code& err, std::size_t bytes_transferred) {
        asio::dispatch(sft->strand_,
                      std::bind(&SslConnection::handleWriteResponse0,
                                sft, reply,
                                err,
                                bytes_transferred));
      });
}

void SslConnection::doSocketTransferCallback()
{
  sslSocketTransferCallback_(std::move(socket_));
}
} // namespace server
} // namespace http

#endif // HTTP_WITH_SSL
