/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Red Hat, Inc.
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Kai Engert <kengert@redhat.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsThreadUtils.h"
#include "nsSSLThread.h"
#include "nsAutoLock.h"
#include "nsNSSIOLayer.h"

#include "ssl.h"

#ifdef PR_LOGGING
extern PRLogModuleInfo* gPIPNSSLog;
#endif

nsSSLThread::nsSSLThread()
: mBusySocket(nsnull),
  mSocketScheduledToBeDestroyed(nsnull)
{
  NS_ASSERTION(!ssl_thread_singleton, "nsSSLThread is a singleton, caller attempts to create another instance!");
  
  ssl_thread_singleton = this;
}

nsSSLThread::~nsSSLThread()
{
  ssl_thread_singleton = nsnull;
}

PRFileDesc *nsSSLThread::getRealSSLFD(nsNSSSocketInfo *si)
{
  if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle)
    return nsnull;

  nsAutoLock threadLock(ssl_thread_singleton->mMutex);

  if (si->mThreadData->mReplacedSSLFileDesc)
  {
    return si->mThreadData->mReplacedSSLFileDesc;
  }
  else
  {
    return si->mFd->lower;
  }
}

PRStatus nsSSLThread::requestGetsockname(nsNSSSocketInfo *si, PRNetAddr *addr)
{
  PRFileDesc *fd = getRealSSLFD(si);
  if (!fd)
    return PR_FAILURE;

  return fd->methods->getsockname(fd, addr);
}

PRStatus nsSSLThread::requestGetpeername(nsNSSSocketInfo *si, PRNetAddr *addr)
{
  PRFileDesc *fd = getRealSSLFD(si);
  if (!fd)
    return PR_FAILURE;

  return fd->methods->getpeername(fd, addr);
}

PRStatus nsSSLThread::requestGetsocketoption(nsNSSSocketInfo *si, 
                                             PRSocketOptionData *data)
{
  PRFileDesc *fd = getRealSSLFD(si);
  if (!fd)
    return PR_FAILURE;

  return fd->methods->getsocketoption(fd, data);
}

PRStatus nsSSLThread::requestSetsocketoption(nsNSSSocketInfo *si, 
                                             const PRSocketOptionData *data)
{
  PRFileDesc *fd = getRealSSLFD(si);
  if (!fd)
    return PR_FAILURE;

  return fd->methods->setsocketoption(fd, data);
}

PRStatus nsSSLThread::requestConnectcontinue(nsNSSSocketInfo *si, 
                                             PRInt16 out_flags)
{
  PRFileDesc *fd = getRealSSLFD(si);
  if (!fd)
    return PR_FAILURE;

  return fd->methods->connectcontinue(fd, out_flags);
}

PRInt32 nsSSLThread::requestRecvMsgPeek(nsNSSSocketInfo *si, void *buf, PRInt32 amount,
                                        PRIntn flags, PRIntervalTime timeout)
{
  if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle)
  {
    PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
    return -1;
  }

  PRFileDesc *realSSLFD;

  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);

    if (si == ssl_thread_singleton->mBusySocket)
    {
      PORT_SetError(PR_WOULD_BLOCK_ERROR);
      return -1;
    }

    switch (si->mThreadData->mSSLState)
    {
      case nsSSLSocketThreadData::ssl_idle:
        break;
    
      case nsSSLSocketThreadData::ssl_reading_done:
        {
          // we have data available that we can return

          // if there was a failure, just return the failure,
          // but do not yet clear our state, that should happen
          // in the call to "read".

          if (si->mThreadData->mSSLResultRemainingBytes < 0) {
            if (si->mThreadData->mPRErrorCode != PR_SUCCESS) {
              PR_SetError(si->mThreadData->mPRErrorCode, 0);
            }

            return si->mThreadData->mSSLResultRemainingBytes;
          }

          PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes);

          memcpy(buf, si->mThreadData->mSSLRemainingReadResultData, return_amount);

          return return_amount;
        }

      case nsSSLSocketThreadData::ssl_writing_done:
      case nsSSLSocketThreadData::ssl_pending_write:
      case nsSSLSocketThreadData::ssl_pending_read:

      // for safety reasons, also return would_block on any other state,
      // although this switch statement should be complete and list
      // the appropriate behaviour for each state.
      default:
        {
          PORT_SetError(PR_WOULD_BLOCK_ERROR);
          return -1;
        }
    }

    if (si->mThreadData->mReplacedSSLFileDesc)
    {
      realSSLFD = si->mThreadData->mReplacedSSLFileDesc;
    }
    else
    {
      realSSLFD = si->mFd->lower;
    }
  }

  return realSSLFD->methods->recv(realSSLFD, buf, amount, flags, timeout);
}

