
#if defined(HAVE_CONFIG_H)
  #include "config.h"
#endif

#ifdef USE_SSL
#ifdef USE_DTLS

#include <memory>

#ifndef RESIP_COMPAT_HXX
#include "rutil/compat.hxx"
#endif

#ifndef RESIP_DATA_HXX
#include "rutil/Data.hxx"
#endif

#ifndef RESIP_DNSUTIL_HXX
#include "rutil/DnsUtil.hxx"
#endif

#ifndef RESIP_SOCKET_HXX
#include "rutil/Socket.hxx"
#endif

#ifndef RESIP_LOGGER_HXX
#include "rutil/Logger.hxx"
#endif

#ifndef RESIP_SIPMESSAGE_HXX
#include "resip/stack/SipMessage.hxx"
#endif

#ifndef RESIP_HELPER_HXX
#include "resip/stack/Helper.hxx"
#endif

#ifndef RESIP_SECURITY_HXX
#include "resip/stack/ssl/Security.hxx"
#endif

#ifndef RESIP_DTLSMESSAGE_HXX
#include "resip/stack/DtlsMessage.hxx"
#endif

#ifndef RESIP_DTLSTRANSPORT_HXX
#include "resip/stack/ssl/DtlsTransport.hxx"
#endif

#include "rutil/WinLeakCheck.hxx"

#include <openssl/opensslv.h>
#if !defined(LIBRESSL_VERSION_NUMBER)
#include <openssl/e_os2.h>
#endif
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/x509v3.h>
#include <openssl/ssl.h>

#ifdef USE_SIGCOMP
#include <osc/Stack.h>
#include <osc/StateChanges.h>
#include <osc/SigcompMessage.h>
#endif

#define RESIPROCATE_SUBSYSTEM Subsystem::TRANSPORT

using namespace std;
using namespace resip;

// .slg. Note that DTLS handshakes were really broken in older versions of OpenSSL 0.9.8
//       You should use at least version 0.9.8g for both client and server.

DtlsTransport::DtlsTransport(Fifo<TransactionMessage>& fifo,
                             int portNum,
                             IpVersion version,
                             const Data& interfaceObj,
                             Security& security,
                             const Data& sipDomain,
                             AfterSocketCreationFuncPtr socketFunc,
                             Compression& compression,
                             const Data& certificateFilename, 
                             const Data& privateKeyFilename,
                             const Data& privateKeyPassPhrase)
 : UdpTransport( fifo, portNum, version, StunDisabled, interfaceObj, socketFunc, compression ),
   mTimer( mHandshakePending ),
   mSecurity( &security ),
   mDomain(sipDomain)
{
   // Note on AfterSocketCreateFuncPtr:  because this class uses UdpTransport the bind operation 
   //   is called in the UdpTransport constructor and the transport type passed to AfterSocketCreationFuncPtr
   //   will end up being UDP and not DTLS.  TODO - this should be fixed.  Creating a UdpBaseTransport is one
   //   solution that would align with the TCP flavour of transports.
   
   setTlsDomain(sipDomain);
   InfoLog ( << "Creating DTLS transport host=" << interfaceObj
             << " port=" << mTuple.getPort()
             << " ipv4=" << version ) ;

   mTxFifo.setDescription("DtlsTransport::mTxFifo");

   mTuple.setType( DTLS );

   mClientCtx = mSecurity->createDomainCtx(DTLS_client_method(), Data::Empty, certificateFilename, privateKeyFilename, privateKeyPassPhrase) ;
   mServerCtx = mSecurity->createDomainCtx(DTLS_server_method(), sipDomain, certificateFilename, privateKeyFilename, privateKeyPassPhrase) ;
   resip_assert( mClientCtx ) ;
   resip_assert( mServerCtx ) ;

   mDummyBio = BIO_new( BIO_s_mem() ) ;
   resip_assert( mDummyBio ) ;

   mSendData = nullptr;

   /* DTLS: partial reads end up discarding unread UDP bytes :-(
    * Setting read ahead solves this problem.
    * Source of this comment is: apps/s_client.c from OpenSSL source
    */
   SSL_CTX_set_read_ahead(mClientCtx, 1);
   SSL_CTX_set_read_ahead(mServerCtx, 1);

   /* trying to read from this BIO always returns retry */
   BIO_set_mem_eof_return( mDummyBio, -1 ) ;
}

