Blob Blame History Raw
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// HttpLog.h should generally be included first
#include "HttpLog.h"

#include "Http2Session.h"
#include "nsHttp.h"
#include "nsHttpHandler.h"
#include "nsHttpRequestHead.h"
#include "TCPFastOpen.h"
#include "nsISocketProvider.h"
#include "nsISocketProviderService.h"
#include "nsISSLSocketControl.h"
#include "nsISocketTransport.h"
#include "nsISupportsPriority.h"
#include "nsNetAddr.h"
#include "prerror.h"
#include "prio.h"
#include "TunnelUtils.h"
#include "nsNetCID.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsSocketTransportService2.h"

namespace mozilla {
namespace net {

static PRDescIdentity sLayerIdentity;
static PRIOMethods sLayerMethods;
static PRIOMethods *sLayerMethodsPtr = nullptr;

TLSFilterTransaction::TLSFilterTransaction(nsAHttpTransaction *aWrapped,
                                           const char *aTLSHost,
                                           int32_t aTLSPort,
                                           nsAHttpSegmentReader *aReader,
                                           nsAHttpSegmentWriter *aWriter)
    : mTransaction(aWrapped),
      mEncryptedTextUsed(0),
      mEncryptedTextSize(0),
      mSegmentReader(aReader),
      mSegmentWriter(aWriter),
      mForce(false),
      mNudgeCounter(0) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("TLSFilterTransaction ctor %p\n", this));

  nsCOMPtr<nsISocketProvider> provider;
  nsCOMPtr<nsISocketProviderService> spserv =
      do_GetService(NS_SOCKETPROVIDERSERVICE_CONTRACTID);

  if (spserv) {
    spserv->GetSocketProvider("ssl", getter_AddRefs(provider));
  }

  // Install an NSPR layer to handle getpeername() with a failure. This is kind
  // of silly, but the default one used by the pipe asserts when called and the
  // nss code calls it to see if we are connected to a real socket or not.
  if (!sLayerMethodsPtr) {
    // one time initialization
    sLayerIdentity = PR_GetUniqueIdentity("TLSFilterTransaction Layer");
    sLayerMethods = *PR_GetDefaultIOMethods();
    sLayerMethods.getpeername = GetPeerName;
    sLayerMethods.getsocketoption = GetSocketOption;
    sLayerMethods.setsocketoption = SetSocketOption;
    sLayerMethods.read = FilterRead;
    sLayerMethods.write = FilterWrite;
    sLayerMethods.send = FilterSend;
    sLayerMethods.recv = FilterRecv;
    sLayerMethods.close = FilterClose;
    sLayerMethodsPtr = &sLayerMethods;
  }

  mFD = PR_CreateIOLayerStub(sLayerIdentity, &sLayerMethods);

  if (provider && mFD) {
    mFD->secret = reinterpret_cast<PRFilePrivate *>(this);
    provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr,
                          OriginAttributes(), 0, 0, mFD,
                          getter_AddRefs(mSecInfo));
  }

  if (mTransaction) {
    nsCOMPtr<nsIInterfaceRequestor> callbacks;
    mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
    nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
    if (secCtrl) {
      secCtrl->SetNotificationCallbacks(callbacks);
    }
  }
}

TLSFilterTransaction::~TLSFilterTransaction() {
  LOG(("TLSFilterTransaction dtor %p\n", this));
  Cleanup();
}

void TLSFilterTransaction::Cleanup() {
  if (mTransaction) {
    mTransaction->Close(NS_ERROR_ABORT);
    mTransaction = nullptr;
  }

  if (mFD) {
    PR_Close(mFD);
    mFD = nullptr;
  }
  mSecInfo = nullptr;
  if (mTimer) {
    mTimer->Cancel();
    mTimer = nullptr;
  }
}

void TLSFilterTransaction::Close(nsresult aReason) {
  if (!mTransaction) {
    return;
  }

  if (mTimer) {
    mTimer->Cancel();
    mTimer = nullptr;
  }
  mTransaction->Close(aReason);
  mTransaction = nullptr;
}

nsresult TLSFilterTransaction::OnReadSegment(const char *aData, uint32_t aCount,
                                             uint32_t *outCountRead) {
  LOG(("TLSFilterTransaction %p OnReadSegment %d (buffered %d)\n", this, aCount,
       mEncryptedTextUsed));

  mReadSegmentBlocked = false;
  MOZ_ASSERT(mSegmentReader);
  if (!mSecInfo) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv;
  *outCountRead = 0;

  // get rid of buffer first
  if (mEncryptedTextUsed) {
    rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
      return rv;
    }

    uint32_t amt;
    rv = mSegmentReader->OnReadSegment(mEncryptedText.get(), mEncryptedTextUsed,
                                       &amt);
    if (NS_FAILED(rv)) {
      return rv;
    }

    mEncryptedTextUsed -= amt;
    if (mEncryptedTextUsed) {
      memmove(mEncryptedText.get(), &mEncryptedText[amt], mEncryptedTextUsed);
      return NS_OK;
    }
  }

  // encrypt for network write
  // write aData down the SSL layer into the FilterWrite() method where it will
  // be queued into mEncryptedText. We need to copy it like this in order to
  // guarantee atomic writes

  EnsureBuffer(mEncryptedText, aCount + 4096, 0, mEncryptedTextSize);

  while (aCount > 0) {
    int32_t written = PR_Write(mFD, aData, aCount);
    LOG(("TLSFilterTransaction %p OnReadSegment PRWrite(%d) = %d %d\n", this,
         aCount, written, PR_GetError() == PR_WOULD_BLOCK_ERROR));

    if (written < 1) {
      if (*outCountRead) {
        return NS_OK;
      }
      // mTransaction ReadSegments actually obscures this code, so
      // keep it in a member var for this::ReadSegments to insepct. Similar
      // to nsHttpConnection::mSocketOutCondition
      mReadSegmentBlocked = (PR_GetError() == PR_WOULD_BLOCK_ERROR);
      return mReadSegmentBlocked ? NS_BASE_STREAM_WOULD_BLOCK
                                 : NS_ERROR_FAILURE;
    }
    aCount -= written;
    aData += written;
    *outCountRead += written;
    mNudgeCounter = 0;
  }

  LOG(("TLSFilterTransaction %p OnReadSegment2 (buffered %d)\n", this,
       mEncryptedTextUsed));

  uint32_t amt = 0;
  if (mEncryptedTextUsed) {
    // If we are tunneled on spdy CommitToSegmentSize will prevent partial
    // writes that could interfere with multiplexing. H1 is fine with
    // partial writes.
    rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
    if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
      rv = mSegmentReader->OnReadSegment(mEncryptedText.get(),
                                         mEncryptedTextUsed, &amt);
    }

    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
      // return OK because all the data was consumed and stored in this buffer
      Connection()->TransactionHasDataToWrite(this);
      return NS_OK;
    } else if (NS_FAILED(rv)) {
      return rv;
    }
  }

  if (amt == mEncryptedTextUsed) {
    mEncryptedText = nullptr;
    mEncryptedTextUsed = 0;
    mEncryptedTextSize = 0;
  } else {
    memmove(mEncryptedText.get(), &mEncryptedText[amt],
            mEncryptedTextUsed - amt);
    mEncryptedTextUsed -= amt;
  }
  return NS_OK;
}