nsresult nsSSLThread::requestActivateSSL(nsNSSSocketInfo *si)
{
  PRFileDesc *fd = getRealSSLFD(si);
  if (!fd)
    return NS_ERROR_FAILURE;

  if (SECSuccess != SSL_OptionSet(fd, SSL_SECURITY, PR_TRUE))
    return NS_ERROR_FAILURE;

  if (SECSuccess != SSL_ResetHandshake(fd, PR_FALSE))
    return NS_ERROR_FAILURE;

  return NS_OK;
}

PRInt16 nsSSLThread::requestPoll(nsNSSSocketInfo *si, PRInt16 in_flags, PRInt16 *out_flags)
{
  if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle)
    return 0;

  *out_flags = 0;

  PRBool want_sleep_and_wakeup_on_any_socket_activity = PR_FALSE;
  PRBool handshake_timeout = PR_FALSE;
  
  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);

    if (ssl_thread_singleton->mBusySocket)
    {
      // If there is currently any socket busy on the SSL thread,
      // use our own poll method implementation.
      
      switch (si->mThreadData->mSSLState)
      {
        case nsSSLSocketThreadData::ssl_writing_done:
        {
          if (in_flags & PR_POLL_WRITE)
          {
            *out_flags |= PR_POLL_WRITE;
          }

          return in_flags;
        }
        break;
        
        case nsSSLSocketThreadData::ssl_reading_done:
        {
          if (in_flags & PR_POLL_READ)
          {
            *out_flags |= PR_POLL_READ;
          }

          return in_flags;
        }
        break;
        
        case nsSSLSocketThreadData::ssl_pending_write:
        case nsSSLSocketThreadData::ssl_pending_read:
        {
          if (si == ssl_thread_singleton->mBusySocket)
          {
            if (nsSSLIOLayerHelpers::mSharedPollableEvent)
            {
              // The lower layer of the socket is currently the pollable event,
              // which signals the readable state.
              
              return PR_POLL_READ;
            }
            else
            {
              // Unfortunately we do not have a pollable event
              // that we could use to wake up the caller, as soon
              // as the previously requested I/O operation has completed.
              // Therefore we must use a kind of busy wait,
              // we want the caller to check again, whenever any
              // activity is detected on the associated socket.
              // Unfortunately this could mean, the caller will detect
              // activity very often, until we are finally done with
              // the previously requested action and are able to
              // return the buffered result.
              // As our real I/O activity is happening on the other thread
              // let's sleep some cycles, in order to not waste all CPU
              // resources.
              // But let's make sure we do not hold our shared mutex
              // while waiting, so let's leave this block first.

              want_sleep_and_wakeup_on_any_socket_activity = PR_TRUE;
              break;
            }
          }
          else
          {
            // We should never get here, well, at least not with the current
            // implementation of SSL thread, where we have one worker only.
            // While another socket is busy, this socket "si" 
            // can not be marked with pending I/O at the same time.
            
            NS_NOTREACHED("Socket not busy on SSL thread marked as pending");
            return 0;
          }
        }
        break;
        
        case nsSSLSocketThreadData::ssl_idle:
        {
          if (si->mThreadData->mOneBytePendingFromEarlierWrite)
          {
            if (in_flags & PR_POLL_WRITE)
            {
              // In this scenario we always want the caller to immediately
              // try a write again, because it might not wake up otherwise.
              *out_flags |= PR_POLL_WRITE;
              return in_flags;
            }
          }

          handshake_timeout = si->HandshakeTimeout();

          if (si != ssl_thread_singleton->mBusySocket)
          {
            // Some other socket is currently busy on the SSL thread.
            // It is possible that busy socket is currently blocked (e.g. by UI).
            // Therefore we should not report "si" as being readable/writeable,
            // regardless whether it is.
            // (Because if we did report readable/writeable to the caller,
            // the caller would repeatedly request us to do I/O, 
            // although our read/write function would not be able to fulfil
            // the request, because our single worker is blocked).
            // To avoid the unnecessary busy loop in that scenario, 
            // for socket "si" we report "not ready" to the caller.
            // We do this by faking our caller did not ask for neither
            // readable nor writeable when querying the lower layer.
            // (this will leave querying for exceptions enabled)
            
            in_flags &= ~(PR_POLL_READ | PR_POLL_WRITE);
          }
        }
        break;
        
        default:
          break;
      }
    }
    else
    {
      handshake_timeout = si->HandshakeTimeout();
    }

    if (handshake_timeout)
    {
      NS_ASSERTION(in_flags & PR_POLL_EXCEPT, "nsSSLThread::requestPoll handshake timeout, but caller did not poll for EXCEPT");

      *out_flags |= PR_POLL_EXCEPT;
      return in_flags;
    }
  }

  if (want_sleep_and_wakeup_on_any_socket_activity)
  {
    // This is where we wait for any socket activity,
    // because we do not have a pollable event.
    // XXX Will this really cause us to wake up
    //     whatever happens?

    PR_Sleep( PR_MillisecondsToInterval(1) );
    return PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT;
  }

  return si->mFd->lower->methods->poll(si->mFd->lower, in_flags, out_flags);
}

