current commit
260916.1 · lib,src: apply multiple updates to dtls implementation
#include "gtest/gtest.h"
#include "node_sockaddr-inl.h"

using node::SocketAddress;
using node::SocketAddressBlockList;
using node::SocketAddressLRU;

TEST(SocketAddress, SocketAddress) {
  CHECK(SocketAddress::is_numeric_host("123.123.123.123"));
  CHECK(!SocketAddress::is_numeric_host("localhost"));

  sockaddr_storage storage;
  sockaddr_storage storage2;
  SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage);
  SocketAddress::ToSockAddr(AF_INET, "1.1.1.1", 80, &storage2);

  SocketAddress addr(reinterpret_cast<const sockaddr*>(&storage));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage2));

  CHECK_EQ(addr.length(), sizeof(sockaddr_in));
  CHECK_EQ(addr.family(), AF_INET);
  CHECK_EQ(addr.address(), "123.123.123.123");
  CHECK_EQ(addr.port(), 443);

  addr.set_flow_label(12345);
  CHECK_EQ(addr.flow_label(), 0);

  CHECK_NE(addr, addr2);
  CHECK_EQ(addr, addr);

  CHECK_EQ(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr));
  CHECK_NE(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr2));

  addr.Update(reinterpret_cast<uint8_t*>(&storage2), sizeof(sockaddr_in));
  CHECK_EQ(addr.length(), sizeof(sockaddr_in));
  CHECK_EQ(addr.family(), AF_INET);
  CHECK_EQ(addr.address(), "1.1.1.1");
  CHECK_EQ(addr.port(), 80);

  SocketAddress::Map<size_t> map;
  map[addr]++;
  map[addr]++;
  CHECK_EQ(map[addr], 2);
}

TEST(SocketAddress, HashAndEqualAgree) {
  // Hash covers family, port and address. operator== memcmps the whole
  // sockaddr, so it also sees sin_zero, which is padding the kernel is not
  // obliged to zero for us. Two datagrams from one peer can therefore compare
  // unequal, which for a session table means one peer with two sessions.
  //
  // Not a broken container: operator== is stricter than Hash, so equal keys
  // still hash equal and unordered_map's invariant holds. Map keeps that
  // pairing. PeerMap is for callers that need one peer to mean one entry.
  sockaddr_storage s1, s2;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 443, &s1);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 443, &s2);

  // Same peer, dirty padding.
  reinterpret_cast<sockaddr_in*>(&s2)->sin_zero[0] = 0x01;

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&s2));

  SocketAddress::Equal equal;

  // They hash the same...
  CHECK_EQ(SocketAddress::Hash()(addr1), SocketAddress::Hash()(addr2));
  // ...and operator== disagrees, which is the inconsistency Equal exists to
  // avoid. If this ever starts passing, Equal is no longer needed.
  CHECK(!(addr1 == addr2));
  // Equal agrees with Hash.
  CHECK(equal(addr1, addr2));

  // Map pairs Hash with the default std::equal_to, which splits this single
  // peer across two entries of one bucket. Unchanged deliberately: it is what
  // SocketAddressLRU uses, and QUIC counts entries in one of those.
  SocketAddress::Map<size_t> split;
  split[addr1]++;
  split[addr2]++;
  CHECK_EQ(split.size(), 2);

  // PeerMap is the one that treats them as the peer they are.
  SocketAddress::PeerMap<size_t> map;
  map[addr1]++;
  map[addr2]++;
  CHECK_EQ(map.size(), 1);
  CHECK_EQ(map[addr1], 2);

  // Port is still part of the key.
  sockaddr_storage s3;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 8080, &s3);
  SocketAddress addr3(reinterpret_cast<const sockaddr*>(&s3));
  CHECK(!equal(addr1, addr3));
  map[addr3]++;
  CHECK_EQ(map.size(), 2);
}