int32_t TLSFilterTransaction::FilterOutput(const char *aBuf, int32_t aAmount) {
  EnsureBuffer(mEncryptedText, mEncryptedTextUsed + aAmount, mEncryptedTextUsed,
               mEncryptedTextSize);
  memcpy(&mEncryptedText[mEncryptedTextUsed], aBuf, aAmount);
  mEncryptedTextUsed += aAmount;
  return aAmount;
}

nsresult TLSFilterTransaction::CommitToSegmentSize(uint32_t size,
                                                   bool forceCommitment) {
  if (!mSegmentReader) {
    return NS_ERROR_FAILURE;
  }

  // pad the commit by a little bit to leave room for encryption overhead
  // this isn't foolproof and we may still have to buffer, but its a good start
  mForce = forceCommitment;
  return mSegmentReader->CommitToSegmentSize(size + 1024, forceCommitment);
}

nsresult TLSFilterTransaction::OnWriteSegment(char *aData, uint32_t aCount,
                                              uint32_t *outCountRead) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(mSegmentWriter);
  LOG(("TLSFilterTransaction::OnWriteSegment %p max=%d\n", this, aCount));
  if (!mSecInfo) {
    return NS_ERROR_FAILURE;
  }

  // this will call through to FilterInput to get data from the higher
  // level connection before removing the local TLS layer
  mFilterReadCode = NS_OK;
  int32_t bytesRead = PR_Read(mFD, aData, aCount);
  if (bytesRead == -1) {
    if (PR_GetError() == PR_WOULD_BLOCK_ERROR) {
      return NS_BASE_STREAM_WOULD_BLOCK;
    }
    return NS_ERROR_FAILURE;
  }
  *outCountRead = bytesRead;

  if (NS_SUCCEEDED(mFilterReadCode) && !bytesRead) {
    LOG(
        ("TLSFilterTransaction::OnWriteSegment %p "
         "Second layer of TLS stripping results in STREAM_CLOSED\n",
         this));
    mFilterReadCode = NS_BASE_STREAM_CLOSED;
  }

  LOG(("TLSFilterTransaction::OnWriteSegment %p rv=%" PRIx32 " didread=%d "
       "2 layers of ssl stripped to plaintext\n",
       this, static_cast<uint32_t>(mFilterReadCode), bytesRead));
  return mFilterReadCode;
}

int32_t TLSFilterTransaction::FilterInput(char *aBuf, int32_t aAmount) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(mSegmentWriter);
  LOG(("TLSFilterTransaction::FilterInput max=%d\n", aAmount));

  uint32_t outCountRead = 0;
  mFilterReadCode =
      mSegmentWriter->OnWriteSegment(aBuf, aAmount, &outCountRead);
  if (NS_SUCCEEDED(mFilterReadCode) && outCountRead) {
    LOG(("TLSFilterTransaction::FilterInput rv=%" PRIx32
         " read=%d input from net "
         "1 layer stripped, 1 still on\n",
         static_cast<uint32_t>(mFilterReadCode), outCountRead));
    if (mReadSegmentBlocked) {
      mNudgeCounter = 0;
    }
  }
  if (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK) {
    PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
    return -1;
  }
  return outCountRead;
}

nsresult TLSFilterTransaction::ReadSegments(nsAHttpSegmentReader *aReader,
                                            uint32_t aCount,
                                            uint32_t *outCountRead) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("TLSFilterTransaction::ReadSegments %p max=%d\n", this, aCount));

  if (!mTransaction) {
    return NS_ERROR_UNEXPECTED;
  }

  mReadSegmentBlocked = false;
  mSegmentReader = aReader;
  nsresult rv = mTransaction->ReadSegments(this, aCount, outCountRead);
  LOG(("TLSFilterTransaction %p called trans->ReadSegments rv=%" PRIx32 " %d\n",
       this, static_cast<uint32_t>(rv), *outCountRead));
  if (NS_SUCCEEDED(rv) && mReadSegmentBlocked) {
    rv = NS_BASE_STREAM_WOULD_BLOCK;
    LOG(("TLSFilterTransaction %p read segment blocked found rv=%" PRIx32 "\n",
         this, static_cast<uint32_t>(rv)));
    Unused << Connection()->ForceSend();
  }

  return rv;
}

nsresult TLSFilterTransaction::WriteSegments(nsAHttpSegmentWriter *aWriter,
                                             uint32_t aCount,
                                             uint32_t *outCountWritten) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("TLSFilterTransaction::WriteSegments %p max=%d\n", this, aCount));

  if (!mTransaction) {
    return NS_ERROR_UNEXPECTED;
  }

  mSegmentWriter = aWriter;
  nsresult rv = mTransaction->WriteSegments(this, aCount, outCountWritten);
  if (NS_SUCCEEDED(rv) && NS_FAILED(mFilterReadCode) && !(*outCountWritten)) {
    // nsPipe turns failures into silent OK.. undo that!
    rv = mFilterReadCode;
    if (Connection() && (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK)) {
      Unused << Connection()->ResumeRecv();
    }
  }
  LOG(("TLSFilterTransaction %p called trans->WriteSegments rv=%" PRIx32
       " %d\n",
       this, static_cast<uint32_t>(rv), *outCountWritten));
  return rv;
}

nsresult TLSFilterTransaction::GetTransactionSecurityInfo(
    nsISupports **outSecInfo) {
  if (!mSecInfo) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsISupports> temp(mSecInfo);
  temp.forget(outSecInfo);
  return NS_OK;
}