PRStatus nsSSLThread::requestClose(nsNSSSocketInfo *si)
{
  if (!ssl_thread_singleton || !si)
    return PR_FAILURE;

  PRBool close_later = PR_FALSE;
  nsCOMPtr<nsIRequest> requestToCancel;

  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);

    if (ssl_thread_singleton->mBusySocket == si) {
    
      // That's tricky, SSL thread is currently busy with this socket,
      // and might even be blocked on it (UI or OCSP).
      // We should not close the socket directly, but rather
      // schedule closing it, at the time the SSL thread is done.
      // If there is indeed a depending OCSP request pending,
      // we should cancel it now.
      
      if (ssl_thread_singleton->mPendingHTTPRequest)
      {
        requestToCancel.swap(ssl_thread_singleton->mPendingHTTPRequest);
      }
      
      close_later = PR_TRUE;
      ssl_thread_singleton->mSocketScheduledToBeDestroyed = si;

      PR_NotifyAllCondVar(ssl_thread_singleton->mCond);
    }
  }

  if (requestToCancel)
  {
    if (NS_IsMainThread())
    {
      requestToCancel->Cancel(NS_ERROR_ABORT);
    }
    else
    {
      NS_WARNING("Attempt to close SSL socket from a thread that is not the main thread. Can not cancel pending HTTP request from NSS");
    }
  
    requestToCancel = nsnull;
  }
  
  if (!close_later)
  {
    return si->CloseSocketAndDestroy();
  }
  
  return PR_SUCCESS;
}

void nsSSLThread::restoreOriginalSocket_locked(nsNSSSocketInfo *si)
{
  if (si->mThreadData->mReplacedSSLFileDesc)
  {
    if (nsSSLIOLayerHelpers::mPollableEventCurrentlySet)
    {
      nsSSLIOLayerHelpers::mPollableEventCurrentlySet = PR_FALSE;
      if (nsSSLIOLayerHelpers::mSharedPollableEvent)
      {
        PR_WaitForPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent);
      }
    }

    if (nsSSLIOLayerHelpers::mSharedPollableEvent)
    {
      // need to restore
      si->mFd->lower = si->mThreadData->mReplacedSSLFileDesc;
      si->mThreadData->mReplacedSSLFileDesc = nsnull;
    }

    nsSSLIOLayerHelpers::mSocketOwningPollableEvent = nsnull;
  }
}

PRStatus nsSSLThread::getRealFDIfBlockingSocket_locked(nsNSSSocketInfo *si, 
                                                       PRFileDesc *&out_fd)
{
  out_fd = nsnull;

  PRFileDesc *realFD = 
    (si->mThreadData->mReplacedSSLFileDesc) ?
      si->mThreadData->mReplacedSSLFileDesc : si->mFd->lower;

  if (si->mBlockingState == nsNSSSocketInfo::blocking_state_unknown)
  {
    PRSocketOptionData sod;
    sod.option = PR_SockOpt_Nonblocking;
    if (PR_GetSocketOption(realFD, &sod) == PR_FAILURE)
      return PR_FAILURE;

    si->mBlockingState = sod.value.non_blocking ?
      nsNSSSocketInfo::is_nonblocking_socket : nsNSSSocketInfo::is_blocking_socket;
  }

  if (si->mBlockingState == nsNSSSocketInfo::is_blocking_socket)
  {
    out_fd = realFD;
  }

  return PR_SUCCESS;
}

