/* 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 "MediaTransportHandlerIPC.h"

#include "common/browser_logging/CSFLog.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/MediaTransportChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/net/SocketProcessBridgeChild.h"
#include "nsThreadUtils.h"

namespace mozilla {

static const char* mthipcLogTag = "MediaTransportHandler";
#ifdef LOGTAG
#  undef LOGTAG
#endif
#define LOGTAG mthipcLogTag

MediaTransportHandlerIPC::MediaTransportHandlerIPC() = default;

MediaTransportHandlerIPC::~MediaTransportHandlerIPC() = default;

void MediaTransportHandlerIPC::Initialize() {
  using EndpointPromise =
      MozPromise<mozilla::ipc::Endpoint<mozilla::dom::PMediaTransportChild>,
                 nsCString, true>;

  // TODO(bug 1926450): Historically we've used STS for this, but there are
  // probably better options.
  nsresult res;
  mThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
  MOZ_ASSERT(mThread);

  mInitPromise =
      net::SocketProcessBridgeChild::GetSocketProcessBridge()
          ->Then(
              GetCurrentSerialEventTarget(), __func__,
              [](const RefPtr<net::SocketProcessBridgeChild>& aBridge) {
                mozilla::ipc::Endpoint<mozilla::dom::PMediaTransportParent>
                    parentEndpoint;
                mozilla::ipc::Endpoint<mozilla::dom::PMediaTransportChild>
                    childEndpoint;
                mozilla::dom::PMediaTransport::CreateEndpoints(&parentEndpoint,
                                                               &childEndpoint);

                if (!aBridge || !aBridge->SendInitMediaTransport(
                                    std::move(parentEndpoint))) {
                  NS_WARNING(
                      "MediaTransportHandlerIPC async init failed! Webrtc "
                      "networking "
                      "will not work!");
                  return EndpointPromise::CreateAndReject(
                      nsCString("SendInitMediaTransport failed!"), __func__);
                }

                return EndpointPromise::CreateAndResolve(
                    std::move(childEndpoint), __func__);
              },
              [](const nsCString& aError) {
                return EndpointPromise::CreateAndReject(aError, __func__);
              })
          ->Then(
              mThread, __func__,
              [this, self = RefPtr<MediaTransportHandlerIPC>(this)](
                  mozilla::ipc::Endpoint<mozilla::dom::PMediaTransportChild>&&
                      aEndpoint) {
                RefPtr child = MakeRefPtr<MediaTransportChild>(this);
                aEndpoint.Bind(child);
                mChild = child;

                CSFLogDebug(LOGTAG, "%s Init done", __func__);
                return InitPromise::CreateAndResolve(true, __func__);
              },
              [=](const nsCString& aError) {
                CSFLogError(
                    LOGTAG,
                    "MediaTransportHandlerIPC async init failed! Webrtc "
                    "networking will not work! Error was %s",
                    aError.get());
                NS_WARNING(
                    "MediaTransportHandlerIPC async init failed! Webrtc "
                    "networking "
                    "will not work!");
                return InitPromise::CreateAndReject(aError, __func__);
              });
}

RefPtr<MediaTransportHandler::IceLogPromise>
MediaTransportHandlerIPC::GetIceLog(const nsCString& aPattern) {
  return mInitPromise->Then(
      mThread, __func__,
      [=, this,
       self = RefPtr<MediaTransportHandlerIPC>(this)](bool /* dummy */) {
        if (!mChild) {
          return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
        }
        // Compiler has trouble deducing the return type here for some reason,
        // so we use a temp variable as a hint.
        // SendGetIceLog _almost_ returns an IceLogPromise; the reject value
        // differs (ipc::ResponseRejectReason vs nsresult) so we need to
        // convert.
        RefPtr<IceLogPromise> promise = mChild->SendGetIceLog(aPattern)->Then(
            mThread, __func__,
            [](WebrtcGlobalLog&& aLogLines) {
              return IceLogPromise::CreateAndResolve(std::move(aLogLines),
                                                     __func__);
            },
            [](ipc::ResponseRejectReason aReason) {
              return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
            });
        return promise;
      },
      [](const nsCString& aError) {
        return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
      });
}