DtlsTransport::~DtlsTransport()
{
   DebugLog (<< "Shutting down " << mTuple);

   while(mDtlsConnections.begin() != mDtlsConnections.end())
   {
       _cleanupConnectionState(mDtlsConnections.begin()->second, mDtlsConnections.begin()->first);
   }
   SSL_CTX_free(mClientCtx);mClientCtx=nullptr;
   SSL_CTX_free(mServerCtx);mServerCtx=nullptr;

   BIO_free( mDummyBio) ;
}

void
DtlsTransport::_read()
{
   Tuple sender(mTuple);
   auto len = processRxRecv(sender);
   if (len <= 0)
   {
      return;
   }

   SSL *ssl ;
   BIO *rbio ;
   BIO *wbio ;

   /* begin SSL stuff */
   struct sockaddr peer = sender.getMutableSockaddr();

   ssl = mDtlsConnections[ *((struct sockaddr_in *)&peer) ] ;

   /*
    * If we don't have a binding for this peer,
    * then we're a server.
    */
   if (ssl == nullptr)
   {
      ssl = SSL_new( mServerCtx ) ;
      resip_assert( ssl ) ;

      // clear SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE set in SSL_CTX if we are a server
      SSL_set_verify(ssl, 0, nullptr);

      InfoLog( << "DTLS handshake starting (Server mode)" );

      SSL_set_accept_state( ssl ) ;

      wbio = BIO_new_dgram( (int)mFd, BIO_NOCLOSE ) ;
      resip_assert( wbio ) ;

      BIO_dgram_set_peer( wbio, &peer ) ;

      SSL_set_bio( ssl, nullptr, wbio ) ;

      /* remember this connection */
      mDtlsConnections[ *((struct sockaddr_in *)&peer) ] = ssl ;
   }

   rbio = BIO_new_mem_buf(mRxBuffer.data(), len);
   BIO_set_mem_eof_return( rbio, -1 ) ;

   SSL_set0_rbio( ssl, rbio );

   len = SSL_read(ssl, mRxSslBuffer.data(), static_cast<int>(mRxSslBuffer.size()));
   int err = SSL_get_error( ssl, len ) ;

   /* done with the rbio */
   SSL_set0_rbio( ssl, mDummyBio );

   if ( len <= 0 )
   {
      char errorString[1024];

      switch( err )
      {
         case SSL_ERROR_NONE:
            break ;
         case SSL_ERROR_SSL:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS read condition SSL_ERROR_SSL on"
                         << " addr = " << inet_ntoa(((struct sockaddr_in *)&peer)->sin_addr)
                         << " port = " << ntohs(((struct sockaddr_in *)&peer)->sin_port)
                         << " error = " << errorString );
            }
            break ;
         case SSL_ERROR_WANT_READ:
            break ;
         case SSL_ERROR_WANT_WRITE:
            break ;
         case SSL_ERROR_SYSCALL:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS read condition SSL_ERROR_SYSCALL on"
                         << " addr = " << inet_ntoa(((struct sockaddr_in *)&peer)->sin_addr)
                         << " port = " << ntohs(((struct sockaddr_in *)&peer)->sin_port)
                         << " error = " << errorString );
            }
            break ;
            /* connection closed */
         case SSL_ERROR_ZERO_RETURN:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS read condition SSL_ERROR_ZERO_RETURN on"
                         << " addr = " << inet_ntoa(((struct sockaddr_in *)&peer)->sin_addr)
                         << " port = " << ntohs(((struct sockaddr_in *)&peer)->sin_port)
                         << " error = " << errorString );

               _cleanupConnectionState( ssl, *((struct sockaddr_in *)&peer) ) ;
            }
            break ;
         case SSL_ERROR_WANT_CONNECT:
            break ;
         case SSL_ERROR_WANT_ACCEPT:
            break ;
         default:
            break ;
      }
   }

   if ( len <= 0 )
   {
       return ;
   }

   if ( SSL_in_init( ssl ) )
   {
      mTimer.add( ssl, DtlsReceiveTimeout ) ;
   }

   processRxParseSip(mRxSslBuffer.data(), len, sender);
}

