/* 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/. */

#include <algorithm>
#include <mutex>

#include "mozilla/net/WebrtcTCPSocket.h"
#include "mozilla/net/WebrtcTCPSocketCallback.h"
#include "nsISocketTransport.h"

#define GTEST_HAS_RTTI 0
#include "gtest/gtest.h"
#include "gtest_utils.h"

static const uint32_t kDefaultTestTimeout = 2000;
static const char kReadData[] = "Hello, World!";
static const size_t kReadDataLength = sizeof(kReadData) - 1;
MOZ_RUNINIT static const std::string kReadDataString =
    std::string(kReadData, kReadDataLength);
static int kDataLargeOuterLoopCount = 128;
static int kDataLargeInnerLoopCount = 1024;

namespace mozilla {

using namespace net;
using namespace testing;

class WebrtcTCPSocketTestCallback;

class FakeSocketTransportProvider : public nsISocketTransport {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  // nsISocketTransport
  NS_IMETHOD GetHost(nsACString& aHost) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetPort(int32_t* aPort) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetScriptableOriginAttributes(
      JSContext* cx, JS::MutableHandle<JS::Value> aOriginAttributes) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetScriptableOriginAttributes(
      JSContext* cx, JS::Handle<JS::Value> aOriginAttributes) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  virtual nsresult GetOriginAttributes(
      mozilla::OriginAttributes* _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  virtual nsresult SetOriginAttributes(
      const mozilla::OriginAttributes& aOriginAttrs) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetPeerAddr(mozilla::net::NetAddr* _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetSelfAddr(mozilla::net::NetAddr* _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD Bind(mozilla::net::NetAddr* aLocalAddr) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetScriptablePeerAddr(nsINetAddr** _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetScriptableSelfAddr(nsINetAddr** _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetTlsSocketControl(
      nsITLSSocketControl** aTLSSocketControl) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetSecurityCallbacks(
      nsIInterfaceRequestor** aSecurityCallbacks) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetSecurityCallbacks(
      nsIInterfaceRequestor* aSecurityCallbacks) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD IsAlive(bool* _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetTimeout(uint32_t aType, uint32_t* _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetTimeout(uint32_t aType, uint32_t aValue) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetLinger(bool aPolarity, int16_t aTimeout) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetReuseAddrPort(bool reuseAddrPort) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetConnectionFlags(uint32_t* aConnectionFlags) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetConnectionFlags(uint32_t aConnectionFlags) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetIsTRRConnection(bool isTRRConnection) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetTlsFlags(uint32_t* aTlsFlags) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetQoSBits(uint8_t* aQoSBits) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetQoSBits(uint8_t aQoSBits) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetRecvBufferSize(uint32_t* aRecvBufferSize) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetSendBufferSize(uint32_t* aSendBufferSize) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetKeepaliveEnabled(bool* aKeepaliveEnabled) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetKeepaliveEnabled(bool aKeepaliveEnabled) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetKeepaliveVals(int32_t keepaliveIdleTime,
                              int32_t keepaliveRetryInterval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetResetIPFamilyPreference(
      bool* aResetIPFamilyPreference) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetEchConfigUsed(bool* aEchConfigUsed) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetEchConfig(const nsACString& aEchConfig) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD ResolvedByTRR(bool* _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetEffectiveTRRMode(
      nsIRequest::TRRMode* aEffectiveTRRMode) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetTrrSkipReason(nsITRRSkipReason::value* aSkipReason) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetRetryDnsIfPossible(bool* aRetryDns) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetStatus(nsresult* aStatus) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD GetIsTRRConnection(bool* aIsTRRConnection) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }

  // nsITransport
  NS_IMETHOD OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
                             uint32_t aSegmentCount,
                             nsIInputStream** _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
                              uint32_t aSegmentCount,
                              nsIOutputStream** _retval) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }
  NS_IMETHOD SetEventSink(nsITransportEventSink* aSink,
                          nsIEventTarget* aEventTarget) override {
    MOZ_ASSERT(false);
    return NS_OK;
  }

  // fake except for these methods which are OK to call
  // nsISocketTransport
  NS_IMETHOD SetRecvBufferSize(uint32_t aRecvBufferSize) override {
    return NS_OK;
  }
  NS_IMETHOD SetSendBufferSize(uint32_t aSendBufferSize) override {
    return NS_OK;
  }
  // nsITransport
  NS_IMETHOD Close(nsresult aReason) override { return NS_OK; }

 protected:
  virtual ~FakeSocketTransportProvider() = default;
};

NS_IMPL_ISUPPORTS(FakeSocketTransportProvider, nsISocketTransport, nsITransport)

// Implements some common elements to WebrtcTCPSocketTestOutputStream and
// WebrtcTCPSocketTestInputStream.
class WebrtcTCPSocketTestStream {
 public:
  WebrtcTCPSocketTestStream();

  void Fail() { mMustFail = true; }

  size_t DataLength();
  template <typename T>
  void AppendElements(const T* aBuffer, size_t aLength);

 protected:
  virtual ~WebrtcTCPSocketTestStream() = default;

  nsTArray<uint8_t> mData;
  std::mutex mDataMutex;

  bool mMustFail;
};

WebrtcTCPSocketTestStream::WebrtcTCPSocketTestStream() : mMustFail(false) {}

template <typename T>
void WebrtcTCPSocketTestStream::AppendElements(const T* aBuffer,
                                               size_t aLength) {
  std::lock_guard<std::mutex> guard(mDataMutex);
  mData.AppendElements(aBuffer, aLength);
}

size_t WebrtcTCPSocketTestStream::DataLength() {
  std::lock_guard<std::mutex> guard(mDataMutex);
  return mData.Length();
}

class WebrtcTCPSocketTestInputStream : public nsIAsyncInputStream,
                                       public WebrtcTCPSocketTestStream {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIASYNCINPUTSTREAM
  NS_DECL_NSIINPUTSTREAM

  WebrtcTCPSocketTestInputStream()
      : mMaxReadSize(1024 * 1024),
        mMutex("WebrtcTCPSocketTestInputStream::mMutex"),
        mAllowCallbacks(false) {}

  void DoCallback();
  void CallCallback(const nsCOMPtr<nsIInputStreamCallback>& aCallback);
  void AllowCallbacks() { mAllowCallbacks = true; }

  size_t mMaxReadSize;

 protected:
  virtual ~WebrtcTCPSocketTestInputStream() = default;

 private:
  mutable Mutex mMutex;
  nsCOMPtr<nsIInputStreamCallback> mCallback;
  nsCOMPtr<nsIEventTarget> mCallbackTarget;

  bool mAllowCallbacks;
};

NS_IMPL_ISUPPORTS(WebrtcTCPSocketTestInputStream, nsIAsyncInputStream,
                  nsIInputStream)

nsresult WebrtcTCPSocketTestInputStream::AsyncWait(
    nsIInputStreamCallback* aCallback, uint32_t aFlags,
    uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
  MOZ_ASSERT(!aEventTarget, "no event target should be set");

  {
    MutexAutoLock lock(mMutex);
    mCallback = aCallback;
    mCallbackTarget = NS_GetCurrentThread();
  }

  if (mAllowCallbacks && DataLength() > 0) {
    DoCallback();
  }

  return NS_OK;
}

nsresult WebrtcTCPSocketTestInputStream::CloseWithStatus(nsresult aStatus) {
  return Close();
}

nsresult WebrtcTCPSocketTestInputStream::Close() { return NS_OK; }

nsresult WebrtcTCPSocketTestInputStream::Available(uint64_t* aAvailable) {
  *aAvailable = DataLength();
  return NS_OK;
}

nsresult WebrtcTCPSocketTestInputStream::StreamStatus() { return NS_OK; }

nsresult WebrtcTCPSocketTestInputStream::Read(char* aBuffer, uint32_t aCount,
                                              uint32_t* aRead) {
  std::lock_guard<std::mutex> guard(mDataMutex);
  if (mMustFail) {
    return NS_ERROR_FAILURE;
  }
  *aRead = std::min({(size_t)aCount, mData.Length(), mMaxReadSize});
  memcpy(aBuffer, mData.Elements(), *aRead);
  mData.RemoveElementsAt(0, *aRead);
  return *aRead > 0 ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
}

nsresult WebrtcTCPSocketTestInputStream::ReadSegments(nsWriteSegmentFun aWriter,
                                                      void* aClosure,
                                                      uint32_t aCount,
                                                      uint32_t* _retval) {
  MOZ_ASSERT(false);
  return NS_OK;
}

nsresult WebrtcTCPSocketTestInputStream::IsNonBlocking(bool* aIsNonBlocking) {
  *aIsNonBlocking = true;
  return NS_OK;
}

void WebrtcTCPSocketTestInputStream::CallCallback(
    const nsCOMPtr<nsIInputStreamCallback>& aCallback) {
  aCallback->OnInputStreamReady(this);
}

void WebrtcTCPSocketTestInputStream::DoCallback() {
  MutexAutoLock lock(mMutex);
  if (mCallback) {
    mCallbackTarget->Dispatch(
        NewRunnableMethod<const nsCOMPtr<nsIInputStreamCallback>&>(
            "WebrtcTCPSocketTestInputStream::DoCallback", this,
            &WebrtcTCPSocketTestInputStream::CallCallback,
            std::move(mCallback)));

    mCallbackTarget = nullptr;
  }
}

class WebrtcTCPSocketTestOutputStream : public nsIAsyncOutputStream,
                                        public WebrtcTCPSocketTestStream {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIASYNCOUTPUTSTREAM
  NS_DECL_NSIOUTPUTSTREAM

  WebrtcTCPSocketTestOutputStream()
      : mMaxWriteSize(1024 * 1024),
        mMutex("WebrtcTCPSocketTestOutputStream::mMutex") {}

  void DoCallback();
  void CallCallback(const nsCOMPtr<nsIOutputStreamCallback>& aCallback);

  std::string DataString();

  uint32_t mMaxWriteSize;

 protected:
  virtual ~WebrtcTCPSocketTestOutputStream() = default;

 private:
  mutable Mutex mMutex;
  nsCOMPtr<nsIOutputStreamCallback> mCallback;
  nsCOMPtr<nsIEventTarget> mCallbackTarget;
};

NS_IMPL_ISUPPORTS(WebrtcTCPSocketTestOutputStream, nsIAsyncOutputStream,
                  nsIOutputStream)

nsresult WebrtcTCPSocketTestOutputStream::AsyncWait(
    nsIOutputStreamCallback* aCallback, uint32_t aFlags,
    uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
  MOZ_ASSERT(!aEventTarget, "no event target should be set");

  {
    MutexAutoLock lock(mMutex);
    mCallback = aCallback;
    mCallbackTarget = NS_GetCurrentThread();
  }

  return NS_OK;
}

nsresult WebrtcTCPSocketTestOutputStream::CloseWithStatus(nsresult aStatus) {
  return Close();
}

nsresult WebrtcTCPSocketTestOutputStream::Close() { return NS_OK; }

nsresult WebrtcTCPSocketTestOutputStream::Flush() { return NS_OK; }

nsresult WebrtcTCPSocketTestOutputStream::StreamStatus() {
  return mMustFail ? NS_ERROR_FAILURE : NS_OK;
}

nsresult WebrtcTCPSocketTestOutputStream::Write(const char* aBuffer,
                                                uint32_t aCount,
                                                uint32_t* aWrote) {
  if (mMustFail) {
    return NS_ERROR_FAILURE;
  }
  *aWrote = std::min(aCount, mMaxWriteSize);
  AppendElements(aBuffer, *aWrote);
  return NS_OK;
}

nsresult WebrtcTCPSocketTestOutputStream::WriteSegments(
    nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
    uint32_t* _retval) {
  MOZ_ASSERT(false);
  return NS_OK;
}

nsresult WebrtcTCPSocketTestOutputStream::WriteFrom(nsIInputStream* aFromStream,
                                                    uint32_t aCount,
                                                    uint32_t* _retval) {
  MOZ_ASSERT(false);
  return NS_OK;
}

nsresult WebrtcTCPSocketTestOutputStream::IsNonBlocking(bool* aIsNonBlocking) {
  *aIsNonBlocking = true;
  return NS_OK;
}

void WebrtcTCPSocketTestOutputStream::CallCallback(
    const nsCOMPtr<nsIOutputStreamCallback>& aCallback) {
  aCallback->OnOutputStreamReady(this);
}

void WebrtcTCPSocketTestOutputStream::DoCallback() {
  MutexAutoLock lock(mMutex);
  if (mCallback) {
    mCallbackTarget->Dispatch(
        NewRunnableMethod<const nsCOMPtr<nsIOutputStreamCallback>&>(
            "WebrtcTCPSocketTestOutputStream::CallCallback", this,
            &WebrtcTCPSocketTestOutputStream::CallCallback,
            std::move(mCallback)));

    mCallbackTarget = nullptr;
  }
}

std::string WebrtcTCPSocketTestOutputStream::DataString() {
  std::lock_guard<std::mutex> guard(mDataMutex);
  return std::string((char*)mData.Elements(), mData.Length());
}

// Fake as in not the real WebrtcTCPSocket but real enough
class FakeWebrtcTCPSocket : public WebrtcTCPSocket {
 public:
  explicit FakeWebrtcTCPSocket(WebrtcTCPSocketCallback* aCallback)
      : WebrtcTCPSocket(aCallback) {}

 protected:
  virtual ~FakeWebrtcTCPSocket() = default;

  void InvokeOnClose(nsresult aReason) override;
  void InvokeOnConnected() override;
  void InvokeOnRead(nsTArray<uint8_t>&& aReadData) override;
};

void FakeWebrtcTCPSocket::InvokeOnClose(nsresult aReason) {
  mProxyCallbacks->OnClose(aReason);
}

void FakeWebrtcTCPSocket::InvokeOnConnected() {
  mProxyCallbacks->OnConnected("http"_ns);
}

void FakeWebrtcTCPSocket::InvokeOnRead(nsTArray<uint8_t>&& aReadData) {
  mProxyCallbacks->OnRead(std::move(aReadData));
}

class WebrtcTCPSocketTest : public MtransportTest {
 public:
  WebrtcTCPSocketTest()
      : mSocketThread(nullptr),
        mSocketTransport(nullptr),
        mInputStream(nullptr),
        mOutputStream(nullptr),
        mChannel(nullptr),
        mCallback(nullptr),
        mOnCloseCalled(false),
        mOnConnectedCalled(false) {}

  // WebrtcTCPSocketCallback forwards from mCallback
  void OnClose(nsresult aReason);
  void OnConnected(const nsACString& aProxyType);
  void OnRead(nsTArray<uint8_t>&& aReadData);

  void SetUp() override;
  void TearDown() override;
  size_t CountUnwrittenBytes() {
    size_t result;
    test_utils_->SyncDispatchToSTS(WrapRunnableRet(
        &result, this, &WebrtcTCPSocketTest::CountUnwrittenBytes_s));
    return result;
  }
  size_t CountUnwrittenBytes_s() { return mChannel->CountUnwrittenBytes(); }

  void DoTransportAvailable();

  std::string ReadDataAsString();
  std::string GetDataLarge();

  nsCOMPtr<nsIEventTarget> mSocketThread;

  nsCOMPtr<nsISocketTransport> mSocketTransport;
  RefPtr<WebrtcTCPSocketTestInputStream> mInputStream;
  RefPtr<WebrtcTCPSocketTestOutputStream> mOutputStream;
  RefPtr<FakeWebrtcTCPSocket> mChannel;
  RefPtr<WebrtcTCPSocketTestCallback> mCallback;

  std::atomic<bool> mOnCloseCalled;
  std::atomic<bool> mOnConnectedCalled;

  size_t ReadDataLength();
  template <typename T>
  void AppendReadData(const T* aBuffer, size_t aLength);

 private:
  nsTArray<uint8_t> mReadData;
  std::mutex mReadDataMutex;
};

class WebrtcTCPSocketTestCallback : public WebrtcTCPSocketCallback {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcTCPSocketTestCallback, override)

  explicit WebrtcTCPSocketTestCallback(WebrtcTCPSocketTest* aTest)
      : mTest(aTest) {}

  // WebrtcTCPSocketCallback
  void OnClose(nsresult aReason) override;
  void OnConnected(const nsACString& aProxyType) override;
  void OnRead(nsTArray<uint8_t>&& aReadData) override;

 protected:
  virtual ~WebrtcTCPSocketTestCallback() = default;

 private:
  WebrtcTCPSocketTest* mTest;
};

void WebrtcTCPSocketTest::SetUp() {
  MtransportTest::SetUp();

  nsresult rv;
  // WebrtcTCPSocket's threading model is the same as mtransport
  // all socket operations are done on the socket thread
  // callbacks are invoked on the main thread
  mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
  ASSERT_TRUE(NS_SUCCEEDED(rv));

  mSocketTransport = new FakeSocketTransportProvider();
  mInputStream = new WebrtcTCPSocketTestInputStream();
  mOutputStream = new WebrtcTCPSocketTestOutputStream();
  mCallback = new WebrtcTCPSocketTestCallback(this);
  mChannel = new FakeWebrtcTCPSocket(mCallback.get());
}

void WebrtcTCPSocketTest::TearDown() { MtransportTest::TearDown(); }

// WebrtcTCPSocketCallback
void WebrtcTCPSocketTest::OnRead(nsTArray<uint8_t>&& aReadData) {
  AppendReadData(aReadData.Elements(), aReadData.Length());
}

void WebrtcTCPSocketTest::OnConnected(const nsACString& aProxyType) {
  mOnConnectedCalled = true;
}

void WebrtcTCPSocketTest::OnClose(nsresult aReason) { mOnCloseCalled = true; }

void WebrtcTCPSocketTest::DoTransportAvailable() {
  if (!mSocketThread->IsOnCurrentThread()) {
    mSocketThread->Dispatch(
        NS_NewRunnableFunction("DoTransportAvailable", [this]() -> void {
          nsresult rv;
          rv = mChannel->OnTransportAvailable(mSocketTransport, mInputStream,
                                              mOutputStream);
          ASSERT_EQ(NS_OK, rv);
        }));
  } else {
    // should always be called on the main thread
    MOZ_ASSERT(0);
  }
}

std::string WebrtcTCPSocketTest::ReadDataAsString() {
  std::lock_guard<std::mutex> guard(mReadDataMutex);
  return std::string((char*)mReadData.Elements(), mReadData.Length());
}

std::string WebrtcTCPSocketTest::GetDataLarge() {
  std::string data;
  for (int i = 0; i < kDataLargeOuterLoopCount * kDataLargeInnerLoopCount;
       ++i) {
    data += kReadData;
  }
  return data;
}

template <typename T>
void WebrtcTCPSocketTest::AppendReadData(const T* aBuffer, size_t aLength) {
  std::lock_guard<std::mutex> guard(mReadDataMutex);
  mReadData.AppendElements(aBuffer, aLength);
}

size_t WebrtcTCPSocketTest::ReadDataLength() {
  std::lock_guard<std::mutex> guard(mReadDataMutex);
  return mReadData.Length();
}

void WebrtcTCPSocketTestCallback::OnClose(nsresult aReason) {
  mTest->OnClose(aReason);
}

void WebrtcTCPSocketTestCallback::OnConnected(const nsACString& aProxyType) {
  mTest->OnConnected(aProxyType);
}

void WebrtcTCPSocketTestCallback::OnRead(nsTArray<uint8_t>&& aReadData) {
  mTest->OnRead(std::move(aReadData));
}

}  // namespace mozilla

typedef mozilla::WebrtcTCPSocketTest WebrtcTCPSocketTest;

TEST_F(WebrtcTCPSocketTest, SetUp) {}

TEST_F(WebrtcTCPSocketTest, TransportAvailable) {
  DoTransportAvailable();
  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
}

TEST_F(WebrtcTCPSocketTest, Read) {
  DoTransportAvailable();
  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);

  mInputStream->AppendElements(kReadData, kReadDataLength);
  mInputStream->DoCallback();

  ASSERT_TRUE_WAIT(ReadDataAsString() == kReadDataString, kDefaultTestTimeout);
}

TEST_F(WebrtcTCPSocketTest, Write) {
  DoTransportAvailable();
  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);

  nsTArray<uint8_t> data;
  data.AppendElements(kReadData, kReadDataLength);
  mChannel->Write(std::move(data));

  ASSERT_TRUE_WAIT(CountUnwrittenBytes() == kReadDataLength,
                   kDefaultTestTimeout);

  mOutputStream->DoCallback();

  ASSERT_TRUE_WAIT(mOutputStream->DataString() == kReadDataString,
                   kDefaultTestTimeout);
}

TEST_F(WebrtcTCPSocketTest, ReadFail) {
  DoTransportAvailable();
  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);

  mInputStream->AppendElements(kReadData, kReadDataLength);
  mInputStream->Fail();
  mInputStream->DoCallback();

  ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
  ASSERT_EQ(0U, ReadDataLength());
}

TEST_F(WebrtcTCPSocketTest, WriteFail) {
  DoTransportAvailable();
  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);