PRInt32 nsSSLThread::requestRead(nsNSSSocketInfo *si, void *buf, PRInt32 amount, 
                                 PRIntervalTime timeout)
{
  if (!ssl_thread_singleton || !si || !buf || !amount || !ssl_thread_singleton->mThreadHandle)
  {
    PR_SetError(PR_UNKNOWN_ERROR, 0);
    return -1;
  }

  PRBool this_socket_is_busy = PR_FALSE;
  PRBool some_other_socket_is_busy = PR_FALSE;
  nsSSLSocketThreadData::ssl_state my_ssl_state = nsSSLSocketThreadData::ssl_invalid;
  PRFileDesc *blockingFD = nsnull;

  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);

    if (ssl_thread_singleton->mExitRequested) {
      PR_SetError(PR_UNKNOWN_ERROR, 0);
      return -1;
    }

    if (getRealFDIfBlockingSocket_locked(si, blockingFD) == PR_FAILURE) {
      return -1;
    }

    if (!blockingFD)
    {
      my_ssl_state = si->mThreadData->mSSLState;
  
      if (ssl_thread_singleton->mBusySocket == si)
      {
        this_socket_is_busy = PR_TRUE;
  
        if (my_ssl_state == nsSSLSocketThreadData::ssl_reading_done)
        {
          // we will now care for the data that's ready,
          // the socket is no longer busy on the ssl thread
          
          restoreOriginalSocket_locked(si);
  
          ssl_thread_singleton->mBusySocket = nsnull;
          
          // We'll handle the results further down,
          // while not holding the lock.
        }
      }
      else if (ssl_thread_singleton->mBusySocket)
      {
        some_other_socket_is_busy = PR_TRUE;
      }
  
      if (!this_socket_is_busy && si->HandshakeTimeout())
      {
        restoreOriginalSocket_locked(si);
        PR_SetError(PR_CONNECT_RESET_ERROR, 0);
        checkHandshake(-1, PR_TRUE, si->mFd->lower, si);
        return -1;
      }
    }
    // leave this mutex protected scope before the blockingFD handling
  }

  if (blockingFD)
  {
    // this is an exception, we do not use our SSL thread at all,
    // just pass the call through to libssl.
    return blockingFD->methods->recv(blockingFD, buf, amount, 0, timeout);
  }

  switch (my_ssl_state)
  {
    case nsSSLSocketThreadData::ssl_idle:
      {
        NS_ASSERTION(!this_socket_is_busy, "oops, unexpected incosistency");
        
        if (some_other_socket_is_busy)
        {
          PORT_SetError(PR_WOULD_BLOCK_ERROR);
          return -1;
        }
        
        // ssl thread is not busy, we'll continue below
      }
      break;

    case nsSSLSocketThreadData::ssl_reading_done:
      // there has been a previous request to read, that is now done!
      {
        // failure ?
        if (si->mThreadData->mSSLResultRemainingBytes < 0) {
          if (si->mThreadData->mPRErrorCode != PR_SUCCESS) {
            PR_SetError(si->mThreadData->mPRErrorCode, 0);
            si->mThreadData->mPRErrorCode = PR_SUCCESS;
          }

          si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle;
          return si->mThreadData->mSSLResultRemainingBytes;
        }

        PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes);

        memcpy(buf, si->mThreadData->mSSLRemainingReadResultData, return_amount);

        si->mThreadData->mSSLResultRemainingBytes -= return_amount;

        if (!si->mThreadData->mSSLResultRemainingBytes) {
          si->mThreadData->mSSLRemainingReadResultData = nsnull;
          si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle;
        }
        else {
          si->mThreadData->mSSLRemainingReadResultData += return_amount;
        }

        return return_amount;
      }
      // we never arrive here, see return statement above
      break;


    // We should not see the following events here,
    // because we have not yet signaled Necko that we are 
    // readable/writable again, so if we end up here,
    // it means that Necko decided to try read/write again,
    // for whatever reason. No problem, just return would_block,
    case nsSSLSocketThreadData::ssl_pending_write:
    case nsSSLSocketThreadData::ssl_pending_read:

    // We should not see this state here, because Necko has previously
    // requested us to write, Necko is not yet aware that it's done,
    // (although it meanwhile is), but Necko now tries to read?
    // If that ever happens, it's confusing, but not a problem,
    // just let Necko know we can not do that now and return would_block.
    case nsSSLSocketThreadData::ssl_writing_done:

    // for safety reasons, also return would_block on any other state,
    // although this switch statement should be complete and list
    // the appropriate behaviour for each state.
    default:
      {
        PORT_SetError(PR_WOULD_BLOCK_ERROR);
        return -1;
      }
      // we never arrive here, see return statement above
      break;
  }

  if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) {
    PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0);
    return -1;
  }

  if (si->GetCanceled()) {
    return PR_FAILURE;
  }

  // si is idle and good, and no other socket is currently busy,
  // so it's fine to continue with the request.

  if (!si->mThreadData->ensure_buffer_size(amount))
  {
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    return -1;
  }
  
  si->mThreadData->mSSLRequestedTransferAmount = amount;
  si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_pending_read;

  // Remember we are operating on a layered file descriptor, that consists of
  // a PSM code layer (nsNSSIOLayer), a NSS code layer (SSL protocol logic), 
  // and the raw socket at the bottommost layer.
  //
  // We don't want to call the SSL layer read/write directly on this thread, 
  // because it might block, should a callback to UI (for user confirmation)
  // or Necko (for retrieving OCSP verification data) be necessary.
  // As Necko is single threaded, it is currently waiting for this 
  // function to return, and a callback into Necko from NSS couldn't succeed.
  // 
  // Therefore we must defer the request to read/write to a separate SSL thread.
  // We will return WOULD_BLOCK to Necko, and will return the real results
  // once the I/O operation on the SSL thread is ready.
  //
  // The tricky part is to wake up Necko, as soon as the I/O operation 
  // on the SSL thread is done.
  //
  // In order to achieve that, we manipulate the layering of the file 
  // descriptor. Usually the PSM layer points to the SSL layer as its lower 
  // layer. We change that to a pollable event file descriptor.
  //
  // Once we return from this original read/write function, Necko will 
  // poll/select on the file descriptor. As result data is not yet ready, we will 
  // instruct Necko to select on the bottommost file descriptor 
  // (by using appropriate flags in PSM's layer implementation of the 
  // poll method), which is the pollable event.
  //
  // Once the SSL thread is done with the call to the SSL layer, it will 
  // "set" the pollable event, causing Necko to wake up on the file descriptor 
  // and call read/write again. Now that the file descriptor is in the done state, 
  // we'll arrive in this read/write function again. We'll detect the socket is 
  // in the done state, and restore the original SSL level file descriptor.
  // Finally, we return the data obtained on the SSL thread back to our caller.

  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);

    if (nsSSLIOLayerHelpers::mSharedPollableEvent)
    {
      NS_ASSERTION(!nsSSLIOLayerHelpers::mSocketOwningPollableEvent, 
                   "oops, some other socket still owns our shared pollable event");
  
      NS_ASSERTION(!si->mThreadData->mReplacedSSLFileDesc, "oops");
  
      si->mThreadData->mReplacedSSLFileDesc = si->mFd->lower;
      si->mFd->lower = nsSSLIOLayerHelpers::mSharedPollableEvent;
    }

    nsSSLIOLayerHelpers::mSocketOwningPollableEvent = si;
    ssl_thread_singleton->mBusySocket = si;

    // notify the thread
    PR_NotifyAllCondVar(ssl_thread_singleton->mCond);
  }

  PORT_SetError(PR_WOULD_BLOCK_ERROR);
  return -1;
}