nsresult TLSFilterTransaction::NudgeTunnel(NudgeTunnelCallback *aCallback) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("TLSFilterTransaction %p NudgeTunnel\n", this));
  mNudgeCallback = nullptr;

  if (!mSecInfo) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsISSLSocketControl> ssl(do_QueryInterface(mSecInfo));
  nsresult rv = ssl ? ssl->DriveHandshake() : NS_ERROR_FAILURE;
  if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
    // fatal handshake failure
    LOG(("TLSFilterTransaction %p Fatal Handshake Failure: %d\n", this,
         PR_GetError()));
    return NS_ERROR_FAILURE;
  }

  uint32_t notUsed;
  Unused << OnReadSegment("", 0, &notUsed);

  // The SSL Layer does some unusual things with PR_Poll that makes it a bad
  // match for multiplexed SSL sessions. We work around this by manually polling
  // for the moment during the brief handshake phase or otherwise blocked on
  // write. Thankfully this is a pretty unusual state. NSPR doesn't help us here
  // - asserting when polling without the NSPR IO layer on the bottom of the
  // stack. As a follow-on we can do some NSPR and maybe libssl changes to make
  // this more event driven, but this is acceptable for getting started.

  uint32_t counter = mNudgeCounter++;
  uint32_t delay;

  if (!counter) {
    delay = 0;
  } else if (counter < 8) {  // up to 48ms at 6
    delay = 6;
  } else if (counter < 34) {  // up to 499 ms at 17ms
    delay = 17;
  } else {  // after that at 51ms (3 old windows ticks)
    delay = 51;
  }

  if (!mTimer) {
    mTimer = NS_NewTimer();
  }

  mNudgeCallback = aCallback;
  if (!mTimer || NS_FAILED(mTimer->InitWithCallback(this, delay,
                                                    nsITimer::TYPE_ONE_SHOT))) {
    return StartTimerCallback();
  }

  LOG(("TLSFilterTransaction %p NudgeTunnel timer started\n", this));
  return NS_OK;
}

NS_IMETHODIMP
TLSFilterTransaction::Notify(nsITimer *timer) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("TLSFilterTransaction %p NudgeTunnel notify\n", this));

  if (timer != mTimer) {
    return NS_ERROR_UNEXPECTED;
  }
  DebugOnly<nsresult> rv = StartTimerCallback();
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  return NS_OK;
}

NS_IMETHODIMP
TLSFilterTransaction::GetName(nsACString &aName) {
  aName.AssignLiteral("TLSFilterTransaction");
  return NS_OK;
}

nsresult TLSFilterTransaction::StartTimerCallback() {
  LOG(("TLSFilterTransaction %p NudgeTunnel StartTimerCallback %p\n", this,
       mNudgeCallback.get()));

  if (mNudgeCallback) {
    // This class can be called re-entrantly, so cleanup m* before ->on()
    RefPtr<NudgeTunnelCallback> cb(mNudgeCallback);
    mNudgeCallback = nullptr;
    cb->OnTunnelNudged(this);
  }
  return NS_OK;
}

PRStatus TLSFilterTransaction::GetPeerName(PRFileDesc *aFD, PRNetAddr *addr) {
  NetAddr peeraddr;
  TLSFilterTransaction *self =
      reinterpret_cast<TLSFilterTransaction *>(aFD->secret);

  if (!self->mTransaction ||
      NS_FAILED(self->mTransaction->Connection()->Transport()->GetPeerAddr(
          &peeraddr))) {
    return PR_FAILURE;
  }
  NetAddrToPRNetAddr(&peeraddr, addr);
  return PR_SUCCESS;
}

PRStatus TLSFilterTransaction::GetSocketOption(PRFileDesc *aFD,
                                               PRSocketOptionData *aOpt) {
  if (aOpt->option == PR_SockOpt_Nonblocking) {
    aOpt->value.non_blocking = PR_TRUE;
    return PR_SUCCESS;
  }
  return PR_FAILURE;
}

PRStatus TLSFilterTransaction::SetSocketOption(PRFileDesc *aFD,
                                               const PRSocketOptionData *aOpt) {
  return PR_FAILURE;
}

PRStatus TLSFilterTransaction::FilterClose(PRFileDesc *aFD) {
  return PR_SUCCESS;
}

int32_t TLSFilterTransaction::FilterWrite(PRFileDesc *aFD, const void *aBuf,
                                          int32_t aAmount) {
  TLSFilterTransaction *self =
      reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
  return self->FilterOutput(static_cast<const char *>(aBuf), aAmount);
}

int32_t TLSFilterTransaction::FilterSend(PRFileDesc *aFD, const void *aBuf,
                                         int32_t aAmount, int, PRIntervalTime) {
  return FilterWrite(aFD, aBuf, aAmount);
}

int32_t TLSFilterTransaction::FilterRead(PRFileDesc *aFD, void *aBuf,
                                         int32_t aAmount) {
  TLSFilterTransaction *self =
      reinterpret_cast<TLSFilterTransaction *>(aFD->secret);
  return self->FilterInput(static_cast<char *>(aBuf), aAmount);
}

int32_t TLSFilterTransaction::FilterRecv(PRFileDesc *aFD, void *aBuf,
                                         int32_t aAmount, int, PRIntervalTime) {
  return FilterRead(aFD, aBuf, aAmount);
}

/////
// The other methods of TLSFilterTransaction just call mTransaction->method
/////

void TLSFilterTransaction::SetConnection(nsAHttpConnection *aConnection) {
  if (!mTransaction) {
    return;
  }

  mTransaction->SetConnection(aConnection);
}

nsAHttpConnection *TLSFilterTransaction::Connection() {
  if (!mTransaction) {
    return nullptr;
  }
  return mTransaction->Connection();
}

void TLSFilterTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **outCB) {
  if (!mTransaction) {
    return;
  }
  mTransaction->GetSecurityCallbacks(outCB);
}

void TLSFilterTransaction::OnTransportStatus(nsITransport *aTransport,
                                             nsresult aStatus,
                                             int64_t aProgress) {
  if (!mTransaction) {
    return;
  }
  mTransaction->OnTransportStatus(aTransport, aStatus, aProgress);
}

nsHttpConnectionInfo *TLSFilterTransaction::ConnectionInfo() {
  if (!mTransaction) {
    return nullptr;
  }
  return mTransaction->ConnectionInfo();
}

bool TLSFilterTransaction::IsDone() {
  if (!mTransaction) {
    return true;
  }
  return mTransaction->IsDone();
}

nsresult TLSFilterTransaction::Status() {
  if (!mTransaction) {
    return NS_ERROR_UNEXPECTED;
  }

  return mTransaction->Status();
}