void DtlsTransport::_write( FdSet& fdset )
{
   SSL *ssl ;
   BIO *wBio ;
   int retry = 0 ;

   SendData *sendData ;
   if ( mSendData != nullptr)
       sendData = mSendData ;
   else
       sendData = mTxFifo.getNext() ;

   //DebugLog (<< "Sent: " <<  sendData->data);
   //DebugLog (<< "Sending message on udp.");

   resip_assert( &(*sendData) );
   resip_assert( sendData->destination.getPort() != 0 );

   sockaddr peer = sendData->destination.getSockaddr();

   ssl = mDtlsConnections[ *((struct sockaddr_in *)&peer) ] ;

   /* If we don't have a binding, then we're a client */
   if ( ssl == nullptr)
   {
      ssl = SSL_new( mClientCtx ) ;
      resip_assert( ssl ) ;


      InfoLog( << "DTLS handshake starting (client mode)" );

      SSL_set_connect_state( ssl ) ;

      wBio = BIO_new_dgram( (int)mFd, BIO_NOCLOSE ) ;
      resip_assert( wBio ) ;

      BIO_dgram_set_peer( wBio, &peer) ;

      /* the real rbio will be set by _read */
      SSL_set_bio( ssl, mDummyBio, wBio ) ;

      /* we should be ready to take this out if the
       * connection fails later */
      mDtlsConnections [ *((struct sockaddr_in *)&peer) ] = ssl ;
   }

   int expected;
   int count;

#ifdef USE_SIGCOMP
   // If message needs to be compressed, compress it here.
   if (mSigcompStack &&
       sendData->sigcompId.size() > 0 &&
       !sendData->isAlreadyCompressed )
   {
       osc::SigcompMessage *sm = mSigcompStack->compressMessage
         (sendData->data.data(), sendData->data.size(),
          sendData->sigcompId.data(), sendData->sigcompId.size(),
          isReliable());

       DebugLog (<< "Compressed message from "
                 << sendData->data.size() << " bytes to "
                 << sm->getDatagramLength() << " bytes");

       expected = sm->getDatagramLength();

       count = SSL_write(ssl,
                         sm->getDatagramMessage(),
                         sm->getDatagramLength());
       delete sm;
   }
   else
#endif
   {
      expected = (int)sendData->data.size();

      count = SSL_write(ssl, sendData->data.data(),
                        (int)sendData->data.size());

      if(count < expected)
      {
         WarningLog(<< "expected = " << expected << " but SSL_write only sent " << count);
      }
   }

   /*
    * all reads go through _read, so the most likely result during a handshake
    * will be SSL_ERROR_WANT_READ
    */

   if ( count <= 0 )
   {
      /* cache unqueued data */
      mSendData = sendData ;

      int err = SSL_get_error( ssl, count ) ;

      char errorString[1024];

      switch( err )
      {
         case SSL_ERROR_NONE:
            break;
         case SSL_ERROR_SSL:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS write condition SSL_ERROR_SSL on "
                         << sendData->destination
                         << " error = " << errorString );
            }
            break;
         case SSL_ERROR_WANT_READ:
            retry = 1 ;
            break;
         case SSL_ERROR_WANT_WRITE:
             retry = 1 ;
             fdset.setWrite(mFd);
            break;
         case SSL_ERROR_SYSCALL:
            {
               int e = getErrno();
               error(e);

               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS write condition SSL_ERROR_SYSCALL "
                         << "Failed (" << e << ") sending to "
                         << sendData->destination
                         << " error = " << errorString );

               fail(sendData->transactionId);
            }
            break;
         case SSL_ERROR_ZERO_RETURN:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS write condition SSL_ERROR_ZERO_RETURN on "
                         << sendData->destination
                         << " error = " << errorString );

               _cleanupConnectionState( ssl, *((struct sockaddr_in *)&peer) ) ;
            }
            break ;
         case SSL_ERROR_WANT_CONNECT:
            break;
         case SSL_ERROR_WANT_ACCEPT:
            break;
         default:
            break ;
      }
   }
   else
   {
      mSendData = nullptr;
   }

   /*
    * ngm: is sendData deleted by a higher layer?  Seems to be the case after
    * checking with UdpTransport
    */

   if ( ! retry && count != int(sendData->data.size()) )
   {
      ErrLog (<< "UDPTransport - send buffer full" );
      fail(sendData->transactionId);
   }
}