PRInt32 nsSSLThread::requestWrite(nsNSSSocketInfo *si, const void *buf, PRInt32 amount,
                                  PRIntervalTime timeout)
{
  if (!ssl_thread_singleton || !si || !buf || !amount || !ssl_thread_singleton->mThreadHandle)
  {
    PR_SetError(PR_UNKNOWN_ERROR, 0);
    return -1;
  }

  PRBool this_socket_is_busy = PR_FALSE;
  PRBool some_other_socket_is_busy = PR_FALSE;
  nsSSLSocketThreadData::ssl_state my_ssl_state = nsSSLSocketThreadData::ssl_invalid;
  PRFileDesc *blockingFD = nsnull;
  
  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);
    
    if (ssl_thread_singleton->mExitRequested) {
      PR_SetError(PR_UNKNOWN_ERROR, 0);
      return -1;
    }

    if (getRealFDIfBlockingSocket_locked(si, blockingFD) == PR_FAILURE) {
      return -1;
    }

    if (!blockingFD)
    {
      my_ssl_state = si->mThreadData->mSSLState;
  
      if (ssl_thread_singleton->mBusySocket == si)
      {
        this_socket_is_busy = PR_TRUE;
        
        if (my_ssl_state == nsSSLSocketThreadData::ssl_writing_done)
        {
          // we will now care for the data that's ready,
          // the socket is no longer busy on the ssl thread
          
          restoreOriginalSocket_locked(si);
  
          ssl_thread_singleton->mBusySocket = nsnull;
          
          // We'll handle the results further down,
          // while not holding the lock.
        }
      }
      else if (ssl_thread_singleton->mBusySocket)
      {
        some_other_socket_is_busy = PR_TRUE;
      }
  
      if (!this_socket_is_busy && si->HandshakeTimeout())
      {
        restoreOriginalSocket_locked(si);
        PR_SetError(PR_CONNECT_RESET_ERROR, 0);
        checkHandshake(-1, PR_FALSE, si->mFd->lower, si);
        return -1;
      }
    }
    // leave this mutex protected scope before the blockingFD handling
  }

  if (blockingFD)
  {
    // this is an exception, we do not use our SSL thread at all,
    // just pass the call through to libssl.
    return blockingFD->methods->send(blockingFD, buf, amount, 0, timeout);
  }

  switch (my_ssl_state)
  {
    case nsSSLSocketThreadData::ssl_idle:
      {
        NS_ASSERTION(!this_socket_is_busy, "oops, unexpected incosistency");
        
        if (some_other_socket_is_busy)
        {
          PORT_SetError(PR_WOULD_BLOCK_ERROR);
          return -1;
        }
        
        // ssl thread is not busy, we'll continue below
      }
      break;

    case nsSSLSocketThreadData::ssl_writing_done:
      // there has been a previous request to write, that is now done!
      {
        // failure ?
        if (si->mThreadData->mSSLResultRemainingBytes < 0) {
          if (si->mThreadData->mPRErrorCode != PR_SUCCESS) {
            PR_SetError(si->mThreadData->mPRErrorCode, 0);
            si->mThreadData->mPRErrorCode = PR_SUCCESS;
          }

          si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle;
          return si->mThreadData->mSSLResultRemainingBytes;
        }

        PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes);

        si->mThreadData->mSSLResultRemainingBytes -= return_amount;

        if (!si->mThreadData->mSSLResultRemainingBytes) {
          si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle;
        }

        return return_amount;
      }
      break;
      
    // We should not see the following events here,
    // because we have not yet signaled Necko that we are 
    // readable/writable again, so if we end up here,
    // it means that Necko decided to try read/write again,
    // for whatever reason. No problem, just return would_block,
    case nsSSLSocketThreadData::ssl_pending_write:
    case nsSSLSocketThreadData::ssl_pending_read:

    // We should not see this state here, because Necko has previously
    // requested us to read, Necko is not yet aware that it's done,
    // (although it meanwhile is), but Necko now tries to write?
    // If that ever happens, it's confusing, but not a problem,
    // just let Necko know we can not do that now and return would_block.
    case nsSSLSocketThreadData::ssl_reading_done:

    // for safety reasons, also return would_block on any other state,
    // although this switch statement should be complete and list
    // the appropriate behaviour for each state.
    default:
      {
        PORT_SetError(PR_WOULD_BLOCK_ERROR);
        return -1;
      }
      // we never arrive here, see return statement above
      break;
  }

  if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) {
    PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0);
    return -1;
  }

  if (si->GetCanceled()) {
    return PR_FAILURE;
  }

  // si is idle and good, and no other socket is currently busy,
  // so it's fine to continue with the request.

  // However, use special handling for the 
  //   mOneBytePendingFromEarlierWrite
  // scenario, where we will not change any of our buffers at this point, 
  // as we are waiting for completion of the earlier write.

  if (!si->mThreadData->mOneBytePendingFromEarlierWrite)
  {
    if (!si->mThreadData->ensure_buffer_size(amount))
    {
      PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
      return -1;
    }
  
    memcpy(si->mThreadData->mSSLDataBuffer, buf, amount);
    si->mThreadData->mSSLRequestedTransferAmount = amount;
  }

  si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_pending_write;

  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);

    if (nsSSLIOLayerHelpers::mSharedPollableEvent)
    {
      NS_ASSERTION(!nsSSLIOLayerHelpers::mSocketOwningPollableEvent, 
                   "oops, some other socket still owns our shared pollable event");
  
      NS_ASSERTION(!si->mThreadData->mReplacedSSLFileDesc, "oops");
  
      si->mThreadData->mReplacedSSLFileDesc = si->mFd->lower;
      si->mFd->lower = nsSSLIOLayerHelpers::mSharedPollableEvent;
    }

    nsSSLIOLayerHelpers::mSocketOwningPollableEvent = si;
    ssl_thread_singleton->mBusySocket = si;

    PR_NotifyAllCondVar(ssl_thread_singleton->mCond);
  }

  PORT_SetError(PR_WOULD_BLOCK_ERROR);
  return -1;
}