void MediaTransportHandlerIPC::ClearIceLog() {
  mInitPromise->Then(
      mThread, __func__,
      [this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendClearIceLog();
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::EnterPrivateMode() {
  mInitPromise->Then(
      mThread, __func__,
      [this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendEnterPrivateMode();
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::ExitPrivateMode() {
  mInitPromise->Then(
      mThread, __func__,
      [this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendExitPrivateMode();
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::CreateIceCtx(const std::string& aName) {
  CSFLogDebug(LOGTAG, "MediaTransportHandlerIPC::CreateIceCtx start");

  mInitPromise->Then(
      mThread, __func__,
      [=, this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          CSFLogDebug(LOGTAG, "%s starting", __func__);
          if (NS_WARN_IF(!mChild->SendCreateIceCtx(aName))) {
            CSFLogError(LOGTAG, "%s failed!", __func__);
          }
        }
      },
      [](const nsCString& aError) {});
}

nsresult MediaTransportHandlerIPC::SetIceConfig(
    const nsTArray<dom::RTCIceServer>& aIceServers,
    dom::RTCIceTransportPolicy aIcePolicy) {
  // PeerConnectionImpl validates before calling this.
  // The socket process will re-validate when it receives the RTCIceServer
  // array, so no pre-validation is needed here.
  mInitPromise->Then(
      mThread, __func__,
      [=, this, iceServers = aIceServers.Clone(),
       self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          if (NS_WARN_IF(!mChild->SendSetIceConfig(std::move(iceServers),
                                                   aIcePolicy))) {
            CSFLogError(LOGTAG, "%s failed!", __func__);
          }
        }
      },
      [](const nsCString& aError) {});

  return NS_OK;
}

void MediaTransportHandlerIPC::Destroy() {
  if (mChild) {
    mChild->Shutdown();
    mThread->Dispatch(NS_NewRunnableFunction(
        __func__, [child = std::move(mChild)]() { child->Close(); }));
  }
  delete this;
}

// We will probably be able to move the proxy lookup stuff into
// this class once we move mtransport to its own process.
void MediaTransportHandlerIPC::SetProxyConfig(
    NrSocketProxyConfig&& aProxyConfig) {
  mInitPromise->Then(
      mThread, __func__,
      [aProxyConfig = std::move(aProxyConfig), this,
       self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) mutable {
        if (mChild) {
          mChild->SendSetProxyConfig(aProxyConfig.GetConfig());
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::EnsureProvisionalTransport(
    const std::string& aTransportId, const std::string& aLocalUfrag,
    const std::string& aLocalPwd, int aComponentCount) {
  mInitPromise->Then(
      mThread, __func__,
      [=, this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendEnsureProvisionalTransport(aTransportId, aLocalUfrag,
                                                 aLocalPwd, aComponentCount);
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::SetTargetForDefaultLocalAddressLookup(
    const std::string& aTargetIp, uint16_t aTargetPort) {
  mInitPromise->Then(
      mThread, __func__,
      [=, this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendSetTargetForDefaultLocalAddressLookup(aTargetIp,
                                                            aTargetPort);
        }
      },
      [](const nsCString& aError) {});
}

// We set default-route-only as late as possible because it depends on what
// capture permissions have been granted on the window, which could easily
// change between Init (ie; when the PC is created) and StartIceGathering
// (ie; when we set the local description).
void MediaTransportHandlerIPC::StartIceGathering(
    bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
    // TODO(bug 1522205): It probably makes sense to look this up internally
    const nsTArray<NrIceStunAddr>& aStunAddrs) {
  mInitPromise->Then(
      mThread, __func__,
      [=, this, stunAddrs = aStunAddrs.Clone(),
       self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendStartIceGathering(aDefaultRouteOnly,
                                        aObfuscateHostAddresses, stunAddrs);
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::ActivateTransport(
    const std::string& aTransportId, const std::string& aLocalUfrag,
    const std::string& aLocalPwd, size_t aComponentCount,
    const std::string& aUfrag, const std::string& aPassword,
    const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
    SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
    bool aPrivacyRequested) {
  mInitPromise->Then(
      mThread, __func__,
      [=, this, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(),
       self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendActivateTransport(aTransportId, aLocalUfrag, aLocalPwd,
                                        aComponentCount, aUfrag, aPassword,
                                        keyDer, certDer, aAuthType, aDtlsClient,
                                        aDigests, aPrivacyRequested);
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::RemoveTransportsExcept(
    const std::set<std::string>& aTransportIds) {
  std::vector<std::string> transportIds(aTransportIds.begin(),
                                        aTransportIds.end());
  mInitPromise->Then(
      mThread, __func__,
      [this, self = RefPtr<MediaTransportHandlerIPC>(this),
       transportIds = std::move(transportIds)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendRemoveTransportsExcept(transportIds);
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::StartIceChecks(
    bool aIsControlling, const std::vector<std::string>& aIceOptions) {
  mInitPromise->Then(
      mThread, __func__,
      [=, this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendStartIceChecks(aIsControlling, aIceOptions);
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::SendPacket(const std::string& aTransportId,
                                          MediaPacket&& aPacket) {
  mInitPromise->Then(
      mThread, __func__,
      [this, self = RefPtr<MediaTransportHandlerIPC>(this), aTransportId,
       aPacket = std::move(aPacket)](bool /*dummy*/) mutable {
        if (mChild) {
          mChild->SendSendPacket(aTransportId, std::move(aPacket));
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::AddIceCandidate(
    const std::string& aTransportId, const std::string& aCandidate,
    const std::string& aUfrag, const std::string& aObfuscatedAddress) {
  mInitPromise->Then(
      mThread, __func__,
      [=, this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendAddIceCandidate(aTransportId, aCandidate, aUfrag,
                                      aObfuscatedAddress);
        }
      },
      [](const nsCString& aError) {});
}

void MediaTransportHandlerIPC::UpdateNetworkState(bool aOnline) {
  mInitPromise->Then(
      mThread, __func__,
      [=, this, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
        if (mChild) {
          mChild->SendUpdateNetworkState(aOnline);
        }
      },
      [](const nsCString& aError) {});
}

RefPtr<dom::RTCStatsPromise> MediaTransportHandlerIPC::GetIceStats(
    const std::string& aTransportId, DOMHighResTimeStamp aNow) {
  using IPCPromise = dom::PMediaTransportChild::GetIceStatsPromise;
  return mInitPromise
      ->Then(mThread, __func__,
             [aTransportId, aNow, this, self = RefPtr(this)](
                 const InitPromise::ResolveOrRejectValue& aValue) {
               if (aValue.IsReject()) {
                 return IPCPromise::CreateAndResolve(
                     MakeUnique<dom::RTCStatsCollection>(),
                     "MediaTransportHandlerIPC::GetIceStats_1");
               }
               if (!mChild) {
                 return IPCPromise::CreateAndResolve(
                     MakeUnique<dom::RTCStatsCollection>(),
                     "MediaTransportHandlerIPC::GetIceStats_1");
               }
               return mChild->SendGetIceStats(aTransportId, aNow);
             })
      ->Then(mThread, __func__, [](IPCPromise::ResolveOrRejectValue&& aValue) {
        if (aValue.IsReject()) {
          return dom::RTCStatsPromise::CreateAndResolve(
              MakeUnique<dom::RTCStatsCollection>(),
              "MediaTransportHandlerIPC::GetIceStats_2");
        }
        return dom::RTCStatsPromise::CreateAndResolve(
            std::move(aValue.ResolveValue()),
            "MediaTransportHandlerIPC::GetIceStats_2");
      });
}

MediaTransportChild::MediaTransportChild(MediaTransportHandlerIPC* aUser)
    : mMutex("MediaTransportChild"), mUser(aUser) {}

MediaTransportChild::~MediaTransportChild() = default;

mozilla::ipc::IPCResult MediaTransportChild::RecvOnCandidate(
    const string& transportId, CandidateInfo&& candidateInfo) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnCandidate(transportId, std::move(candidateInfo));
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnCandidateError(
    IceCandidateErrorInfo&& errorInfo) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnCandidateError(std::move(errorInfo));
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnAlpnNegotiated(
    const string& alpn) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnAlpnNegotiated(alpn);
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnGatheringStateChange(
    const string& transportId, const RTCIceGathererState& state) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnGatheringStateChange(transportId, state);
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnConnectionStateChange(
    const string& transportId, const RTCIceTransportState& state) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnConnectionStateChange(transportId, state);
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnPacketReceived(
    string&& transportId, MediaPacket&& packet) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnPacketReceived(std::forward<string>(transportId),
                            std::forward<MediaPacket>(packet));
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnEncryptedSending(
    const string& transportId, MediaPacket&& packet) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnEncryptedSending(transportId, std::move(packet));
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnStateChange(
    const string& transportId, const TransportLayer::State& state,
    nsTArray<nsTArray<uint8_t>>&& remoteCerts,
    Maybe<dom::RTCErrorParams> error) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnStateChange(transportId, state, std::move(remoteCerts), error);
  }
  return ipc::IPCResult::Ok();
}

mozilla::ipc::IPCResult MediaTransportChild::RecvOnRtcpStateChange(
    const string& transportId, const TransportLayer::State& state,
    Maybe<dom::RTCErrorParams> error) {
  MutexAutoLock lock(mMutex);
  if (mUser) {
    mUser->OnRtcpStateChange(transportId, state, error);
  }
  return ipc::IPCResult::Ok();
}

void MediaTransportChild::Shutdown() {
  MutexAutoLock lock(mMutex);
  mUser = nullptr;
}

}  // namespace mozilla