uint32_t TLSFilterTransaction::Caps() {
  if (!mTransaction) {
    return 0;
  }

  return mTransaction->Caps();
}

void TLSFilterTransaction::SetDNSWasRefreshed() {
  if (!mTransaction) {
    return;
  }

  mTransaction->SetDNSWasRefreshed();
}

void TLSFilterTransaction::SetProxyConnectFailed() {
  if (!mTransaction) {
    return;
  }

  mTransaction->SetProxyConnectFailed();
}

nsHttpRequestHead *TLSFilterTransaction::RequestHead() {
  if (!mTransaction) {
    return nullptr;
  }

  return mTransaction->RequestHead();
}

uint32_t TLSFilterTransaction::Http1xTransactionCount() {
  if (!mTransaction) {
    return 0;
  }

  return mTransaction->Http1xTransactionCount();
}

nsresult TLSFilterTransaction::TakeSubTransactions(
    nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) {
  LOG(("TLSFilterTransaction::TakeSubTransactions [this=%p] mTransaction %p\n",
       this, mTransaction.get()));

  if (!mTransaction) {
    return NS_ERROR_UNEXPECTED;
  }

  if (mTransaction->TakeSubTransactions(outTransactions) ==
      NS_ERROR_NOT_IMPLEMENTED) {
    outTransactions.AppendElement(mTransaction);
  }
  mTransaction = nullptr;

  return NS_OK;
}

nsresult TLSFilterTransaction::SetProxiedTransaction(
    nsAHttpTransaction *aTrans) {
  LOG(("TLSFilterTransaction::SetProxiedTransaction [this=%p] aTrans=%p\n",
       this, aTrans));

  mTransaction = aTrans;
  nsCOMPtr<nsIInterfaceRequestor> callbacks;
  mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
  nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
  if (secCtrl && callbacks) {
    secCtrl->SetNotificationCallbacks(callbacks);
  }

  return NS_OK;
}

bool TLSFilterTransaction::IsNullTransaction() {
  if (!mTransaction) {
    return false;
  }
  return mTransaction->IsNullTransaction();
}

NullHttpTransaction *TLSFilterTransaction::QueryNullTransaction() {
  if (!mTransaction) {
    return nullptr;
  }
  return mTransaction->QueryNullTransaction();
}

nsHttpTransaction *TLSFilterTransaction::QueryHttpTransaction() {
  if (!mTransaction) {
    return nullptr;
  }
  return mTransaction->QueryHttpTransaction();
}

class SocketInWrapper : public nsIAsyncInputStream,
                        public nsAHttpSegmentWriter {
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_FORWARD_NSIASYNCINPUTSTREAM(mStream->)

  SocketInWrapper(nsIAsyncInputStream *aWrapped, TLSFilterTransaction *aFilter)
      : mStream(aWrapped), mTLSFilter(aFilter) {}

  NS_IMETHOD Close() override {
    mTLSFilter = nullptr;
    return mStream->Close();
  }

  NS_IMETHOD Available(uint64_t *_retval) override {
    return mStream->Available(_retval);
  }

  NS_IMETHOD IsNonBlocking(bool *_retval) override {
    return mStream->IsNonBlocking(_retval);
  }

  NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
                          uint32_t aCount, uint32_t *_retval) override {
    return mStream->ReadSegments(aWriter, aClosure, aCount, _retval);
  }

  // finally, ones that don't get forwarded :)
  NS_IMETHOD Read(char *aBuf, uint32_t aCount, uint32_t *_retval) override;
  virtual nsresult OnWriteSegment(char *segment, uint32_t count,
                                  uint32_t *countWritten) override;

 private:
  virtual ~SocketInWrapper(){};

  nsCOMPtr<nsIAsyncInputStream> mStream;
  RefPtr<TLSFilterTransaction> mTLSFilter;
};

nsresult SocketInWrapper::OnWriteSegment(char *segment, uint32_t count,
                                         uint32_t *countWritten) {
  LOG(("SocketInWrapper OnWriteSegment %d %p filter=%p\n", count, this,
       mTLSFilter.get()));

  nsresult rv = mStream->Read(segment, count, countWritten);
  LOG(("SocketInWrapper OnWriteSegment %p wrapped read %" PRIx32 " %d\n", this,
       static_cast<uint32_t>(rv), *countWritten));
  return rv;
}

NS_IMETHODIMP
SocketInWrapper::Read(char *aBuf, uint32_t aCount, uint32_t *_retval) {
  LOG(("SocketInWrapper Read %d %p filter=%p\n", aCount, this,
       mTLSFilter.get()));

  if (!mTLSFilter) {
    return NS_ERROR_UNEXPECTED;  // protect potentially dangling mTLSFilter
  }

  // mTLSFilter->mSegmentWriter MUST be this at ctor time
  return mTLSFilter->OnWriteSegment(aBuf, aCount, _retval);
}

class SocketOutWrapper : public nsIAsyncOutputStream,
                         public nsAHttpSegmentReader {
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_FORWARD_NSIASYNCOUTPUTSTREAM(mStream->)

  SocketOutWrapper(nsIAsyncOutputStream *aWrapped,
                   TLSFilterTransaction *aFilter)
      : mStream(aWrapped), mTLSFilter(aFilter) {}

  NS_IMETHOD Close() override {
    mTLSFilter = nullptr;
    return mStream->Close();
  }

  NS_IMETHOD Flush() override { return mStream->Flush(); }

  NS_IMETHOD IsNonBlocking(bool *_retval) override {
    return mStream->IsNonBlocking(_retval);
  }

  NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void *aClosure,
                           uint32_t aCount, uint32_t *_retval) override {
    return mStream->WriteSegments(aReader, aClosure, aCount, _retval);
  }

  NS_IMETHOD WriteFrom(nsIInputStream *aFromStream, uint32_t aCount,
                       uint32_t *_retval) override {
    return mStream->WriteFrom(aFromStream, aCount, _retval);
  }

  // finally, ones that don't get forwarded :)
  NS_IMETHOD Write(const char *aBuf, uint32_t aCount,
                   uint32_t *_retval) override;
  virtual nsresult OnReadSegment(const char *segment, uint32_t count,
                                 uint32_t *countRead) override;

 private:
  virtual ~SocketOutWrapper(){};

  nsCOMPtr<nsIAsyncOutputStream> mStream;
  RefPtr<TLSFilterTransaction> mTLSFilter;
};