void
DtlsTransport::_doHandshake( void )
{
   DtlsMessage *msg = mHandshakePending.getNext() ;
   SSL *ssl = msg->getSsl() ;

   delete msg ;

   ERR_clear_error();

   int ret = SSL_do_handshake( ssl ) ;

   if (ret <= 0) {
      int err = SSL_get_error(ssl, ret);

      char errorString[1024];

      switch (err)
      {
         case SSL_ERROR_NONE:
            break;
         case SSL_ERROR_SSL:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS handshake code SSL_ERROR_SSL"
                         << " error = " << errorString );
            }
            break;
         case SSL_ERROR_WANT_READ:
            break;
         case SSL_ERROR_WANT_WRITE:
            break;
         case SSL_ERROR_SYSCALL:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS handshake code SSL_ERROR_SYSCALL"
                         << " error = " << errorString );
            }
            break;
         case SSL_ERROR_ZERO_RETURN:
            {
               ERR_error_string_n(ERR_get_error(), errorString, sizeof(errorString));
               DebugLog( << "Got DTLS handshake code SSL_ERROR_ZERO_RETURN"
                         << " error = " << errorString );
            }
            break;
         case SSL_ERROR_WANT_CONNECT:
            break;
         case SSL_ERROR_WANT_ACCEPT:
            break;
         default:
            break ;
      }
   }
}

void
DtlsTransport::process(FdSet& fdset)
{
   // pull buffers to send out of TxFifo
   // receive datagrams from fd
   // preparse and stuff into RxFifo

   mTimer.process() ;

   while ( mHandshakePending.messageAvailable() )
      _doHandshake() ;

   if ( ( mSendData != nullptr || mTxFifo.messageAvailable() )
       && fdset.readyToWrite( mFd ) )
      _write( fdset ) ;

   // !jf! this may have to change - when we read a message that is too big
   if ( fdset.readyToRead(mFd) )
   {
      _read();
   }
}


void
DtlsTransport::buildFdSet( FdSet& fdset )
{
   fdset.setRead(mFd);

   if ( mSendData != nullptr || mTxFifo.messageAvailable() )
   {
     fdset.setWrite(mFd);
   }
}

void
DtlsTransport::_cleanupConnectionState( SSL *ssl, struct sockaddr_in peer )
{
   /*
    * SSL_free decrements the ref-count for mDummyBio by 1, so
    * add 1 to the ref-count to make sure it does not get free'd
    */
   BIO_up_ref(mDummyBio);
   SSL_shutdown(ssl);
   SSL_free(ssl) ;
   mDtlsConnections.erase(peer) ;
}

void
DtlsTransport::_mapDebug( const char *where, const char *action, SSL *ssl )
{
   fprintf( stderr, "%s: %s\t%p\n", where, action, ssl ) ;
   fprintf( stderr, "map sizet = %d\n", (unsigned int)mDtlsConnections.size() ) ;
}

void
DtlsTransport::_printSock( const struct sockaddr_in *sock )
{
   fprintf( stderr, "addr = %s\t port = %d\n", inet_ntoa( sock->sin_addr ),
            ntohs( sock->sin_port ) ) ;
}

#endif /* USE_DTLS */
#endif /* USE_SSL */

/* ====================================================================
 * The Vovida Software License, Version 1.0
 *
 * Copyright (c) 2000 Vovida Networks, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
 *    and "Vovida Open Communication Application Library (VOCAL)" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact vocal@vovida.org.
 *
 * 4. Products derived from this software may not be called "VOCAL", nor
 *    may "VOCAL" appear in their name, without prior written
 *    permission of Vovida Networks, Inc.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
 * NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * ====================================================================
 *
 * This software consists of voluntary contributions made by Vovida
 * Networks, Inc. and many individuals on behalf of Vovida Networks,
 * Inc.  For more information on Vovida Networks, Inc., please see
 * <http://www.vovida.org/>.
 *
 */