TEST(SocketAddress, HashAndEqualAgreeIPv6) {
  sockaddr_storage s1, s2;
  SocketAddress::ToSockAddr(AF_INET6, "fe80::1", 443, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "fe80::1", 443, &s2);

  // flowinfo is a QoS label, not identity. It must not split the key.
  reinterpret_cast<sockaddr_in6*>(&s2)->sin6_flowinfo = 0xabcd;

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&s2));

  SocketAddress::Equal equal;

  CHECK_EQ(SocketAddress::Hash()(addr1), SocketAddress::Hash()(addr2));
  CHECK(equal(addr1, addr2));

  SocketAddress::PeerMap<size_t> map;
  map[addr1]++;
  map[addr2]++;
  CHECK_EQ(map.size(), 1);

  // scope_id *is* identity: the same link-local address on two interfaces is
  // two different peers, and must not collapse into one entry.
  sockaddr_storage s3;
  SocketAddress::ToSockAddr(AF_INET6, "fe80::1", 443, &s3);
  reinterpret_cast<sockaddr_in6*>(&s3)->sin6_scope_id = 2;
  SocketAddress addr3(reinterpret_cast<const sockaddr*>(&s3));

  CHECK(!equal(addr1, addr3));
  CHECK_NE(SocketAddress::Hash()(addr1), SocketAddress::Hash()(addr3));
  map[addr3]++;
  CHECK_EQ(map.size(), 2);
}

TEST(SocketAddress, IpHashAndIpEqual) {
  sockaddr_storage s1, s2, s3;
  // Same IP, different ports.
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 443, &s1);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 8080, &s2);
  // Different IP.
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.2", 443, &s3);

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress addr3(reinterpret_cast<const sockaddr*>(&s3));

  SocketAddress::IpHash ip_hash;
  SocketAddress::IpEqual ip_equal;

  // Same IP, different port: should hash equal and compare equal.
  CHECK_EQ(ip_hash(addr1), ip_hash(addr2));
  CHECK(ip_equal(addr1, addr2));

  // Different IP: should not compare equal.
  CHECK(!ip_equal(addr1, addr3));

  // Full Hash (includes port) should differ for same IP, different port.
  CHECK_NE(SocketAddress::Hash()(addr1), SocketAddress::Hash()(addr2));

  // IpMap should treat same-IP-different-port as the same key.
  SocketAddress::IpMap<uint16_t> map;
  map[addr1] = 1;
  map[addr2]++;  // Same IP as addr1, should increment the same entry.
  CHECK_EQ(map[addr1], 2);
  CHECK_EQ(map.size(), 1);

  map[addr3] = 10;
  CHECK_EQ(map.size(), 2);
  CHECK_EQ(map[addr3], 10);
}

TEST(SocketAddress, IpHashIPv6) {
  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "::1", 8080, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "::2", 443, &s3);

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress addr3(reinterpret_cast<const sockaddr*>(&s3));

  SocketAddress::IpHash ip_hash;
  SocketAddress::IpEqual ip_equal;

  // Same IPv6, different port: equal.
  CHECK_EQ(ip_hash(addr1), ip_hash(addr2));
  CHECK(ip_equal(addr1, addr2));

  // Different IPv6: not equal.
  CHECK(!ip_equal(addr1, addr3));

  // IpMap with IPv6 keys.
  SocketAddress::IpMap<uint16_t> map;
  map[addr1] = 5;
  map[addr2]++;
  CHECK_EQ(map[addr1], 6);
  CHECK_EQ(map.size(), 1);
}

TEST(SocketAddress, IpEqualCrossFamily) {
  sockaddr_storage s1, s2;
  SocketAddress::ToSockAddr(AF_INET, "127.0.0.1", 443, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &s2);

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&s2));

  SocketAddress::IpEqual ip_equal;

  // Different address families should never be equal.
  CHECK(!ip_equal(addr1, addr2));
}

TEST(SocketAddress, SocketAddressIPv6) {
  sockaddr_storage storage;
  SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &storage);

  SocketAddress addr(reinterpret_cast<const sockaddr*>(&storage));

  CHECK_EQ(addr.length(), sizeof(sockaddr_in6));
  CHECK_EQ(addr.family(), AF_INET6);
  CHECK_EQ(addr.address(), "::1");
  CHECK_EQ(addr.port(), 443);

  addr.set_flow_label(12345);
  CHECK_EQ(addr.flow_label(), 12345);
}

