/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 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/. */

#include <algorithm>
#include "TCPSocketChild.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/Unused.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/dom/PBrowserChild.h"
#include "mozilla/dom/BrowserChild.h"
#include "nsITCPSocketCallback.h"
#include "TCPSocket.h"
#include "nsContentUtils.h"
#include "js/ArrayBuffer.h"  // JS::NewArrayBufferWithContents
#include "js/RootingAPI.h"   // JS::MutableHandle
#include "js/Utility.h"  // js::ArrayBufferContentsArena, JS::FreePolicy, js_pod_arena_malloc
#include "js/Value.h"  // JS::Value

using mozilla::net::gNeckoChild;

namespace IPC {

bool DeserializeArrayBuffer(JSContext* cx, const nsTArray<uint8_t>& aBuffer,
                            JS::MutableHandle<JS::Value> aVal) {
  mozilla::UniquePtr<uint8_t[], JS::FreePolicy> data(
      js_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena,
                                   aBuffer.Length()));
  if (!data) return false;
  memcpy(data.get(), aBuffer.Elements(), aBuffer.Length());

  JSObject* obj =
      JS::NewArrayBufferWithContents(cx, aBuffer.Length(), data.get());
  if (!obj) return false;
  // If JS::NewArrayBufferWithContents returns non-null, the ownership of
  // the data is transfered to obj, so we release the ownership here.
  mozilla::Unused << data.release();

  aVal.setObject(*obj);
  return true;
}

}  // namespace IPC

namespace mozilla::dom {

NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocketChildBase)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TCPSocketChildBase)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocket)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TCPSocketChildBase)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocket)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketChildBase)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketChildBase)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

TCPSocketChildBase::TCPSocketChildBase() : mIPCOpen(false) {
  mozilla::HoldJSObjects(this);
}

TCPSocketChildBase::~TCPSocketChildBase() { mozilla::DropJSObjects(this); }

NS_IMETHODIMP_(MozExternalRefCountType) TCPSocketChild::Release(void) {
  nsrefcnt refcnt = TCPSocketChildBase::Release();
  if (refcnt == 1 && mIPCOpen) {
    PTCPSocketChild::SendRequestDelete();
    return 1;
  }
  return refcnt;
}

TCPSocketChild::TCPSocketChild(const nsAString& aHost, const uint16_t& aPort,
                               nsISerialEventTarget* aTarget)
    : mHost(aHost), mPort(aPort), mIPCEventTarget(aTarget) {}

void TCPSocketChild::SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL,
                              bool aUseArrayBuffers) {
  mSocket = aSocket;

  AddIPDLReference();
  gNeckoChild->SendPTCPSocketConstructor(this, mHost, mPort);
  PTCPSocketChild::SendOpen(mHost, mPort, aUseSSL, aUseArrayBuffers);
}

void TCPSocketChildBase::ReleaseIPDLReference() {
  MOZ_ASSERT(mIPCOpen);
  mIPCOpen = false;
  mSocket = nullptr;
  this->Release();
}

void TCPSocketChildBase::AddIPDLReference() {
  MOZ_ASSERT(!mIPCOpen);
  mIPCOpen = true;
  this->AddRef();
}

TCPSocketChild::~TCPSocketChild() = default;

mozilla::ipc::IPCResult TCPSocketChild::RecvUpdateBufferedAmount(
    const uint32_t& aBuffered, const uint32_t& aTrackingNumber) {
  mSocket->UpdateBufferedAmount(aBuffered, aTrackingNumber);
  return IPC_OK();
}

mozilla::ipc::IPCResult TCPSocketChild::RecvCallback(
    const nsString& aType, const CallbackData& aData,
    const uint32_t& aReadyState) {
  mSocket->UpdateReadyState(aReadyState);

  if (aData.type() == CallbackData::Tvoid_t) {
    mSocket->FireEvent(aType);

  } else if (aData.type() == CallbackData::TTCPError) {
    const TCPError& err(aData.get_TCPError());
    mSocket->FireErrorEvent(err.name(), err.message(), err.errorCode());

  } else if (aData.type() == CallbackData::TSendableData) {
    const SendableData& data = aData.get_SendableData();

    if (data.type() == SendableData::TArrayOfuint8_t) {
      mSocket->FireDataArrayEvent(aType, data.get_ArrayOfuint8_t());
    } else if (data.type() == SendableData::TnsCString) {
      mSocket->FireDataStringEvent(aType, data.get_nsCString());
    } else {
      MOZ_CRASH("Invalid callback data type!");
    }
  } else {
    MOZ_CRASH("Invalid callback type!");
  }
  return IPC_OK();
}

void TCPSocketChild::SendSend(const nsACString& aData) {
  SendData(nsCString(aData));
}

nsresult TCPSocketChild::SendSend(const ArrayBuffer& aData,
                                  uint32_t aByteOffset, uint32_t aByteLength) {
  uint32_t buflen = aData.Length();
  uint32_t offset = std::min(buflen, aByteOffset);
  uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
  FallibleTArray<uint8_t> fallibleArr;
  if (!fallibleArr.InsertElementsAt(0, aData.Data() + offset, nbytes,
                                    fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  SendData(SendableData{std::move(fallibleArr)});
  return NS_OK;
}

void TCPSocketChild::SetSocket(TCPSocket* aSocket) { mSocket = aSocket; }

void TCPSocketChild::GetHost(nsAString& aHost) { aHost = mHost; }

void TCPSocketChild::GetPort(uint16_t* aPort) const { *aPort = mPort; }

mozilla::ipc::IPCResult TCPSocketChild::RecvRequestDelete() {
  mozilla::Unused << Send__delete__(this);
  return IPC_OK();
}

}  // namespace mozilla::dom
