ehsan.akhgari%gmail.com b3279cb6e5 Bug 480619 - FF does not load objects over secure connection after clear private data, r=kaie, p=bjarne@runitsoft.com
git-svn-id: svn://10.0.0.236/trunk@258302 18797224-902f-48f8-a5cc-f745e15eee43
2009-09-01 19:41:22 +00:00

1148 lines
36 KiB
C++

/* ***** 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;
}
// Socket is unusable - set error and return -1. See bug #480619.
if (si->isPK11LoggedOut() || si->isAlreadyShutDown())
{
PR_SetError(PR_SOCKET_SHUTDOWN_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;
// Socket is unusable - set EXCEPT-flag and return. See bug #480619.
if (si->isPK11LoggedOut() || si->isAlreadyShutDown())
{
*out_flags |= PR_POLL_EXCEPT;
return in_flags;
}
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;