TEST(SocketAddressLRU, SocketAddressLRU) {
  struct Foo {
    int c;
    bool expired;
  };

  struct FooLRUTraits {
    using Type = Foo;

    static bool CheckExpired(const SocketAddress& address,
                             const Type& type,
                             uint64_t now) {
      return type.expired;
    }

    static void Touch(const SocketAddress& address, Type* type, uint64_t now) {
      type->expired = false;
    }
  };

  SocketAddressLRU<FooLRUTraits> lru(2);

  sockaddr_storage storage[4];

  SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage[0]);
  SocketAddress::ToSockAddr(AF_INET, "123.123.123.124", 443, &storage[1]);
  SocketAddress::ToSockAddr(AF_INET, "123.123.123.125", 443, &storage[2]);
  SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage[3]);

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&storage[0]));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage[1]));
  SocketAddress addr3(reinterpret_cast<const sockaddr*>(&storage[2]));
  SocketAddress addr4(reinterpret_cast<const sockaddr*>(&storage[3]));

  uint64_t now = uv_hrtime();
  Foo* foo = lru.Upsert(addr1, now);
  CHECK_NOT_NULL(foo);
  CHECK_EQ(foo->c, 0);
  CHECK_EQ(foo->expired, false);

  foo->c = 1;
  foo->expired = true;

  foo = lru.Upsert(addr1, now);
  CHECK_NOT_NULL(lru.Peek(addr1));
  CHECK_EQ(lru.Peek(addr1), lru.Peek(addr4));
  CHECK_EQ(lru.Peek(addr1)->c, 1);
  CHECK_EQ(lru.Peek(addr1)->expired, false);
  CHECK_EQ(lru.size(), 1);

  foo = lru.Upsert(addr2, now);
  foo->c = 2;
  foo->expired = true;
  CHECK_NOT_NULL(lru.Peek(addr2));
  CHECK_EQ(lru.Peek(addr2)->c, 2);
  CHECK_EQ(lru.size(), 2);

  foo->expired = true;

  foo = lru.Upsert(addr3, now);
  foo->c = 3;
  foo->expired = false;
  CHECK_NOT_NULL(lru.Peek(addr3));
  CHECK_EQ(lru.Peek(addr3)->c, 3);
  CHECK_EQ(lru.size(), 1);

  // addr1 was removed because we exceeded size.
  // addr2 was removed because it was expired.
  CHECK_NULL(lru.Peek(addr1));
  CHECK_NULL(lru.Peek(addr2));
}

TEST(SocketAddress, Comparison) {
  sockaddr_storage storage[6];

  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &storage[0]);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.2", 0, &storage[1]);
  SocketAddress::ToSockAddr(AF_INET6, "::1", 0, &storage[2]);
  SocketAddress::ToSockAddr(AF_INET6, "::2", 0, &storage[3]);
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.1", 0, &storage[4]);
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.2", 0, &storage[5]);

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&storage[0]));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&storage[1]));
  SocketAddress addr3(reinterpret_cast<const sockaddr*>(&storage[2]));
  SocketAddress addr4(reinterpret_cast<const sockaddr*>(&storage[3]));
  SocketAddress addr5(reinterpret_cast<const sockaddr*>(&storage[4]));
  SocketAddress addr6(reinterpret_cast<const sockaddr*>(&storage[5]));

  CHECK_EQ(addr1.compare(addr1), std::partial_ordering::equivalent);
  CHECK_EQ(addr1.compare(addr2), std::partial_ordering::less);
  CHECK_EQ(addr2.compare(addr1), std::partial_ordering::greater);
  CHECK(addr1 <= addr1);
  CHECK(addr1 < addr2);
  CHECK(addr1 <= addr2);
  CHECK(addr2 >= addr2);
  CHECK(addr2 > addr1);
  CHECK(addr2 >= addr1);

  CHECK_EQ(addr3.compare(addr3), std::partial_ordering::equivalent);
  CHECK_EQ(addr3.compare(addr4), std::partial_ordering::less);
  CHECK_EQ(addr4.compare(addr3), std::partial_ordering::greater);
  CHECK(addr3 <= addr3);
  CHECK(addr3 < addr4);
  CHECK(addr3 <= addr4);
  CHECK(addr4 >= addr4);
  CHECK(addr4 > addr3);
  CHECK(addr4 >= addr3);

  // Not comparable
  CHECK_EQ(addr1.compare(addr3), std::partial_ordering::unordered);
  CHECK_EQ(addr3.compare(addr1), std::partial_ordering::unordered);
  CHECK(!(addr1 < addr3));
  CHECK(!(addr1 > addr3));
  CHECK(!(addr1 >= addr3));
  CHECK(!(addr1 <= addr3));
  CHECK(!(addr3 < addr1));
  CHECK(!(addr3 > addr1));
  CHECK(!(addr3 >= addr1));
  CHECK(!(addr3 <= addr1));

  // Comparable
  CHECK_EQ(addr1.compare(addr5), std::partial_ordering::equivalent);
  CHECK_EQ(addr2.compare(addr6), std::partial_ordering::equivalent);
  CHECK_EQ(addr1.compare(addr6), std::partial_ordering::less);
  CHECK_EQ(addr6.compare(addr1), std::partial_ordering::greater);
  CHECK(addr1 <= addr5);
  CHECK(addr1 <= addr6);
  CHECK(addr1 < addr6);
  CHECK(addr6 > addr1);
  CHECK(addr6 >= addr1);
  CHECK(addr2 >= addr6);
  CHECK(addr2 >= addr5);
}