nsresult SocketOutWrapper::OnReadSegment(const char *segment, uint32_t count,
                                         uint32_t *countWritten) {
  return mStream->Write(segment, count, countWritten);
}

NS_IMETHODIMP
SocketOutWrapper::Write(const char *aBuf, uint32_t aCount, uint32_t *_retval) {
  LOG(("SocketOutWrapper Write %d %p filter=%p\n", aCount, this,
       mTLSFilter.get()));

  // mTLSFilter->mSegmentReader MUST be this at ctor time
  if (!mTLSFilter) {
    return NS_ERROR_UNEXPECTED;  // protect potentially dangling mTLSFilter
  }

  return mTLSFilter->OnReadSegment(aBuf, aCount, _retval);
}

void TLSFilterTransaction::newIODriver(nsIAsyncInputStream *aSocketIn,
                                       nsIAsyncOutputStream *aSocketOut,
                                       nsIAsyncInputStream **outSocketIn,
                                       nsIAsyncOutputStream **outSocketOut) {
  SocketInWrapper *inputWrapper = new SocketInWrapper(aSocketIn, this);
  mSegmentWriter = inputWrapper;
  nsCOMPtr<nsIAsyncInputStream> newIn(inputWrapper);
  newIn.forget(outSocketIn);

  SocketOutWrapper *outputWrapper = new SocketOutWrapper(aSocketOut, this);
  mSegmentReader = outputWrapper;
  nsCOMPtr<nsIAsyncOutputStream> newOut(outputWrapper);
  newOut.forget(outSocketOut);
}

SpdyConnectTransaction *TLSFilterTransaction::QuerySpdyConnectTransaction() {
  if (!mTransaction) {
    return nullptr;
  }
  return mTransaction->QuerySpdyConnectTransaction();
}

class SocketTransportShim : public nsISocketTransport {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSITRANSPORT
  NS_DECL_NSISOCKETTRANSPORT

  explicit SocketTransportShim(nsISocketTransport *aWrapped)
      : mWrapped(aWrapped){};

 private:
  virtual ~SocketTransportShim(){};

  nsCOMPtr<nsISocketTransport> mWrapped;
};

class OutputStreamShim : public nsIAsyncOutputStream {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIOUTPUTSTREAM
  NS_DECL_NSIASYNCOUTPUTSTREAM

  friend class SpdyConnectTransaction;

  explicit OutputStreamShim(SpdyConnectTransaction *aTrans)
      : mCallback(nullptr), mStatus(NS_OK) {
    mWeakTrans = do_GetWeakReference(aTrans);
  }

 private:
  virtual ~OutputStreamShim(){};

  nsWeakPtr mWeakTrans;  // SpdyConnectTransaction *
  nsIOutputStreamCallback *mCallback;
  nsresult mStatus;
};

class InputStreamShim : public nsIAsyncInputStream {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIINPUTSTREAM
  NS_DECL_NSIASYNCINPUTSTREAM

  friend class SpdyConnectTransaction;

  explicit InputStreamShim(SpdyConnectTransaction *aTrans)
      : mCallback(nullptr), mStatus(NS_OK) {
    mWeakTrans = do_GetWeakReference(aTrans);
  }

 private:
  virtual ~InputStreamShim(){};

  nsWeakPtr mWeakTrans;  // SpdyConnectTransaction *
  nsIInputStreamCallback *mCallback;
  nsresult mStatus;
};

SpdyConnectTransaction::SpdyConnectTransaction(nsHttpConnectionInfo *ci,
                                               nsIInterfaceRequestor *callbacks,
                                               uint32_t caps,
                                               nsHttpTransaction *trans,
                                               nsAHttpConnection *session)
    : NullHttpTransaction(ci, callbacks, caps | NS_HTTP_ALLOW_KEEPALIVE),
      mConnectStringOffset(0),
      mSession(session),
      mSegmentReader(nullptr),
      mInputDataSize(0),
      mInputDataUsed(0),
      mInputDataOffset(0),
      mOutputDataSize(0),
      mOutputDataUsed(0),
      mOutputDataOffset(0),
      mForcePlainText(false) {
  LOG(("SpdyConnectTransaction ctor %p\n", this));

  mTimestampSyn = TimeStamp::Now();
  mRequestHead = new nsHttpRequestHead();
  DebugOnly<nsresult> rv =
      nsHttpConnection::MakeConnectString(trans, mRequestHead, mConnectString);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  mDrivingTransaction = trans;
}

SpdyConnectTransaction::~SpdyConnectTransaction() {
  LOG(("SpdyConnectTransaction dtor %p\n", this));

  if (mDrivingTransaction) {
    // requeue it I guess. This should be gone.
    Unused << gHttpHandler->InitiateTransaction(
        mDrivingTransaction, mDrivingTransaction->Priority());
  }
}

void SpdyConnectTransaction::ForcePlainText() {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(!mInputDataUsed && !mInputDataSize && !mInputDataOffset);
  MOZ_ASSERT(!mForcePlainText);
  MOZ_ASSERT(!mTunnelTransport, "call before mapstreamtohttpconnection");

  mForcePlainText = true;
}

void SpdyConnectTransaction::MapStreamToHttpConnection(
    nsISocketTransport *aTransport, nsHttpConnectionInfo *aConnInfo) {
  mConnInfo = aConnInfo;

  mTunnelTransport = new SocketTransportShim(aTransport);
  mTunnelStreamIn = new InputStreamShim(this);
  mTunnelStreamOut = new OutputStreamShim(this);
  mTunneledConn = new nsHttpConnection();

  // this new http connection has a specific hashkey (i.e. to a particular
  // host via the tunnel) and is associated with the tunnel streams
  LOG(("SpdyConnectTransaction new httpconnection %p %s\n", mTunneledConn.get(),
       aConnInfo->HashKey().get()));

  nsCOMPtr<nsIInterfaceRequestor> callbacks;
  GetSecurityCallbacks(getter_AddRefs(callbacks));
  mTunneledConn->SetTransactionCaps(Caps());
  MOZ_ASSERT(aConnInfo->UsingHttpsProxy());
  TimeDuration rtt = TimeStamp::Now() - mTimestampSyn;
  DebugOnly<nsresult> rv = mTunneledConn->Init(
      aConnInfo, gHttpHandler->ConnMgr()->MaxRequestDelay(), mTunnelTransport,
      mTunnelStreamIn, mTunnelStreamOut, true, callbacks,
      PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds())));
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  if (mForcePlainText) {
    mTunneledConn->ForcePlainText();
  } else {
    mTunneledConn->SetupSecondaryTLS();
    mTunneledConn->SetInSpdyTunnel(true);
  }

  // make the originating transaction stick to the tunneled conn
  RefPtr<nsAHttpConnection> wrappedConn =
      gHttpHandler->ConnMgr()->MakeConnectionHandle(mTunneledConn);
  mDrivingTransaction->SetConnection(wrappedConn);
  mDrivingTransaction->MakeSticky();

  // jump the priority and start the dispatcher
  Unused << gHttpHandler->InitiateTransaction(
      mDrivingTransaction, nsISupportsPriority::PRIORITY_HIGHEST - 60);
  mDrivingTransaction = nullptr;
}