  nsTArray<uint8_t> array;
  array.AppendElements(kReadData, kReadDataLength);
  mChannel->Write(std::move(array));

  ASSERT_TRUE_WAIT(CountUnwrittenBytes() == kReadDataLength,
                   kDefaultTestTimeout);

  mOutputStream->Fail();
  mOutputStream->DoCallback();

  ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
  ASSERT_EQ(0U, mOutputStream->DataLength());
}

TEST_F(WebrtcTCPSocketTest, ReadLarge) {
  DoTransportAvailable();
  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);

  const std::string data = GetDataLarge();

  mInputStream->AppendElements(data.c_str(), data.length());
  // make sure reading loops more than once
  mInputStream->mMaxReadSize = 3072;
  mInputStream->AllowCallbacks();
  mInputStream->DoCallback();

  ASSERT_TRUE_WAIT(ReadDataAsString() == data, kDefaultTestTimeout);
}

TEST_F(WebrtcTCPSocketTest, WriteLarge) {
  DoTransportAvailable();
  ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);

  const std::string data = GetDataLarge();

  for (int i = 0; i < kDataLargeOuterLoopCount; ++i) {
    nsTArray<uint8_t> array;
    int chunkSize = kReadDataString.length() * kDataLargeInnerLoopCount;
    int offset = i * chunkSize;
    array.AppendElements(data.c_str() + offset, chunkSize);
    mChannel->Write(std::move(array));
  }

  ASSERT_TRUE_WAIT(CountUnwrittenBytes() == data.length(), kDefaultTestTimeout);

  // make sure writing loops more than once per write request
  mOutputStream->mMaxWriteSize = 1024;
  mOutputStream->DoCallback();

  ASSERT_TRUE_WAIT(mOutputStream->DataString() == data, kDefaultTestTimeout);
}
