#include "../../base/nsSocketTransport2.h"
#include "../../base/nsSocketTransportService2.h"
#if defined(MOZ_WIDGET_ANDROID)
#  include "AndroidNetworkBlockedReason.h"
#endif
#include "gtest/gtest.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsISocketTransport.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"

namespace mozilla {
namespace net {

TEST(TestSocketTransportService, PortRemappingPreferenceReading)
{
  nsCOMPtr<nsISocketTransportService> service =
      do_GetService("@mozilla.org/network/socket-transport-service;1");
  ASSERT_TRUE(service);

  auto* sts = gSocketTransportService;
  ASSERT_TRUE(sts);

  NS_DispatchAndSpinEventLoopUntilComplete(
      "test"_ns, sts, NS_NewRunnableFunction("test", [&]() {
        auto CheckPortRemap = [&](uint16_t input, uint16_t output) -> bool {
          sts->ApplyPortRemap(&input);
          return input == output;
        };

        // Ill-formed prefs
        ASSERT_FALSE(sts->UpdatePortRemapPreference(";"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference(" ;"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("; "_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("foo"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference(" foo"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference(" foo "_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("10=20;"_ns));

        ASSERT_FALSE(sts->UpdatePortRemapPreference("1"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1="_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1,="_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-="_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-,="_ns));

        ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2,"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2-3"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,=3"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4,"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-2,3-4="_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("10000000=10"_ns));

        ASSERT_FALSE(sts->UpdatePortRemapPreference("1=2;3"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3="_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-foo=2;3=15"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=foo;3=15"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;foo=15"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2;3=foo"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1-4=2x3=15"_ns));
        ASSERT_FALSE(sts->UpdatePortRemapPreference("1+4=2;3=15"_ns));

        // Well-formed prefs
        ASSERT_TRUE(sts->UpdatePortRemapPreference("1=2"_ns));
        ASSERT_TRUE(CheckPortRemap(1, 2));
        ASSERT_TRUE(CheckPortRemap(2, 2));
        ASSERT_TRUE(CheckPortRemap(3, 3));

        ASSERT_TRUE(sts->UpdatePortRemapPreference("10=20"_ns));
        ASSERT_TRUE(CheckPortRemap(1, 1));
        ASSERT_TRUE(CheckPortRemap(2, 2));
        ASSERT_TRUE(CheckPortRemap(3, 3));
        ASSERT_TRUE(CheckPortRemap(10, 20));

        ASSERT_TRUE(sts->UpdatePortRemapPreference("100-200=1000"_ns));
        ASSERT_TRUE(CheckPortRemap(10, 10));
        ASSERT_TRUE(CheckPortRemap(99, 99));
        ASSERT_TRUE(CheckPortRemap(100, 1000));
        ASSERT_TRUE(CheckPortRemap(101, 1000));
        ASSERT_TRUE(CheckPortRemap(200, 1000));
        ASSERT_TRUE(CheckPortRemap(201, 201));

        ASSERT_TRUE(sts->UpdatePortRemapPreference("100-200,500=1000"_ns));
        ASSERT_TRUE(CheckPortRemap(10, 10));
        ASSERT_TRUE(CheckPortRemap(99, 99));
        ASSERT_TRUE(CheckPortRemap(100, 1000));
        ASSERT_TRUE(CheckPortRemap(101, 1000));
        ASSERT_TRUE(CheckPortRemap(200, 1000));
        ASSERT_TRUE(CheckPortRemap(201, 201));
        ASSERT_TRUE(CheckPortRemap(499, 499));
        ASSERT_TRUE(CheckPortRemap(500, 1000));
        ASSERT_TRUE(CheckPortRemap(501, 501));

        ASSERT_TRUE(sts->UpdatePortRemapPreference("1-3=10;5-8,12=20"_ns));
        ASSERT_TRUE(CheckPortRemap(1, 10));
        ASSERT_TRUE(CheckPortRemap(2, 10));
        ASSERT_TRUE(CheckPortRemap(3, 10));
        ASSERT_TRUE(CheckPortRemap(4, 4));
        ASSERT_TRUE(CheckPortRemap(5, 20));
        ASSERT_TRUE(CheckPortRemap(8, 20));
        ASSERT_TRUE(CheckPortRemap(11, 11));
        ASSERT_TRUE(CheckPortRemap(12, 20));

        ASSERT_TRUE(sts->UpdatePortRemapPreference("80=8080;443=8080"_ns));
        ASSERT_TRUE(CheckPortRemap(80, 8080));
        ASSERT_TRUE(CheckPortRemap(443, 8080));

        // Later rules rewrite earlier rules
        ASSERT_TRUE(sts->UpdatePortRemapPreference("10=100;10=200"_ns));
        ASSERT_TRUE(CheckPortRemap(10, 200));

        ASSERT_TRUE(sts->UpdatePortRemapPreference("10-20=100;10-20=200"_ns));
        ASSERT_TRUE(CheckPortRemap(10, 200));
        ASSERT_TRUE(CheckPortRemap(20, 200));

        ASSERT_TRUE(sts->UpdatePortRemapPreference("10-20=100;15=200"_ns));
        ASSERT_TRUE(CheckPortRemap(10, 100));
        ASSERT_TRUE(CheckPortRemap(15, 200));
        ASSERT_TRUE(CheckPortRemap(20, 100));

        ASSERT_TRUE(sts->UpdatePortRemapPreference(
            " 100 - 200 = 1000 ; 150 = 2000 "_ns));
        ASSERT_TRUE(CheckPortRemap(100, 1000));
        ASSERT_TRUE(CheckPortRemap(150, 2000));
        ASSERT_TRUE(CheckPortRemap(200, 1000));

        // Turn off any mapping
        ASSERT_TRUE(sts->UpdatePortRemapPreference(""_ns));
        for (uint32_t port = 0; port < 65536; ++port) {
          ASSERT_TRUE(CheckPortRemap((uint16_t)port, (uint16_t)port));
        }
      }));
}

TEST(TestSocketTransportService, StatusValues)
{
  static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_RESOLVING) ==
                NS_NET_STATUS_RESOLVING_HOST);
  static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_RESOLVED) ==
                NS_NET_STATUS_RESOLVED_HOST);
  static_assert(
      static_cast<nsresult>(nsISocketTransport::STATUS_CONNECTING_TO) ==
      NS_NET_STATUS_CONNECTING_TO);
  static_assert(
      static_cast<nsresult>(nsISocketTransport::STATUS_CONNECTED_TO) ==
      NS_NET_STATUS_CONNECTED_TO);
  static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_SENDING_TO) ==
                NS_NET_STATUS_SENDING_TO);
  static_assert(static_cast<nsresult>(nsISocketTransport::STATUS_WAITING_FOR) ==
                NS_NET_STATUS_WAITING_FOR);
  static_assert(
      static_cast<nsresult>(nsISocketTransport::STATUS_RECEIVING_FROM) ==
      NS_NET_STATUS_RECEIVING_FROM);
  static_assert(static_cast<nsresult>(
                    nsISocketTransport::STATUS_TLS_HANDSHAKE_STARTING) ==
                NS_NET_STATUS_TLS_HANDSHAKE_STARTING);
  static_assert(
      static_cast<nsresult>(nsISocketTransport::STATUS_TLS_HANDSHAKE_ENDED) ==
      NS_NET_STATUS_TLS_HANDSHAKE_ENDED);
}

// PR_END_OF_FILE_ERROR, PR_CONNECT_RESET_ERROR, and PR_CONNECT_ABORTED_ERROR
// should all map to NS_ERROR_NET_RESET so that HTTP transactions automatically
// retry on unexpected connection drops.
TEST(TestSocketTransportService, ErrorAccordingToNSPR)
{
  EXPECT_EQ(ErrorAccordingToNSPR(PR_END_OF_FILE_ERROR), NS_ERROR_NET_RESET);
  EXPECT_EQ(ErrorAccordingToNSPR(PR_CONNECT_RESET_ERROR), NS_ERROR_NET_RESET);
  EXPECT_EQ(ErrorAccordingToNSPR(PR_CONNECT_ABORTED_ERROR), NS_ERROR_NET_RESET);
}

#if defined(MOZ_WIDGET_ANDROID)
// 1 is ANDROID_NETWORK_BLOCKED_REASON_LNP from <android/multinetwork.h>; 0 is
// ANDROID_NETWORK_BLOCKED_REASON_NONE. Any other value is some other Android
// network-blocked reason and must not be misclassified as LNP.
TEST(TestSocketTransportService, IsAndroidNetworkBlockedReasonLNP)
{
  EXPECT_TRUE(IsAndroidNetworkBlockedReasonLNP(1));
  EXPECT_FALSE(IsAndroidNetworkBlockedReasonLNP(0));
  EXPECT_FALSE(IsAndroidNetworkBlockedReasonLNP(-1));
  EXPECT_FALSE(IsAndroidNetworkBlockedReasonLNP(2));
}
#endif  // defined(MOZ_WIDGET_ANDROID)

}  // namespace net
}  // namespace mozilla