nsresult SpdyConnectTransaction::Flush(uint32_t count, uint32_t *countRead) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("SpdyConnectTransaction::Flush %p count %d avail %d\n", this, count,
       mOutputDataUsed - mOutputDataOffset));

  if (!mSegmentReader) {
    return NS_ERROR_UNEXPECTED;
  }

  *countRead = 0;
  count = std::min(count, (mOutputDataUsed - mOutputDataOffset));
  if (count) {
    nsresult rv;
    rv = mSegmentReader->OnReadSegment(&mOutputData[mOutputDataOffset], count,
                                       countRead);
    if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
      LOG(("SpdyConnectTransaction::Flush %p Error %" PRIx32 "\n", this,
           static_cast<uint32_t>(rv)));
      CreateShimError(rv);
      return rv;
    }
  }

  mOutputDataOffset += *countRead;
  if (mOutputDataOffset == mOutputDataUsed) {
    mOutputDataOffset = mOutputDataUsed = 0;
  }
  if (!(*countRead)) {
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  if (mOutputDataUsed != mOutputDataOffset) {
    LOG(("SpdyConnectTransaction::Flush %p Incomplete %d\n", this,
         mOutputDataUsed - mOutputDataOffset));
    mSession->TransactionHasDataToWrite(this);
  }

  return NS_OK;
}

nsresult SpdyConnectTransaction::ReadSegments(nsAHttpSegmentReader *reader,
                                              uint32_t count,
                                              uint32_t *countRead) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("SpdyConnectTransaction::ReadSegments %p count %d conn %p\n", this,
       count, mTunneledConn.get()));

  mSegmentReader = reader;

  // spdy stream carrying tunnel is not setup yet.
  if (!mTunneledConn) {
    uint32_t toWrite = mConnectString.Length() - mConnectStringOffset;
    toWrite = std::min(toWrite, count);
    *countRead = toWrite;
    if (toWrite) {
      nsresult rv = mSegmentReader->OnReadSegment(
          mConnectString.BeginReading() + mConnectStringOffset, toWrite,
          countRead);
      if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
        LOG(
            ("SpdyConnectTransaction::ReadSegments %p OnReadSegmentError "
             "%" PRIx32 "\n",
             this, static_cast<uint32_t>(rv)));
        CreateShimError(rv);
      } else {
        mConnectStringOffset += toWrite;
        if (mConnectString.Length() == mConnectStringOffset) {
          mConnectString.Truncate();
          mConnectStringOffset = 0;
        }
      }
      return rv;
    }
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  if (mForcePlainText) {
    // this path just ignores sending the request so that we can
    // send a synthetic reply in writesegments()
    LOG(
        ("SpdyConnectTransaciton::ReadSegments %p dropping %d output bytes "
         "due to synthetic reply\n",
         this, mOutputDataUsed - mOutputDataOffset));
    *countRead = mOutputDataUsed - mOutputDataOffset;
    mOutputDataOffset = mOutputDataUsed = 0;
    mTunneledConn->DontReuse();
    return NS_OK;
  }

  *countRead = 0;
  Unused << Flush(count, countRead);
  if (!mTunnelStreamOut->mCallback) {
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  nsresult rv =
      mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
  if (NS_FAILED(rv)) {
    return rv;
  }

  uint32_t subtotal;
  count -= *countRead;
  rv = Flush(count, &subtotal);
  *countRead += subtotal;
  return rv;
}

void SpdyConnectTransaction::CreateShimError(nsresult code) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(NS_FAILED(code));

  if (mTunnelStreamOut && NS_SUCCEEDED(mTunnelStreamOut->mStatus)) {
    mTunnelStreamOut->mStatus = code;
  }

  if (mTunnelStreamIn && NS_SUCCEEDED(mTunnelStreamIn->mStatus)) {
    mTunnelStreamIn->mStatus = code;
  }

  if (mTunnelStreamIn && mTunnelStreamIn->mCallback) {
    mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
  }

  if (mTunnelStreamOut && mTunnelStreamOut->mCallback) {
    mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
  }
}

nsresult SpdyConnectTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
                                               uint32_t count,
                                               uint32_t *countWritten) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  LOG(("SpdyConnectTransaction::WriteSegments %p max=%d cb=%p\n", this, count,
       mTunneledConn ? mTunnelStreamIn->mCallback : nullptr));

  // first call into the tunnel stream to get the demux'd data out of the
  // spdy session.
  EnsureBuffer(mInputData, mInputDataUsed + count, mInputDataUsed,
               mInputDataSize);
  nsresult rv =
      writer->OnWriteSegment(&mInputData[mInputDataUsed], count, countWritten);
  if (NS_FAILED(rv)) {
    if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
      LOG(
          ("SpdyConnectTransaction::WriteSegments wrapped writer %p Error "
           "%" PRIx32 "\n",
           this, static_cast<uint32_t>(rv)));
      CreateShimError(rv);
    }
    return rv;
  }
  mInputDataUsed += *countWritten;
  LOG(
      ("SpdyConnectTransaction %p %d new bytes [%d total] of ciphered data "
       "buffered\n",
       this, *countWritten, mInputDataUsed - mInputDataOffset));

  if (!mTunneledConn || !mTunnelStreamIn->mCallback) {
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  rv = mTunnelStreamIn->mCallback->OnInputStreamReady(mTunnelStreamIn);
  LOG(
      ("SpdyConnectTransaction::WriteSegments %p "
       "after InputStreamReady callback %d total of ciphered data buffered "
       "rv=%" PRIx32 "\n",
       this, mInputDataUsed - mInputDataOffset, static_cast<uint32_t>(rv)));
  LOG(
      ("SpdyConnectTransaction::WriteSegments %p "
       "goodput %p out %" PRId64 "\n",
       this, mTunneledConn.get(), mTunneledConn->ContentBytesWritten()));
  if (NS_SUCCEEDED(rv) && !mTunneledConn->ContentBytesWritten()) {
    mTunnelStreamOut->AsyncWait(mTunnelStreamOut->mCallback, 0, 0, nullptr);
  }
  return rv;
}