void nsSSLThread::Run(void)
{
  // Helper variable, we don't want to call destroy 
  // while holding the mutex.
  nsNSSSocketInfo *socketToDestroy = nsnull;

  while (PR_TRUE)
  {
    if (socketToDestroy)
    {
      socketToDestroy->CloseSocketAndDestroy();
      socketToDestroy = nsnull;
    }

    // remember whether we'll write or read
    nsSSLSocketThreadData::ssl_state busy_socket_ssl_state;
  
    {
      // In this scope we need mutex protection,
      // as we find out what needs to be done.
      
      nsAutoLock threadLock(ssl_thread_singleton->mMutex);
      
      if (mSocketScheduledToBeDestroyed)
      {
        if (mBusySocket == mSocketScheduledToBeDestroyed)
        {
          // That's rare, but it happens.
          // We have received a request to close the socket,
          // although I/O results have not yet been consumed.

          restoreOriginalSocket_locked(mBusySocket);

          mBusySocket->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle;
          mBusySocket = nsnull;
        }
      
        socketToDestroy = mSocketScheduledToBeDestroyed;
        mSocketScheduledToBeDestroyed = nsnull;
        continue; // go back and finally destroy it, before doing anything else
      }

      if (mExitRequested)
        break;

      PRBool pending_work = PR_FALSE;

      do
      {
        if (mBusySocket
            &&
              (mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_read
              ||
              mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_write))
        {
          pending_work = PR_TRUE;
        }

        if (!pending_work)
        {
          // no work to do ? let's wait a moment

          PR_WaitCondVar(mCond, PR_INTERVAL_NO_TIMEOUT);
        }
        
      } while (!pending_work && !mExitRequested && !mSocketScheduledToBeDestroyed);
      
      if (mSocketScheduledToBeDestroyed)
        continue;
      
      if (mExitRequested)
        break;
      
      if (!pending_work)
        continue;
      
      busy_socket_ssl_state = mBusySocket->mThreadData->mSSLState;
    }

    {
      // In this scope we need to make sure NSS does not go away
      // while we are busy.
      nsNSSShutDownPreventionLock locker;

      // Reference for shorter code and to avoid multiple dereferencing.
      nsSSLSocketThreadData &bstd = *mBusySocket->mThreadData;

      PRFileDesc *realFileDesc = bstd.mReplacedSSLFileDesc;
      if (!realFileDesc)
      {
        realFileDesc = mBusySocket->mFd->lower;
      }

      if (nsSSLSocketThreadData::ssl_pending_write == busy_socket_ssl_state)
      {
        PRInt32 bytesWritten = 0;

        if (bstd.mOneBytePendingFromEarlierWrite)
        {
          // Let's try to flush the final pending byte (that libSSL might already have 
          // processed). Let's be correct and send the final byte from our buffer.
          bytesWritten = realFileDesc->methods
            ->write(realFileDesc, &bstd.mThePendingByte, 1);
  
#ifdef DEBUG_SSL_VERBOSE
          PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes\n", (void*)realFileDesc, bytesWritten));
#endif
          
          bytesWritten = checkHandshake(bytesWritten, PR_FALSE, realFileDesc, mBusySocket);
          if (bytesWritten < 0) {
            // give the error back to caller
            bstd.mPRErrorCode = PR_GetError();
          }
          else if (bytesWritten == 1) {
            // Cool, all flushed now. We can exit the one-byte-pending mode, 
            // and report the full amount back to the caller. 
            bytesWritten = bstd.mOriginalRequestedTransferAmount;
            bstd.mOriginalRequestedTransferAmount = 0;
            bstd.mOneBytePendingFromEarlierWrite = PR_FALSE;
          }
        }
        else
        {
          // standard code, try to write the buffer we've been given just now 
          bytesWritten = realFileDesc->methods
            ->write(realFileDesc, 
                    bstd.mSSLDataBuffer, 
                    bstd.mSSLRequestedTransferAmount);
  
#ifdef DEBUG_SSL_VERBOSE
          PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes (out of %d)\n", 
              (void*)realFileDesc, bytesWritten, bstd.mSSLRequestedTransferAmount));
#endif
          
          bytesWritten = checkHandshake(bytesWritten, PR_FALSE, realFileDesc, mBusySocket);
          if (bytesWritten < 0) {
            // give the error back to caller
            bstd.mPRErrorCode = PR_GetError();
          }
          else if (bstd.mSSLRequestedTransferAmount > 1 && 
                   bytesWritten == (bstd.mSSLRequestedTransferAmount - 1)) {
            // libSSL signaled us a short write.
            // While libSSL accepted all data, not all bytes were flushed to the OS socket.
            bstd.mThePendingByte = *(bstd.mSSLDataBuffer + (bstd.mSSLRequestedTransferAmount-1));
            bytesWritten = -1;
            bstd.mPRErrorCode = PR_WOULD_BLOCK_ERROR;
            bstd.mOneBytePendingFromEarlierWrite = PR_TRUE;
            bstd.mOriginalRequestedTransferAmount = bstd.mSSLRequestedTransferAmount;
          }
        }

        bstd.mSSLResultRemainingBytes = bytesWritten;
        busy_socket_ssl_state = nsSSLSocketThreadData::ssl_writing_done;
      }
      else if (nsSSLSocketThreadData::ssl_pending_read == busy_socket_ssl_state)
      {
        PRInt32 bytesRead = realFileDesc->methods
           ->read(realFileDesc, 
                  bstd.mSSLDataBuffer, 
                  bstd.mSSLRequestedTransferAmount);

#ifdef DEBUG_SSL_VERBOSE
        PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] read %d bytes\n", (void*)realFileDesc, bytesRead));
#endif
        bytesRead = checkHandshake(bytesRead, PR_TRUE, realFileDesc, mBusySocket);
        if (bytesRead < 0) {
          // give the error back to caller
          bstd.mPRErrorCode = PR_GetError();
        }

        bstd.mSSLResultRemainingBytes = bytesRead;
        bstd.mSSLRemainingReadResultData = bstd.mSSLDataBuffer;
        busy_socket_ssl_state = nsSSLSocketThreadData::ssl_reading_done;
      }
    }

    // avoid setting event repeatedly
    PRBool needToSetPollableEvent = PR_FALSE;

    {
      nsAutoLock threadLock(ssl_thread_singleton->mMutex);
      
      mBusySocket->mThreadData->mSSLState = busy_socket_ssl_state;
      
      if (!nsSSLIOLayerHelpers::mPollableEventCurrentlySet)
      {
        needToSetPollableEvent = PR_TRUE;
        nsSSLIOLayerHelpers::mPollableEventCurrentlySet = PR_TRUE;
      }
    }

    if (needToSetPollableEvent && nsSSLIOLayerHelpers::mSharedPollableEvent)
    {
      // Wake up the file descriptor on the Necko thread,
      // so it can fetch the results from the SSL I/O call 
      // that we just completed.
      PR_SetPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent);

      // if we don't have a pollable event, we'll have to wake up
      // the caller by other means.
    }
  }

  {
    nsAutoLock threadLock(ssl_thread_singleton->mMutex);
    if (mBusySocket)
    {
      restoreOriginalSocket_locked(mBusySocket);
      mBusySocket = nsnull;
    }
    if (!nsSSLIOLayerHelpers::mPollableEventCurrentlySet)
    {
      nsSSLIOLayerHelpers::mPollableEventCurrentlySet = PR_TRUE;
      if (nsSSLIOLayerHelpers::mSharedPollableEvent)
      {
        PR_SetPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent);
      }
    }
  }
}

PRBool nsSSLThread::exitRequested()
{
  if (!ssl_thread_singleton)
    return PR_FALSE;

  // no lock

  return ssl_thread_singleton->mExitRequested;
}

nsSSLThread *nsSSLThread::ssl_thread_singleton = nsnull;