TEST(SocketAddress, NewAutoFamily) {
  // SocketAddress::New(host, port) without explicit family.
  // Tries AF_INET first, then AF_INET6.
  SocketAddress addr;

  // IPv4 address should succeed.
  CHECK(SocketAddress::New("192.168.1.1", 8080, &addr));
  CHECK_EQ(addr.family(), AF_INET);
  CHECK_EQ(addr.address(), "192.168.1.1");
  CHECK_EQ(addr.port(), 8080);

  // IPv6 address should succeed (fails AF_INET, falls through to AF_INET6).
  CHECK(SocketAddress::New("::1", 443, &addr));
  CHECK_EQ(addr.family(), AF_INET6);
  CHECK_EQ(addr.address(), "::1");
  CHECK_EQ(addr.port(), 443);

  // Invalid address should fail.
  CHECK(!SocketAddress::New("not_an_address", 0, &addr));
}

TEST(SocketAddress, HashIPv6) {
  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "::2", 443, &s3);

  SocketAddress a1(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress a2(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress a3(reinterpret_cast<const sockaddr*>(&s3));

  // Same address and port: hash must be equal.
  CHECK_EQ(SocketAddress::Hash()(a1), SocketAddress::Hash()(a2));

  // Different address: hash should (very likely) differ.
  CHECK_NE(SocketAddress::Hash()(a1), SocketAddress::Hash()(a3));
}

TEST(SocketAddress, IsMatchCrossFamily) {
  sockaddr_storage s1, s2, s3, s4;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.1", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "::1", 0, &s3);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.2", 0, &s4);

  SocketAddress ipv4(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress mapped(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress ipv6(reinterpret_cast<const sockaddr*>(&s3));
  SocketAddress other(reinterpret_cast<const sockaddr*>(&s4));

  // IPv4 matches its IPv4-mapped IPv6 counterpart.
  CHECK(ipv4.is_match(mapped));
  CHECK(mapped.is_match(ipv4));

  // IPv4 does not match a non-mapped IPv6 address.
  CHECK(!ipv4.is_match(ipv6));
  CHECK(!ipv6.is_match(ipv4));

  // Same family, different address.
  CHECK(!ipv4.is_match(other));

  // Self-match.
  CHECK(ipv4.is_match(ipv4));
  CHECK(ipv6.is_match(ipv6));
}

TEST(SocketAddress, InNetworkIPv4) {
  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET, "192.168.1.100", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "192.168.0.0", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &s3);

  SocketAddress addr(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress net(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress outside(reinterpret_cast<const sockaddr*>(&s3));

  CHECK(addr.is_in_network(net, 16));
  CHECK(!outside.is_in_network(net, 16));
  CHECK(!addr.is_in_network(net, 24));  // 192.168.1.x != 192.168.0.x
}

TEST(SocketAddress, InNetworkIPv6) {
  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET6, "2001:db8::1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "2001:db8::", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "2001:db9::1", 0, &s3);

  SocketAddress addr(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress net(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress outside(reinterpret_cast<const sockaddr*>(&s3));

  CHECK(addr.is_in_network(net, 32));
  CHECK(!outside.is_in_network(net, 32));

  // /128 prefix == exact match.
  CHECK(addr.is_in_network(addr, 128));
  CHECK(!outside.is_in_network(addr, 128));
}

TEST(SocketAddress, InNetworkCrossFamily) {
  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.0", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.1", 0, &s3);

  SocketAddress ipv4(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress net6(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress mapped(reinterpret_cast<const sockaddr*>(&s3));

  // IPv4 address in an IPv4-mapped IPv6 subnet.
  CHECK(ipv4.is_in_network(net6, 120));  // prefix 120 = /24 on the IPv4 part
  CHECK(mapped.is_in_network(net6, 120));

  // IPv6 address in IPv4 network.
  sockaddr_storage s4;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.0", 0, &s4);
  SocketAddress net4(reinterpret_cast<const sockaddr*>(&s4));

  CHECK(mapped.is_in_network(net4, 24));
}

TEST(SocketAddressBlockList, Simple) {
  SocketAddressBlockList bl;

  sockaddr_storage storage[2];
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &storage[0]);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.2", 0, &storage[1]);
  std::shared_ptr<SocketAddress> addr1 = std::make_shared<SocketAddress>(
      reinterpret_cast<const sockaddr*>(&storage[0]));
  std::shared_ptr<SocketAddress> addr2 = std::make_shared<SocketAddress>(
      reinterpret_cast<const sockaddr*>(&storage[1]));

  bl.AddSocketAddress(*addr1);
  bl.AddSocketAddress(*addr2);

  CHECK(bl.Apply(*addr1));
  CHECK(bl.Apply(*addr2));

  bl.RemoveSocketAddress(*addr1);

  CHECK(!bl.Apply(*addr1));
  CHECK(bl.Apply(*addr2));
}

TEST(SocketAddressBlockList, CrossFamilyAddress) {
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.1", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "::1", 0, &s3);

  SocketAddress ipv4(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress mapped(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress other(reinterpret_cast<const sockaddr*>(&s3));

  // Adding IPv4 should also match the IPv4-mapped IPv6 form.
  bl.AddSocketAddress(ipv4);
  CHECK(bl.Apply(ipv4));
  CHECK(bl.Apply(mapped));
  CHECK(!bl.Apply(other));

  // Remove should clean up cross-family counterpart.
  bl.RemoveSocketAddress(ipv4);
  CHECK(!bl.Apply(ipv4));
  CHECK(!bl.Apply(mapped));
}

TEST(SocketAddressBlockList, CrossFamilyAddressIPv6) {
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2;
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:192.168.1.1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "192.168.1.1", 0, &s2);

  SocketAddress mapped(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress ipv4(reinterpret_cast<const sockaddr*>(&s2));

  // Adding an IPv4-mapped IPv6 address should also match the IPv4 form.
  bl.AddSocketAddress(mapped);
  CHECK(bl.Apply(mapped));
  CHECK(bl.Apply(ipv4));

  // Remove the IPv6 form should clean up the IPv4 counterpart.
  bl.RemoveSocketAddress(mapped);
  CHECK(!bl.Apply(mapped));
  CHECK(!bl.Apply(ipv4));
}

TEST(SocketAddressBlockList, BatchAddresses) {
  SocketAddressBlockList bl;

  sockaddr_storage storage[3];
  SocketAddress::ToSockAddr(AF_INET, "1.1.1.1", 0, &storage[0]);
  SocketAddress::ToSockAddr(AF_INET, "2.2.2.2", 0, &storage[1]);
  SocketAddress::ToSockAddr(AF_INET, "3.3.3.3", 0, &storage[2]);

  SocketAddress addrs[3] = {
      SocketAddress(reinterpret_cast<const sockaddr*>(&storage[0])),
      SocketAddress(reinterpret_cast<const sockaddr*>(&storage[1])),
      SocketAddress(reinterpret_cast<const sockaddr*>(&storage[2])),
  };

  bl.AddSocketAddresses(addrs, 3);

  CHECK(bl.Apply(addrs[0]));
  CHECK(bl.Apply(addrs[1]));
  CHECK(bl.Apply(addrs[2]));

  sockaddr_storage s4;
  SocketAddress::ToSockAddr(AF_INET, "4.4.4.4", 0, &s4);
  SocketAddress addr4(reinterpret_cast<const sockaddr*>(&s4));
  CHECK(!bl.Apply(addr4));
}

TEST(SocketAddressBlockList, Range) {
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2, s3, s4, s5;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.10", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.5", 0, &s3);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.11", 0, &s4);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.0", 0, &s5);

  SocketAddress start(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress end(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress mid(reinterpret_cast<const sockaddr*>(&s3));
  SocketAddress above(reinterpret_cast<const sockaddr*>(&s4));
  SocketAddress below(reinterpret_cast<const sockaddr*>(&s5));

  bl.AddSocketAddressRange(start, end);

  CHECK(bl.Apply(start));
  CHECK(bl.Apply(end));
  CHECK(bl.Apply(mid));
  CHECK(!bl.Apply(above));
  CHECK(!bl.Apply(below));

  // Remove range.
  bl.RemoveSocketAddressRange(start, end);
  CHECK(!bl.Apply(mid));
}

TEST(SocketAddressBlockList, Subnet) {
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET, "192.168.1.0", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "192.168.1.100", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET, "192.168.2.1", 0, &s3);

  SocketAddress net(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress inside(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress outside(reinterpret_cast<const sockaddr*>(&s3));

  bl.AddSocketAddressMask(net, 24);

  CHECK(bl.Apply(inside));
  CHECK(!bl.Apply(outside));

  // Remove subnet.
  bl.RemoveSocketAddressMask(net, 24);
  CHECK(!bl.Apply(inside));
}

TEST(SocketAddressBlockList, SubnetIPv6) {
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET6, "2001:db8::", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "2001:db8::1", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "2001:db9::1", 0, &s3);

  SocketAddress net(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress inside(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress outside(reinterpret_cast<const sockaddr*>(&s3));

  bl.AddSocketAddressMask(net, 32);

  CHECK(bl.Apply(inside));
  CHECK(!bl.Apply(outside));

  bl.RemoveSocketAddressMask(net, 32);
  CHECK(!bl.Apply(inside));
}

TEST(SocketAddressBlockList, SubnetCrossFamily) {
  SocketAddressBlockList bl;

  // Adding an IPv4 subnet should also match IPv4-mapped IPv6 addresses.
  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.0", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.5", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET6, "::ffff:10.0.0.5", 0, &s3);

  SocketAddress net(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress ipv4(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress mapped(reinterpret_cast<const sockaddr*>(&s3));

  bl.AddSocketAddressMask(net, 24);

  CHECK(bl.Apply(ipv4));
  CHECK(bl.Apply(mapped));
}

TEST(SocketAddressBlockList, ClearAll) {
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET, "1.1.1.1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET, "192.168.0.0", 0, &s3);

  SocketAddress addr(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress rangeStart(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress subnet(reinterpret_cast<const sockaddr*>(&s3));

  sockaddr_storage s4;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.10", 0, &s4);
  SocketAddress rangeEnd(reinterpret_cast<const sockaddr*>(&s4));

  bl.AddSocketAddress(addr);
  bl.AddSocketAddressRange(rangeStart, rangeEnd);
  bl.AddSocketAddressMask(subnet, 16);

  CHECK(bl.Apply(addr));
  CHECK(bl.Apply(rangeStart));

  sockaddr_storage s5;
  SocketAddress::ToSockAddr(AF_INET, "192.168.1.1", 0, &s5);
  SocketAddress subnetAddr(reinterpret_cast<const sockaddr*>(&s5));
  CHECK(bl.Apply(subnetAddr));

  bl.Clear();

  CHECK(!bl.Apply(addr));
  CHECK(!bl.Apply(rangeStart));
  CHECK(!bl.Apply(subnetAddr));
}

TEST(SocketAddressBlockList, ParentBlockList) {
  auto parent = std::make_shared<SocketAddressBlockList>();
  SocketAddressBlockList child(parent);

  sockaddr_storage s1, s2;
  SocketAddress::ToSockAddr(AF_INET, "1.1.1.1", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "2.2.2.2", 0, &s2);

  SocketAddress addr1(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress addr2(reinterpret_cast<const sockaddr*>(&s2));

  parent->AddSocketAddress(addr1);
  child.AddSocketAddress(addr2);

  // Child should match both its own rules and parent's.
  CHECK(child.Apply(addr1));
  CHECK(child.Apply(addr2));

  // Parent should only match its own rules.
  CHECK(parent->Apply(addr1));
  CHECK(!parent->Apply(addr2));
}

TEST(SocketAddressBlockList, SubnetOverlapRemoval) {
  // Removing a broader subnet must restore narrower subnets that were
  // subsumed by the broader prefix in the trie.
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2, s3;
  SocketAddress::ToSockAddr(AF_INET, "10.0.0.0", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET, "10.1.0.0", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET, "10.1.2.3", 0, &s3);

  SocketAddress broad(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress narrow(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress target(reinterpret_cast<const sockaddr*>(&s3));

  bl.AddSocketAddressMask(broad, 8);    // 10.0.0.0/8
  bl.AddSocketAddressMask(narrow, 16);  // 10.1.0.0/16 (subsumed by /8)

  CHECK(bl.Apply(target));  // Covered by /8.

  bl.RemoveSocketAddressMask(broad, 8);

  // After removing /8, the /16 must still work.
  CHECK(bl.Apply(target));

  // Address outside /16 but inside old /8 should no longer match.
  sockaddr_storage s4;
  SocketAddress::ToSockAddr(AF_INET, "10.2.0.1", 0, &s4);
  SocketAddress outside(reinterpret_cast<const sockaddr*>(&s4));
  CHECK(!bl.Apply(outside));
}

TEST(SocketAddressBlockList, SubnetRemoveMixedFamily) {
  // Removing one family's subnet must correctly rebuild the remaining
  // rules, including those from the other family.
  SocketAddressBlockList bl;

  sockaddr_storage s1, s2, s3, s4;
  SocketAddress::ToSockAddr(AF_INET, "192.168.0.0", 0, &s1);
  SocketAddress::ToSockAddr(AF_INET6, "2001:db8::", 0, &s2);
  SocketAddress::ToSockAddr(AF_INET, "192.168.1.1", 0, &s3);
  SocketAddress::ToSockAddr(AF_INET6, "2001:db8::1", 0, &s4);

  SocketAddress ipv4Net(reinterpret_cast<const sockaddr*>(&s1));
  SocketAddress ipv6Net(reinterpret_cast<const sockaddr*>(&s2));
  SocketAddress ipv4Addr(reinterpret_cast<const sockaddr*>(&s3));
  SocketAddress ipv6Addr(reinterpret_cast<const sockaddr*>(&s4));

  bl.AddSocketAddressMask(ipv4Net, 16);
  bl.AddSocketAddressMask(ipv6Net, 32);

  CHECK(bl.Apply(ipv4Addr));
  CHECK(bl.Apply(ipv6Addr));

  // Remove IPv4 subnet — IPv6 subnet must survive the rebuild.
  bl.RemoveSocketAddressMask(ipv4Net, 16);
  CHECK(!bl.Apply(ipv4Addr));
  CHECK(bl.Apply(ipv6Addr));

  // Re-add IPv4, then remove IPv6 — IPv4 must survive.
  bl.AddSocketAddressMask(ipv4Net, 16);
  bl.RemoveSocketAddressMask(ipv6Net, 32);
  CHECK(bl.Apply(ipv4Addr));
  CHECK(!bl.Apply(ipv6Addr));
}