bool SpdyConnectTransaction::ConnectedReadyForInput() {
  return mTunneledConn && mTunnelStreamIn->mCallback;
}

nsHttpRequestHead *SpdyConnectTransaction::RequestHead() {
  return mRequestHead;
}

void SpdyConnectTransaction::Close(nsresult code) {
  LOG(("SpdyConnectTransaction close %p %" PRIx32 "\n", this,
       static_cast<uint32_t>(code)));

  NullHttpTransaction::Close(code);
  if (NS_FAILED(code) && (code != NS_BASE_STREAM_WOULD_BLOCK)) {
    CreateShimError(code);
  } else {
    CreateShimError(NS_BASE_STREAM_CLOSED);
  }
}

NS_IMETHODIMP
OutputStreamShim::AsyncWait(nsIOutputStreamCallback *callback, unsigned int,
                            unsigned int, nsIEventTarget *target) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  bool currentThread;

  if (target && (NS_FAILED(target->IsOnCurrentThread(&currentThread)) ||
                 !currentThread)) {
    return NS_ERROR_FAILURE;
  }

  LOG(("OutputStreamShim::AsyncWait %p callback %p\n", this, callback));
  mCallback = callback;

  RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
  if (!baseTrans) {
    return NS_ERROR_FAILURE;
  }
  SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
  MOZ_ASSERT(trans);
  if (!trans) {
    return NS_ERROR_UNEXPECTED;
  }

  trans->mSession->TransactionHasDataToWrite(trans);

  return NS_OK;
}

NS_IMETHODIMP
OutputStreamShim::CloseWithStatus(nsresult reason) {
  RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
  if (!baseTrans) {
    return NS_ERROR_FAILURE;
  }
  SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
  MOZ_ASSERT(trans);
  if (!trans) {
    return NS_ERROR_UNEXPECTED;
  }

  trans->mSession->CloseTransaction(trans, reason);
  return NS_OK;
}

NS_IMETHODIMP
OutputStreamShim::Close() { return CloseWithStatus(NS_OK); }

NS_IMETHODIMP
OutputStreamShim::Flush() {
  RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
  if (!baseTrans) {
    return NS_ERROR_FAILURE;
  }
  SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
  MOZ_ASSERT(trans);
  if (!trans) {
    return NS_ERROR_UNEXPECTED;
  }

  uint32_t count = trans->mOutputDataUsed - trans->mOutputDataOffset;
  if (!count) {
    return NS_OK;
  }

  uint32_t countRead;
  nsresult rv = trans->Flush(count, &countRead);
  LOG(("OutputStreamShim::Flush %p before %d after %d\n", this, count,
       trans->mOutputDataUsed - trans->mOutputDataOffset));
  return rv;
}

NS_IMETHODIMP
OutputStreamShim::Write(const char *aBuf, uint32_t aCount, uint32_t *_retval) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  if (NS_FAILED(mStatus)) {
    return mStatus;
  }

  RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
  if (!baseTrans) {
    return NS_ERROR_FAILURE;
  }
  SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
  MOZ_ASSERT(trans);
  if (!trans) {
    return NS_ERROR_UNEXPECTED;
  }

  if ((trans->mOutputDataUsed + aCount) >= 512000) {
    *_retval = 0;
    // time for some flow control;
    return NS_BASE_STREAM_WOULD_BLOCK;
  }

  EnsureBuffer(trans->mOutputData, trans->mOutputDataUsed + aCount,
               trans->mOutputDataUsed, trans->mOutputDataSize);
  memcpy(&trans->mOutputData[trans->mOutputDataUsed], aBuf, aCount);
  trans->mOutputDataUsed += aCount;
  *_retval = aCount;
  LOG(("OutputStreamShim::Write %p new %d total %d\n", this, aCount,
       trans->mOutputDataUsed));

  trans->mSession->TransactionHasDataToWrite(trans);

  return NS_OK;
}

NS_IMETHODIMP
OutputStreamShim::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount,
                            uint32_t *_retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
OutputStreamShim::WriteSegments(nsReadSegmentFun aReader, void *aClosure,
                                uint32_t aCount, uint32_t *_retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
OutputStreamShim::IsNonBlocking(bool *_retval) {
  *_retval = true;
  return NS_OK;
}

NS_IMETHODIMP
InputStreamShim::AsyncWait(nsIInputStreamCallback *callback, unsigned int,
                           unsigned int, nsIEventTarget *target) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  bool currentThread;

  if (target && (NS_FAILED(target->IsOnCurrentThread(&currentThread)) ||
                 !currentThread)) {
    return NS_ERROR_FAILURE;
  }

  LOG(("InputStreamShim::AsyncWait %p callback %p\n", this, callback));
  mCallback = callback;
  return NS_OK;
}

NS_IMETHODIMP
InputStreamShim::CloseWithStatus(nsresult reason) {
  RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
  if (!baseTrans) {
    return NS_ERROR_FAILURE;
  }
  SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
  MOZ_ASSERT(trans);
  if (!trans) {
    return NS_ERROR_UNEXPECTED;
  }

  trans->mSession->CloseTransaction(trans, reason);
  return NS_OK;
}

NS_IMETHODIMP
InputStreamShim::Close() { return CloseWithStatus(NS_OK); }

NS_IMETHODIMP
InputStreamShim::Available(uint64_t *_retval) {
  RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
  if (!baseTrans) {
    return NS_ERROR_FAILURE;
  }
  SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
  MOZ_ASSERT(trans);
  if (!trans) {
    return NS_ERROR_UNEXPECTED;
  }

  *_retval = trans->mInputDataUsed - trans->mInputDataOffset;
  return NS_OK;
}

NS_IMETHODIMP
InputStreamShim::Read(char *aBuf, uint32_t aCount, uint32_t *_retval) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  if (NS_FAILED(mStatus)) {
    return mStatus;
  }

  RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
  if (!baseTrans) {
    return NS_ERROR_FAILURE;
  }
  SpdyConnectTransaction *trans = baseTrans->QuerySpdyConnectTransaction();
  MOZ_ASSERT(trans);
  if (!trans) {
    return NS_ERROR_UNEXPECTED;
  }

  uint32_t avail = trans->mInputDataUsed - trans->mInputDataOffset;
  uint32_t tocopy = std::min(aCount, avail);
  *_retval = tocopy;
  memcpy(aBuf, &trans->mInputData[trans->mInputDataOffset], tocopy);
  trans->mInputDataOffset += tocopy;
  if (trans->mInputDataOffset == trans->mInputDataUsed) {
    trans->mInputDataOffset = trans->mInputDataUsed = 0;
  }

  return tocopy ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
}

