// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "quiche/quic/platform/api/quic_socket_address.h"

#include <memory>
#include <sstream>

#include "quiche/quic/platform/api/quic_ip_address.h"
#include "quiche/quic/platform/api/quic_test.h"

namespace quic {
namespace {

TEST(QuicSocketAddress, Uninitialized) {
  QuicSocketAddress uninitialized;
  EXPECT_FALSE(uninitialized.IsInitialized());
}

TEST(QuicSocketAddress, ExplicitConstruction) {
  QuicSocketAddress ipv4_address(QuicIpAddress::Loopback4(), 443);
  QuicSocketAddress ipv6_address(QuicIpAddress::Loopback6(), 443);
  EXPECT_TRUE(ipv4_address.IsInitialized());
  EXPECT_EQ("127.0.0.1:443", ipv4_address.ToString());
  EXPECT_EQ("[::1]:443", ipv6_address.ToString());
  EXPECT_EQ(QuicIpAddress::Loopback4(), ipv4_address.host());
  EXPECT_EQ(QuicIpAddress::Loopback6(), ipv6_address.host());
  EXPECT_EQ(443, ipv4_address.port());
}

TEST(QuicSocketAddress, OutputToStream) {
  QuicSocketAddress ipv4_address(QuicIpAddress::Loopback4(), 443);
  std::stringstream stream;
  stream << ipv4_address;
  EXPECT_EQ("127.0.0.1:443", stream.str());
}

TEST(QuicSocketAddress, FromSockaddrIPv4) {
  union {
    sockaddr_storage storage;
    sockaddr addr;
    sockaddr_in v4;
  } address;

  memset(&address, 0, sizeof(address));
  address.v4.sin_family = AF_INET;
  address.v4.sin_addr = QuicIpAddress::Loopback4().GetIPv4();
  address.v4.sin_port = htons(443);
  EXPECT_EQ("127.0.0.1:443",
            QuicSocketAddress(&address.addr, sizeof(address.v4)).ToString());
  EXPECT_EQ("127.0.0.1:443", QuicSocketAddress(address.storage).ToString());
}

TEST(QuicSocketAddress, FromSockaddrIPv6) {
  union {
    sockaddr_storage storage;
    sockaddr addr;
    sockaddr_in6 v6;
  } address;

  memset(&address, 0, sizeof(address));
  address.v6.sin6_family = AF_INET6;
  address.v6.sin6_addr = QuicIpAddress::Loopback6().GetIPv6();
  address.v6.sin6_port = htons(443);
  EXPECT_EQ("[::1]:443",
            QuicSocketAddress(&address.addr, sizeof(address.v6)).ToString());
  EXPECT_EQ("[::1]:443", QuicSocketAddress(address.storage).ToString());
}

TEST(QuicSocketAddres, ToSockaddrIPv4) {
  union {
    sockaddr_storage storage;
    sockaddr_in v4;
  } address;

  address.storage =
      QuicSocketAddress(QuicIpAddress::Loopback4(), 443).generic_address();
  ASSERT_EQ(AF_INET, address.v4.sin_family);
  EXPECT_EQ(QuicIpAddress::Loopback4(), QuicIpAddress(address.v4.sin_addr));
  EXPECT_EQ(htons(443), address.v4.sin_port);
}

TEST(QuicSocketAddress, Normalize) {
  QuicIpAddress dual_stacked;
  ASSERT_TRUE(dual_stacked.FromString("::ffff:127.0.0.1"));
  ASSERT_TRUE(dual_stacked.IsIPv6());
  QuicSocketAddress not_normalized(dual_stacked, 443);
  QuicSocketAddress normalized = not_normalized.Normalized();
  EXPECT_EQ("[::ffff:127.0.0.1]:443", not_normalized.ToString());
  EXPECT_EQ("127.0.0.1:443", normalized.ToString());
}

// TODO(vasilvv): either ensure this works on all platforms, or deprecate and
// remove this API.
#if defined(__linux__) && !defined(ANDROID)
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>

TEST(QuicSocketAddress, FromSocket) {
  int fd;
  QuicSocketAddress address;
  bool bound = false;
  for (int port = 50000; port < 50400; port++) {
    fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
    ASSERT_GT(fd, 0);

    address = QuicSocketAddress(QuicIpAddress::Loopback6(), port);
    sockaddr_storage raw_address = address.generic_address();
    int bind_result = bind(fd, reinterpret_cast<const sockaddr*>(&raw_address),
                           sizeof(sockaddr_in6));

    if (bind_result < 0 && errno == EADDRINUSE) {
      close(fd);
      continue;
    }

    ASSERT_EQ(0, bind_result);
    bound = true;
    break;
  }
  ASSERT_TRUE(bound);

  QuicSocketAddress real_address;
  ASSERT_EQ(0, real_address.FromSocket(fd));
  ASSERT_TRUE(real_address.IsInitialized());
  EXPECT_EQ(real_address, address);
  close(fd);
}
#endif

}  // namespace
}  // namespace quic