NS_IMETHODIMP
InputStreamShim::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure,
                              uint32_t aCount, uint32_t *_retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
InputStreamShim::IsNonBlocking(bool *_retval) {
  *_retval = true;
  return NS_OK;
}

NS_IMETHODIMP
SocketTransportShim::SetKeepaliveEnabled(bool aKeepaliveEnabled) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::SetKeepaliveVals(int32_t keepaliveIdleTime,
                                      int32_t keepaliveRetryInterval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::SetSecurityCallbacks(
    nsIInterfaceRequestor *aSecurityCallbacks) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
                                     uint32_t aSegmentCount,
                                     nsIInputStream **_retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
                                      uint32_t aSegmentCount,
                                      nsIOutputStream **_retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::Close(nsresult aReason) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::SetEventSink(nsITransportEventSink *aSink,
                                  nsIEventTarget *aEventTarget) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::Bind(NetAddr *aLocalAddr) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SocketTransportShim::GetFirstRetryError(nsresult *aFirstRetryError) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

#define FWD_TS_PTR(fx, ts) \
  NS_IMETHODIMP            \
  SocketTransportShim::fx(ts *arg) { return mWrapped->fx(arg); }

#define FWD_TS_ADDREF(fx, ts) \
  NS_IMETHODIMP               \
  SocketTransportShim::fx(ts **arg) { return mWrapped->fx(arg); }

#define FWD_TS(fx, ts) \
  NS_IMETHODIMP        \
  SocketTransportShim::fx(ts arg) { return mWrapped->fx(arg); }

FWD_TS_PTR(GetKeepaliveEnabled, bool);
FWD_TS_PTR(GetSendBufferSize, uint32_t);
FWD_TS(SetSendBufferSize, uint32_t);
FWD_TS_PTR(GetPort, int32_t);
FWD_TS_PTR(GetPeerAddr, mozilla::net::NetAddr);
FWD_TS_PTR(GetSelfAddr, mozilla::net::NetAddr);
FWD_TS_ADDREF(GetScriptablePeerAddr, nsINetAddr);
FWD_TS_ADDREF(GetScriptableSelfAddr, nsINetAddr);
FWD_TS_ADDREF(GetSecurityInfo, nsISupports);
FWD_TS_ADDREF(GetSecurityCallbacks, nsIInterfaceRequestor);
FWD_TS_PTR(IsAlive, bool);
FWD_TS_PTR(GetConnectionFlags, uint32_t);
FWD_TS(SetConnectionFlags, uint32_t);
FWD_TS_PTR(GetTlsFlags, uint32_t);
FWD_TS(SetTlsFlags, uint32_t);
FWD_TS_PTR(GetRecvBufferSize, uint32_t);
FWD_TS(SetRecvBufferSize, uint32_t);

nsresult SocketTransportShim::GetOriginAttributes(
    mozilla::OriginAttributes *aOriginAttributes) {
  return mWrapped->GetOriginAttributes(aOriginAttributes);
}

nsresult SocketTransportShim::SetOriginAttributes(
    const mozilla::OriginAttributes &aOriginAttributes) {
  return mWrapped->SetOriginAttributes(aOriginAttributes);
}

NS_IMETHODIMP
SocketTransportShim::GetScriptableOriginAttributes(
    JSContext *aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
  return mWrapped->GetScriptableOriginAttributes(aCx, aOriginAttributes);
}

NS_IMETHODIMP
SocketTransportShim::SetScriptableOriginAttributes(
    JSContext *aCx, JS::Handle<JS::Value> aOriginAttributes) {
  return mWrapped->SetScriptableOriginAttributes(aCx, aOriginAttributes);
}

NS_IMETHODIMP
SocketTransportShim::GetHost(nsACString &aHost) {
  return mWrapped->GetHost(aHost);
}

NS_IMETHODIMP
SocketTransportShim::GetTimeout(uint32_t aType, uint32_t *_retval) {
  return mWrapped->GetTimeout(aType, _retval);
}

NS_IMETHODIMP
SocketTransportShim::GetNetworkInterfaceId(nsACString &aNetworkInterfaceId) {
  return mWrapped->GetNetworkInterfaceId(aNetworkInterfaceId);
}

NS_IMETHODIMP
SocketTransportShim::SetNetworkInterfaceId(
    const nsACString &aNetworkInterfaceId) {
  return mWrapped->SetNetworkInterfaceId(aNetworkInterfaceId);
}

NS_IMETHODIMP
SocketTransportShim::SetTimeout(uint32_t aType, uint32_t aValue) {
  return mWrapped->SetTimeout(aType, aValue);
}

NS_IMETHODIMP
SocketTransportShim::SetReuseAddrPort(bool aReuseAddrPort) {
  return mWrapped->SetReuseAddrPort(aReuseAddrPort);
}

NS_IMETHODIMP
SocketTransportShim::GetQoSBits(uint8_t *aQoSBits) {
  return mWrapped->GetQoSBits(aQoSBits);
}

NS_IMETHODIMP
SocketTransportShim::SetQoSBits(uint8_t aQoSBits) {
  return mWrapped->SetQoSBits(aQoSBits);
}

NS_IMETHODIMP
SocketTransportShim::SetFastOpenCallback(TCPFastOpen *aFastOpen) {
  return mWrapped->SetFastOpenCallback(aFastOpen);
}

NS_IMPL_ISUPPORTS(TLSFilterTransaction, nsITimerCallback, nsINamed)
NS_IMPL_ISUPPORTS(SocketTransportShim, nsISocketTransport, nsITransport)
NS_IMPL_ISUPPORTS(InputStreamShim, nsIInputStream, nsIAsyncInputStream)
NS_IMPL_ISUPPORTS(OutputStreamShim, nsIOutputStream, nsIAsyncOutputStream)
NS_IMPL_ISUPPORTS(SocketInWrapper, nsIAsyncInputStream)
NS_IMPL_ISUPPORTS(SocketOutWrapper, nsIAsyncOutputStream)

}  // namespace net
}  // namespace mozilla