current commit
260925.46 · net: speed up BlockList.check for strings
#include "node_sockaddr.h"  // NOLINT(build/include_inline)
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "nbytes.h"
#include "node_debug.h"
#include "node_errors.h"
#include "node_hash.h"
#include "node_sockaddr-inl.h"  // NOLINT(build/include_inline)
#include "uv.h"
#include "v8-fast-api-calls.h"

#include <cstring>

#include <memory>
#include <string>
#include <vector>

namespace node {

using v8::Array;
using v8::CFunction;
using v8::Context;
using v8::FastApiCallbackOptions;
using v8::FastOneByteString;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Isolate;
using v8::Local;
using v8::LocalVector;
using v8::MaybeLocal;
using v8::Object;
using v8::Uint32;
using v8::Value;

namespace {
template <typename T, typename F>
SocketAddress FromUVHandle(F fn, const T& handle) {
  SocketAddress addr;
  int len = sizeof(sockaddr_storage);
  if (fn(&handle, addr.storage(), &len) == 0)
    CHECK_EQ(static_cast<size_t>(len), addr.length());
  else
    addr.storage()->sa_family = 0;
  return addr;
}
}  // namespace

bool SocketAddress::ToSockAddr(int32_t family,
                               const char* host,
                               uint32_t port,
                               sockaddr_storage* addr) {
  switch (family) {
    case AF_INET:
      return uv_ip4_addr(host, port, reinterpret_cast<sockaddr_in*>(addr)) == 0;
    case AF_INET6:
      return uv_ip6_addr(host, port, reinterpret_cast<sockaddr_in6*>(addr)) ==
             0;
    default:
      UNREACHABLE();
  }
}

bool SocketAddress::New(const char* host, uint32_t port, SocketAddress* addr) {
  return New(AF_INET, host, port, addr) || New(AF_INET6, host, port, addr);
}

bool SocketAddress::New(int32_t family,
                        const char* host,
                        uint32_t port,
                        SocketAddress* addr) {
  return ToSockAddr(
      family, host, port, reinterpret_cast<sockaddr_storage*>(addr->storage()));
}

size_t SocketAddress::Hash::operator()(const SocketAddress& addr) const {
  // Hash only the meaningful bytes (family + port + address), not the
  // full 128-byte sockaddr_storage.
  switch (addr.family()) {
    case AF_INET: {
      const sockaddr_in* ipv4 =
          reinterpret_cast<const sockaddr_in*>(addr.raw());
      uint8_t buf[6];
      memcpy(buf, &ipv4->sin_port, 2);
      memcpy(buf + 2, &ipv4->sin_addr, 4);
      return HashBytes(buf, sizeof(buf));
    }
    case AF_INET6: {
      const sockaddr_in6* ipv6 =
          reinterpret_cast<const sockaddr_in6*>(addr.raw());
      // Must hash exactly what Equal compares, scope_id included.
      uint8_t buf[22];
      memcpy(buf, &ipv6->sin6_port, 2);
      memcpy(buf + 2, &ipv6->sin6_addr, 16);
      memcpy(buf + 18, &ipv6->sin6_scope_id, 4);
      return HashBytes(buf, sizeof(buf));
    }
    default:
      UNREACHABLE();
  }
}

bool SocketAddress::Equal::operator()(const SocketAddress& a,
                                      const SocketAddress& b) const {
  if (a.family() != b.family()) return false;
  switch (a.family()) {
    case AF_INET: {
      const sockaddr_in* a4 = reinterpret_cast<const sockaddr_in*>(a.raw());
      const sockaddr_in* b4 = reinterpret_cast<const sockaddr_in*>(b.raw());
      return a4->sin_port == b4->sin_port &&
             memcmp(&a4->sin_addr, &b4->sin_addr, 4) == 0;
    }
    case AF_INET6: {
      const sockaddr_in6* a6 = reinterpret_cast<const sockaddr_in6*>(a.raw());
      const sockaddr_in6* b6 = reinterpret_cast<const sockaddr_in6*>(b.raw());
      // scope_id is part of a link-local address's identity: fe80::1 on one
      // interface is a different peer from fe80::1 on another. flowinfo is a
      // QoS label and is not.
      return a6->sin6_port == b6->sin6_port &&
             a6->sin6_scope_id == b6->sin6_scope_id &&
             memcmp(&a6->sin6_addr, &b6->sin6_addr, 16) == 0;
    }
    default:
      UNREACHABLE();
  }
}

size_t SocketAddress::IpHash::operator()(const SocketAddress& addr) const {
  // Hash only the IP address bytes, ignoring the port.
  switch (addr.family()) {
    case AF_INET: {
      const sockaddr_in* ipv4 =
          reinterpret_cast<const sockaddr_in*>(addr.raw());
      return HashBytes(reinterpret_cast<const uint8_t*>(&ipv4->sin_addr), 4);
    }
    case AF_INET6: {
      const sockaddr_in6* ipv6 =
          reinterpret_cast<const sockaddr_in6*>(addr.raw());
      return HashBytes(reinterpret_cast<const uint8_t*>(&ipv6->sin6_addr), 16);
    }
    default:
      UNREACHABLE();
  }
}

bool SocketAddress::IpEqual::operator()(const SocketAddress& a,
                                        const SocketAddress& b) const {
  if (a.family() != b.family()) return false;
  switch (a.family()) {
    case AF_INET: {
      const sockaddr_in* a4 = reinterpret_cast<const sockaddr_in*>(a.raw());
      const sockaddr_in* b4 = reinterpret_cast<const sockaddr_in*>(b.raw());
      return memcmp(&a4->sin_addr, &b4->sin_addr, 4) == 0;
    }
    case AF_INET6: {
      const sockaddr_in6* a6 = reinterpret_cast<const sockaddr_in6*>(a.raw());
      const sockaddr_in6* b6 = reinterpret_cast<const sockaddr_in6*>(b.raw());
      return memcmp(&a6->sin6_addr, &b6->sin6_addr, 16) == 0;
    }
    default:
      UNREACHABLE();
  }
}

SocketAddress SocketAddress::FromSockName(const uv_tcp_t& handle) {
  return FromUVHandle(uv_tcp_getsockname, handle);
}

SocketAddress SocketAddress::FromSockName(const uv_udp_t& handle) {
  return FromUVHandle(uv_udp_getsockname, handle);
}

SocketAddress SocketAddress::FromPeerName(const uv_tcp_t& handle) {
  return FromUVHandle(uv_tcp_getpeername, handle);
}

SocketAddress SocketAddress::FromPeerName(const uv_udp_t& handle) {
  return FromUVHandle(uv_udp_getpeername, handle);
}

namespace {
constexpr uint8_t mask[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};

bool is_match_ipv4(const SocketAddress& one, const SocketAddress& two) {
  const sockaddr_in* one_in = reinterpret_cast<const sockaddr_in*>(one.data());
  const sockaddr_in* two_in = reinterpret_cast<const sockaddr_in*>(two.data());
  return memcmp(&one_in->sin_addr, &two_in->sin_addr, sizeof(uint32_t)) == 0;
}

bool is_match_ipv6(const SocketAddress& one, const SocketAddress& two) {
  const sockaddr_in6* one_in =
      reinterpret_cast<const sockaddr_in6*>(one.data());
  const sockaddr_in6* two_in =
      reinterpret_cast<const sockaddr_in6*>(two.data());
  return memcmp(&one_in->sin6_addr, &two_in->sin6_addr, 16) == 0;
}

bool is_match_ipv4_ipv6(const SocketAddress& ipv4, const SocketAddress& ipv6) {
  const sockaddr_in* check_ipv4 =
      reinterpret_cast<const sockaddr_in*>(ipv4.data());
  const sockaddr_in6* check_ipv6 =
      reinterpret_cast<const sockaddr_in6*>(ipv6.data());

  const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&check_ipv6->sin6_addr);

  return memcmp(ptr, mask, sizeof(mask)) == 0 &&
         memcmp(ptr + sizeof(mask), &check_ipv4->sin_addr, sizeof(uint32_t)) ==
             0;
}

std::partial_ordering compare_ipv4(const SocketAddress& one,
                                   const SocketAddress& two) {
  const sockaddr_in* one_in = reinterpret_cast<const sockaddr_in*>(one.data());
  const sockaddr_in* two_in = reinterpret_cast<const sockaddr_in*>(two.data());
  const uint32_t s_addr_one = ntohl(one_in->sin_addr.s_addr);
  const uint32_t s_addr_two = ntohl(two_in->sin_addr.s_addr);

  if (s_addr_one < s_addr_two)
    return std::partial_ordering::less;
  else if (s_addr_one == s_addr_two)
    return std::partial_ordering::equivalent;
  else
    return std::partial_ordering::greater;
}

std::partial_ordering compare_ipv6(const SocketAddress& one,
                                   const SocketAddress& two) {
  const sockaddr_in6* one_in =
      reinterpret_cast<const sockaddr_in6*>(one.data());
  const sockaddr_in6* two_in =
      reinterpret_cast<const sockaddr_in6*>(two.data());
  int ret = memcmp(&one_in->sin6_addr, &two_in->sin6_addr, 16);
  if (ret < 0)
    return std::partial_ordering::less;
  else if (ret > 0)
    return std::partial_ordering::greater;
  return std::partial_ordering::equivalent;
}

std::partial_ordering compare_ipv4_ipv6(const SocketAddress& ipv4,
                                        const SocketAddress& ipv6) {
  const sockaddr_in* ipv4_in =
      reinterpret_cast<const sockaddr_in*>(ipv4.data());
  const sockaddr_in6* ipv6_in =
      reinterpret_cast<const sockaddr_in6*>(ipv6.data());

  const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&ipv6_in->sin6_addr);

  if (memcmp(ptr, mask, sizeof(mask)) != 0)
    return std::partial_ordering::unordered;

  int ret = memcmp(&ipv4_in->sin_addr, ptr + sizeof(mask), sizeof(uint32_t));

  if (ret < 0)
    return std::partial_ordering::less;
  else if (ret > 0)
    return std::partial_ordering::greater;
  return std::partial_ordering::equivalent;
}

bool in_network_ipv4(const SocketAddress& ip,
                     const SocketAddress& net,
                     int prefix) {
  uint32_t mask = ((1ull << prefix) - 1) << (32 - prefix);

  const sockaddr_in* ip_in = reinterpret_cast<const sockaddr_in*>(ip.data());
  const sockaddr_in* net_in = reinterpret_cast<const sockaddr_in*>(net.data());

  return (htonl(ip_in->sin_addr.s_addr) & mask) ==
         (htonl(net_in->sin_addr.s_addr) & mask);
}

bool in_network_ipv6(const SocketAddress& ip,
                     const SocketAddress& net,
                     int prefix) {
  // Special case, if prefix == 128, then just do a
  // straight comparison.
  if (prefix == 128)
    return compare_ipv6(ip, net) == std::partial_ordering::equivalent;

  uint8_t r = prefix % 8;
  int len = (prefix - r) / 8;
  uint8_t mask = ((1 << r) - 1) << (8 - r);

  const sockaddr_in6* ip_in = reinterpret_cast<const sockaddr_in6*>(ip.data());
  const sockaddr_in6* net_in =
      reinterpret_cast<const sockaddr_in6*>(net.data());

  if (memcmp(&ip_in->sin6_addr, &net_in->sin6_addr, len) != 0) return false;

  const uint8_t* p1 =
      reinterpret_cast<const uint8_t*>(ip_in->sin6_addr.s6_addr);
  const uint8_t* p2 =
      reinterpret_cast<const uint8_t*>(net_in->sin6_addr.s6_addr);

  return (p1[len] & mask) == (p2[len] & mask);
}

bool in_network_ipv4_ipv6(const SocketAddress& ip,
                          const SocketAddress& net,
                          int prefix) {
  if (prefix == 128)
    return compare_ipv4_ipv6(ip, net) == std::partial_ordering::equivalent;

  uint8_t r = prefix % 8;
  int len = (prefix - r) / 8;
  uint8_t mask = ((1 << r) - 1) << (8 - r);

  const sockaddr_in* ip_in = reinterpret_cast<const sockaddr_in*>(ip.data());
  const sockaddr_in6* net_in =
      reinterpret_cast<const sockaddr_in6*>(net.data());

  uint8_t ip_mask[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0};
  uint8_t* ptr = ip_mask;
  memcpy(ptr + 12, &ip_in->sin_addr, 4);

  if (memcmp(ptr, &net_in->sin6_addr, len) != 0) return false;

  ptr += len;
  const uint8_t* p2 =
      reinterpret_cast<const uint8_t*>(net_in->sin6_addr.s6_addr);

  return (ptr[0] & mask) == (p2[len] & mask);
}

bool in_network_ipv6_ipv4(const SocketAddress& ip,
                          const SocketAddress& net,
                          int prefix) {
  if (prefix == 32)
    return compare_ipv4_ipv6(net, ip) == std::partial_ordering::equivalent;

  uint32_t m = ((1ull << prefix) - 1) << (32 - prefix);

  const sockaddr_in6* ip_in = reinterpret_cast<const sockaddr_in6*>(ip.data());
  const sockaddr_in* net_in = reinterpret_cast<const sockaddr_in*>(net.data());

  const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&ip_in->sin6_addr);

  if (memcmp(ptr, mask, sizeof(mask)) != 0) return false;

  ptr += sizeof(mask);
  uint32_t check = nbytes::ReadUint32BE(ptr);

  return (check & m) == (htonl(net_in->sin_addr.s_addr) & m);
}
}  // namespace

// TODO(@jasnell): The implementations of is_match, compare, and
// is_in_network have not been performance optimized and could
// likely benefit from work on more performant approaches.

bool SocketAddress::is_match(const SocketAddress& other) const {
  switch (family()) {
    case AF_INET:
      switch (other.family()) {
        case AF_INET:
          return is_match_ipv4(*this, other);
        case AF_INET6:
          return is_match_ipv4_ipv6(*this, other);
      }
      break;
    case AF_INET6:
      switch (other.family()) {
        case AF_INET:
          return is_match_ipv4_ipv6(other, *this);
        case AF_INET6:
          return is_match_ipv6(*this, other);
      }
      break;
  }
  return false;
}

std::partial_ordering SocketAddress::compare(const SocketAddress& other) const {
  switch (family()) {
    case AF_INET:
      switch (other.family()) {
        case AF_INET:
          return compare_ipv4(*this, other);
        case AF_INET6:
          return compare_ipv4_ipv6(*this, other);
      }
      break;
    case AF_INET6:
      switch (other.family()) {
        case AF_INET: {
          auto c = compare_ipv4_ipv6(other, *this);
          if (c == std::partial_ordering::unordered) {
            return std::partial_ordering::unordered;
          } else if (c == std::partial_ordering::equivalent) {
            return std::partial_ordering::equivalent;
          } else if (c == std::partial_ordering::less) {
            return std::partial_ordering::greater;
          } else if (c == std::partial_ordering::greater) {
            return std::partial_ordering::less;
          }
          break;
        }
        case AF_INET6:
          return compare_ipv6(*this, other);
      }
      break;
  }
  return std::partial_ordering::unordered;
}

bool SocketAddress::is_in_network(const SocketAddress& other,
                                  int prefix) const {
  switch (family()) {
    case AF_INET:
      switch (other.family()) {
        case AF_INET:
          return in_network_ipv4(*this, other, prefix);
        case AF_INET6:
          return in_network_ipv4_ipv6(*this, other, prefix);
      }
      break;
    case AF_INET6:
      switch (other.family()) {
        case AF_INET:
          return in_network_ipv6_ipv4(*this, other, prefix);
        case AF_INET6:
          return in_network_ipv6(*this, other, prefix);
      }
      break;
  }

  return false;
}

SocketAddressBlockList::SocketAddressBlockList(
    std::shared_ptr<SocketAddressBlockList> parent)
    : parent_(parent) {}

// --- SubnetTrie implementation ---

namespace {
inline int GetBit(const uint8_t* bytes, int bit_index) {
  return (bytes[bit_index >> 3] >> (7 - (bit_index & 7))) & 1;
}

inline const uint8_t* GetAddressBytes(const SocketAddress& addr, int* bits) {
  if (addr.family() == AF_INET) {
    const auto* in = reinterpret_cast<const sockaddr_in*>(addr.data());
    *bits = 32;
    return reinterpret_cast<const uint8_t*>(&in->sin_addr);
  }
  const auto* in6 = reinterpret_cast<const sockaddr_in6*>(addr.data());
  *bits = 128;
  return reinterpret_cast<const uint8_t*>(&in6->sin6_addr);
}
}  // namespace

void SocketAddressBlockList::SubnetTrie::Insert(const uint8_t* address_bytes,
                                                int prefix_length) {
  if (root_ == nullptr) {
    root_ = std::make_unique<Node>();
  }

  Node* node = root_.get();
  for (int i = 0; i < prefix_length; i++) {
    if (node->terminal) {
      // A broader prefix already covers this subnet. No-op.
      return;
    }
    int bit = GetBit(address_bytes, i);
    if (node->children[bit] == nullptr) {
      node->children[bit] = std::make_unique<Node>();
    }
    node = node->children[bit].get();
  }

  if (!node->terminal) {
    node->terminal = true;
    count_++;
    // Prune children — this prefix subsumes all longer prefixes below it.
    node->children[0].reset();
    node->children[1].reset();
  }
}

bool SocketAddressBlockList::SubnetTrie::Lookup(const uint8_t* address_bytes,
                                                int address_bits) const {
  if (root_ == nullptr) return false;

  const Node* node = root_.get();
  // A terminal root means prefix /0 — matches everything.
  if (node->terminal) return true;

  for (int i = 0; i < address_bits; i++) {
    int bit = GetBit(address_bytes, i);
    node = node->children[bit].get();
    if (node == nullptr) return false;
    if (node->terminal) return true;
  }
  return false;
}

void SocketAddressBlockList::SubnetTrie::Clear() {
  root_.reset();
  count_ = 0;
}

void SocketAddressBlockList::AddSocketAddressImpl(
    const SocketAddress& address) {
  if (address_rules_.count(address) == 0) {
    address_count_++;
  }
  address_rules_[address] = address;
  // Insert the cross-family counterpart so that both IPv4 and
  // IPv4-mapped IPv6 lookups resolve in O(1).
  if (address.family() == AF_INET) {
    // Map 1.2.3.4 -> ::ffff:1.2.3.4
    std::string mapped = "::ffff:" + address.address();
    SocketAddress ipv6;
    if (SocketAddress::New(AF_INET6, mapped.c_str(), address.port(), &ipv6)) {
      address_rules_[ipv6] = address;
    }
  } else if (address.family() == AF_INET6) {
    // Check if this is an IPv4-mapped IPv6 address (::ffff:x.x.x.x)
    // and insert the IPv4 counterpart if so.
    const sockaddr_in6* in6 =
        reinterpret_cast<const sockaddr_in6*>(address.data());
    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&in6->sin6_addr);
    constexpr uint8_t ipv4_mapped_prefix[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
    if (memcmp(bytes, ipv4_mapped_prefix, sizeof(ipv4_mapped_prefix)) == 0) {
      sockaddr_in ipv4_addr{};
      ipv4_addr.sin_family = AF_INET;
      ipv4_addr.sin_port = in6->sin6_port;
      memcpy(&ipv4_addr.sin_addr, bytes + sizeof(ipv4_mapped_prefix), 4);
      SocketAddress ipv4(reinterpret_cast<const sockaddr*>(&ipv4_addr));
      address_rules_[ipv4] = address;
    }
  }
}

void SocketAddressBlockList::AddSocketAddress(const SocketAddress& address) {
  RwLock::ScopedLock lock(mutex_);
  AddSocketAddressImpl(address);
}

void SocketAddressBlockList::AddSocketAddresses(const SocketAddress* addresses,
                                                size_t count) {
  RwLock::ScopedLock lock(mutex_);
  for (size_t i = 0; i < count; i++) {
    AddSocketAddressImpl(addresses[i]);
  }
}

void SocketAddressBlockList::RemoveSocketAddress(const SocketAddress& address) {
  RwLock::ScopedLock lock(mutex_);
  if (address_rules_.erase(address)) {
    address_count_--;
  }
  // Also remove the cross-family counterpart.
  if (address.family() == AF_INET) {
    std::string mapped = "::ffff:" + address.address();
    SocketAddress ipv6;
    if (SocketAddress::New(AF_INET6, mapped.c_str(), address.port(), &ipv6)) {
      address_rules_.erase(ipv6);
    }
  } else if (address.family() == AF_INET6) {
    const sockaddr_in6* in6 =
        reinterpret_cast<const sockaddr_in6*>(address.data());
    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&in6->sin6_addr);
    constexpr uint8_t ipv4_mapped_prefix[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
    if (memcmp(bytes, ipv4_mapped_prefix, sizeof(ipv4_mapped_prefix)) == 0) {
      sockaddr_in ipv4_addr{};
      ipv4_addr.sin_family = AF_INET;
      ipv4_addr.sin_port = in6->sin6_port;
      memcpy(&ipv4_addr.sin_addr, bytes + sizeof(ipv4_mapped_prefix), 4);
      SocketAddress ipv4(reinterpret_cast<const sockaddr*>(&ipv4_addr));
      address_rules_.erase(ipv4);
    }
  }
}

void SocketAddressBlockList::AddSocketAddressRange(const SocketAddress& start,
                                                   const SocketAddress& end) {
  DCHECK(!(start > end));
  RwLock::ScopedLock lock(mutex_);
  std::unique_ptr<Rule> rule =
      std::make_unique<SocketAddressRangeRule>(start, end);
  rules_.emplace_front(std::move(rule));
}

void SocketAddressBlockList::AddSocketAddressMask(const SocketAddress& network,
                                                  int prefix) {
  RwLock::ScopedLock lock(mutex_);
  int bits;
  const uint8_t* bytes = GetAddressBytes(network, &bits);

  if (network.family() == AF_INET) {
    ipv4_subnets_.Insert(bytes, prefix);
    // Also insert into IPv6 trie as ::ffff:x.x.x.x with prefix+96.
    uint8_t mapped[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
    memcpy(mapped + 12, bytes, 4);
    ipv6_subnets_.Insert(mapped, prefix + 96);
  } else {
    ipv6_subnets_.Insert(bytes, prefix);
    // Check if this is a ::ffff:x.x.x.x/N subnet — if so, also insert
    // the IPv4 portion into the IPv4 trie.
    constexpr uint8_t v4mapped[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
    if (prefix >= 96 && memcmp(bytes, v4mapped, 12) == 0) {
      ipv4_subnets_.Insert(bytes + 12, prefix - 96);
    }
  }

  // Keep metadata for ListRules serialization.
  subnet_rules_.emplace_front(
      std::make_unique<SocketAddressMaskRule>(network, prefix));
}

void SocketAddressBlockList::RemoveSocketAddressRange(
    const SocketAddress& start, const SocketAddress& end) {
  RwLock::ScopedLock lock(mutex_);
  // rules_ contains only SocketAddressRangeRule instances (subnet rules
  // are stored separately in subnet_rules_).
  for (auto it = rules_.begin(); it != rules_.end(); ++it) {
    auto* range = static_cast<SocketAddressRangeRule*>(it->get());
    if (range->start == start && range->end == end) {
      rules_.erase(it);
      return;
    }
  }
}

void SocketAddressBlockList::RemoveSocketAddressMask(
    const SocketAddress& network, int prefix) {
  RwLock::ScopedLock lock(mutex_);

  // Remove from subnet_rules_ metadata list.
  bool found = false;
  for (auto it = subnet_rules_.begin(); it != subnet_rules_.end(); ++it) {
    if ((*it)->network == network && (*it)->prefix == prefix) {
      subnet_rules_.erase(it);
      found = true;
      break;
    }
  }
  if (!found) return;

  // Rebuild both tries from the remaining subnet_rules_. This handles the
  // case where a broader prefix had subsumed narrower ones in the trie --
  // simply removing the broader prefix from the trie would not restore the
  // narrower entries that were pruned on insert. Rebuilding is O(n) in the
  // number of subnet rules but removal is not a hot path.
  ipv4_subnets_.Clear();
  ipv6_subnets_.Clear();
  for (const auto& rule : subnet_rules_) {
    int bits;
    const uint8_t* b = GetAddressBytes(rule->network, &bits);
    if (rule->network.family() == AF_INET) {
      ipv4_subnets_.Insert(b, rule->prefix);
      uint8_t mapped[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
      memcpy(mapped + 12, b, 4);
      ipv6_subnets_.Insert(mapped, rule->prefix + 96);
    } else {
      ipv6_subnets_.Insert(b, rule->prefix);
      constexpr uint8_t v4mapped[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
      if (rule->prefix >= 96 && memcmp(b, v4mapped, 12) == 0) {
        ipv4_subnets_.Insert(b + 12, rule->prefix - 96);
      }
    }
  }
}

bool SocketAddressBlockList::Apply(const SocketAddress& address) {
  RwLock::ScopedReadLock lock(mutex_);
  // O(1) lookup for exact address matches. The address_rules_ map
  // uses IpHash/IpEqual (port-insensitive, family-sensitive).
  if (address_rules_.count(address)) return true;

  // O(prefix_length) lookup for subnet/mask rules via radix trie.
  int bits;
  const uint8_t* bytes = GetAddressBytes(address, &bits);
  if (address.family() == AF_INET) {
    if (ipv4_subnets_.Lookup(bytes, bits)) return true;
    // Also check IPv6 trie for ::ffff:x.x.x.x subnets.
    uint8_t mapped[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
    memcpy(mapped + 12, bytes, 4);
    if (ipv6_subnets_.Lookup(mapped, 128)) return true;
  } else {
    if (ipv6_subnets_.Lookup(bytes, bits)) return true;
    // Check if this is ::ffff:x.x.x.x — also check IPv4 trie.
    constexpr uint8_t v4mapped[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
    if (memcmp(bytes, v4mapped, 12) == 0) {
      if (ipv4_subnets_.Lookup(bytes + 12, 32)) return true;
    }
  }

  // Linear scan for range rules only. Subnet rules are in the trie.
  for (const auto& rule : rules_) {
    if (rule->Apply(address)) return true;
  }
  return parent_ ? parent_->Apply(address) : false;
}

void SocketAddressBlockList::Clear() {
  RwLock::ScopedLock lock(mutex_);
  rules_.clear();
  address_rules_.clear();
  address_count_ = 0;
  ipv4_subnets_.Clear();
  ipv6_subnets_.Clear();
  subnet_rules_.clear();
}

SocketAddressBlockList::SocketAddressRangeRule::SocketAddressRangeRule(
    const SocketAddress& start_, const SocketAddress& end_)
    : start(start_), end(end_) {}

SocketAddressBlockList::SocketAddressMaskRule::SocketAddressMaskRule(
    const SocketAddress& network_, int prefix_)
    : network(network_), prefix(prefix_) {}

bool SocketAddressBlockList::SocketAddressRangeRule::Apply(
    const SocketAddress& address) {
  return address >= start && address <= end;
}

std::string SocketAddressBlockList::SocketAddressRangeRule::ToString() {
  std::string ret = "Range: ";
  ret += start.family() == AF_INET ? "IPv4" : "IPv6";
  ret += " ";
  ret += start.address();
  ret += "-";
  ret += end.address();
  return ret;
}

bool SocketAddressBlockList::SocketAddressMaskRule::Apply(
    const SocketAddress& address) {
  return address.is_in_network(network, prefix);
}

std::string SocketAddressBlockList::SocketAddressMaskRule::ToString() {
  std::string ret = "Subnet: ";
  ret += network.family() == AF_INET ? "IPv4" : "IPv6";
  ret += " ";
  ret += network.address();
  ret += "/" + std::to_string(prefix);
  return ret;
}

MaybeLocal<Array> SocketAddressBlockList::ListRules(Environment* env) {
  RwLock::ScopedReadLock lock(mutex_);
  LocalVector<Value> rules(env->isolate());
  if (!ListRules(env, &rules)) return MaybeLocal<Array>();
  return Array::New(env->isolate(), rules.data(), rules.size());
}

bool SocketAddressBlockList::ListRules(Environment* env,
                                       LocalVector<Value>* rules) {
  // List local rules first, then parent rules, matching the
  // evaluation order in Apply().
  //
  // address_rules_ may contain cross-family duplicates (e.g. both
  // 1.1.1.1 and ::ffff:1.1.1.1 map to the same original address).
  // Track which originals have been listed to avoid duplicates.
  SocketAddress::Map<bool> seen;
  for (const auto& [_, address] : address_rules_) {
    if (seen.count(address)) continue;
    seen[address] = true;
    std::string str = "Address: ";
    str += address.family() == AF_INET ? "IPv4" : "IPv6";
    str += " ";
    str += address.address();
    Local<Value> v;
    if (!ToV8Value(env->context(), str).ToLocal(&v)) return false;
    rules->push_back(v);
  }
  for (const auto& rule : subnet_rules_) {
    Local<Value> str;
    if (!rule->ToV8String(env).ToLocal(&str)) return false;
    rules->push_back(str);
  }
  for (const auto& rule : rules_) {
    Local<Value> str;
    if (!rule->ToV8String(env).ToLocal(&str)) return false;
    rules->push_back(str);
  }
  return !parent_ || parent_->ListRules(env, rules);
}

void SocketAddressBlockList::MemoryInfo(node::MemoryTracker* tracker) const {
  tracker->TrackField("rules", rules_);
  tracker->TrackFieldWithSize("address_rules",
                              address_rules_.size() * sizeof(SocketAddress));
  tracker->TrackField("subnet_rules", subnet_rules_);
}

void SocketAddressBlockList::SocketAddressRangeRule::MemoryInfo(
    node::MemoryTracker* tracker) const {
  tracker->TrackField("start", start);
  tracker->TrackField("end", end);
}

void SocketAddressBlockList::SocketAddressMaskRule::MemoryInfo(
    node::MemoryTracker* tracker) const {
  tracker->TrackField("network", network);
}

SocketAddressBlockListWrap::SocketAddressBlockListWrap(
    Environment* env,
    Local<Object> wrap,
    std::shared_ptr<SocketAddressBlockList> blocklist)
    : BaseObject(env, wrap), blocklist_(std::move(blocklist)) {
  MakeWeak();
}

BaseObjectPtr<SocketAddressBlockListWrap> SocketAddressBlockListWrap::New(
    Environment* env) {
  Local<Object> obj;
  if (!env->blocklist_constructor_template()
           ->InstanceTemplate()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return nullptr;
  }
  BaseObjectPtr<SocketAddressBlockListWrap> wrap =
      MakeBaseObject<SocketAddressBlockListWrap>(env, obj);
  CHECK(wrap);
  return wrap;
}

BaseObjectPtr<SocketAddressBlockListWrap> SocketAddressBlockListWrap::New(
    Environment* env, std::shared_ptr<SocketAddressBlockList> blocklist) {
  Local<Object> obj;
  if (!env->blocklist_constructor_template()
           ->InstanceTemplate()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return nullptr;
  }
  BaseObjectPtr<SocketAddressBlockListWrap> wrap =
      MakeBaseObject<SocketAddressBlockListWrap>(
          env, obj, std::move(blocklist));
  CHECK(wrap);
  return wrap;
}

void SocketAddressBlockListWrap::New(const FunctionCallbackInfo<Value>& args) {
  CHECK(args.IsConstructCall());
  Environment* env = Environment::GetCurrent(args);
  new SocketAddressBlockListWrap(env, args.This());
}

void SocketAddressBlockListWrap::AddAddress(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(SocketAddressBase::HasInstance(env, args[0]));
  SocketAddressBase* addr;
  ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);

  wrap->blocklist_->AddSocketAddress(*addr->address());

  args.GetReturnValue().Set(true);
}

void SocketAddressBlockListWrap::AddAddresses(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(args[0]->IsArray());
  Local<Array> arr = args[0].As<Array>();
  uint32_t len = arr->Length();

  std::vector<SocketAddress> addresses;
  addresses.reserve(len);

  for (uint32_t i = 0; i < len; i++) {
    Local<Value> item;
    if (!arr->Get(env->context(), i).ToLocal(&item)) return;
    CHECK(SocketAddressBase::HasInstance(env, item));
    SocketAddressBase* addr;
    ASSIGN_OR_RETURN_UNWRAP(&addr, item.As<Object>());
    addresses.push_back(*addr->address());
  }

  wrap->blocklist_->AddSocketAddresses(addresses.data(), addresses.size());
  args.GetReturnValue().Set(true);
}

void SocketAddressBlockListWrap::AddRange(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(SocketAddressBase::HasInstance(env, args[0]));
  CHECK(SocketAddressBase::HasInstance(env, args[1]));

  SocketAddressBase* start_addr;
  SocketAddressBase* end_addr;
  ASSIGN_OR_RETURN_UNWRAP(&start_addr, args[0]);
  ASSIGN_OR_RETURN_UNWRAP(&end_addr, args[1]);

  // Starting address must come before the end address
  if (*start_addr->address() > *end_addr->address())
    return args.GetReturnValue().Set(false);

  wrap->blocklist_->AddSocketAddressRange(*start_addr->address(),
                                          *end_addr->address());

  args.GetReturnValue().Set(true);
}

void SocketAddressBlockListWrap::AddSubnet(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(SocketAddressBase::HasInstance(env, args[0]));
  CHECK(args[1]->IsInt32());

  SocketAddressBase* addr;
  ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);

  int32_t prefix;
  if (!args[1]->Int32Value(env->context()).To(&prefix)) {
    return;
  }

  CHECK_IMPLIES(addr->address()->family() == AF_INET, prefix <= 32);
  CHECK_IMPLIES(addr->address()->family() == AF_INET6, prefix <= 128);
  CHECK_GE(prefix, 0);

  wrap->blocklist_->AddSocketAddressMask(*addr->address(), prefix);

  args.GetReturnValue().Set(true);
}

void SocketAddressBlockListWrap::RemoveAddress(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(SocketAddressBase::HasInstance(env, args[0]));
  SocketAddressBase* addr;
  ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);

  wrap->blocklist_->RemoveSocketAddress(*addr->address());
}

void SocketAddressBlockListWrap::RemoveRange(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(SocketAddressBase::HasInstance(env, args[0]));
  CHECK(SocketAddressBase::HasInstance(env, args[1]));

  SocketAddressBase* start_addr;
  SocketAddressBase* end_addr;
  ASSIGN_OR_RETURN_UNWRAP(&start_addr, args[0]);
  ASSIGN_OR_RETURN_UNWRAP(&end_addr, args[1]);

  wrap->blocklist_->RemoveSocketAddressRange(*start_addr->address(),
                                             *end_addr->address());
}

void SocketAddressBlockListWrap::RemoveSubnet(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(SocketAddressBase::HasInstance(env, args[0]));
  CHECK(args[1]->IsInt32());

  SocketAddressBase* addr;
  ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);

  int32_t prefix;
  if (!args[1]->Int32Value(env->context()).To(&prefix)) {
    return;
  }

  wrap->blocklist_->RemoveSocketAddressMask(*addr->address(), prefix);
}

void SocketAddressBlockListWrap::Check(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(SocketAddressBase::HasInstance(env, args[0]));
  SocketAddressBase* addr;
  ASSIGN_OR_RETURN_UNWRAP(&addr, args[0]);

  args.GetReturnValue().Set(wrap->blocklist_->Apply(*addr->address()));
}

bool SocketAddressBlockListWrap::FastCheck(Local<Object> receiver,
                                           Local<Object> addr_obj) {
  TRACK_V8_FAST_API_CALL("blocklist.check");
  SocketAddressBlockListWrap* wrap =
      FromJSObject<SocketAddressBlockListWrap>(receiver);
  SocketAddressBase* addr = FromJSObject<SocketAddressBase>(addr_obj);
  return wrap->blocklist_->Apply(*addr->address());
}

CFunction SocketAddressBlockListWrap::fast_check_(
    CFunction::Make(&SocketAddressBlockListWrap::FastCheck));

void SocketAddressBlockListWrap::CheckString(
    const FunctionCallbackInfo<Value>& args) {
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(args[0]->IsString());
  CHECK(args[1]->IsInt32());

  Utf8Value address(args.GetIsolate(), args[0]);
  int32_t family = args[1].As<Int32>()->Value();

  SocketAddress addr;
  if (!SocketAddress::New(family, *address, 0, &addr)) {
    // Invalid address string — return false (not blocked).
    args.GetReturnValue().Set(false);
    return;
  }

  args.GetReturnValue().Set(wrap->blocklist_->Apply(addr));
}

bool SocketAddressBlockListWrap::FastCheckString(
    Local<Object> receiver,
    const FastOneByteString& address,
    int32_t family,
    // NOLINTNEXTLINE(runtime/references) This is V8 api.
    FastApiCallbackOptions& options) {
  // FastOneByteString is not NUL-terminated. Copy onto the stack
  // before any other work: a GC would invalidate `address`.
  //
  // uv_ip6_addr only needs the address part (≤39 chars) plus an
  // optional %zone. A zone longer than UV_IF_NAMESIZE is unknown
  // (scope_id 0), same as omitting it. Keep that behavior when the
  // full string does not fit so Fast API and CheckString agree.
  constexpr size_t kMax = INET6_ADDRSTRLEN + UV_IF_NAMESIZE;
  char buf[kMax];
  if (address.length < kMax) {
    memcpy(buf, address.data, address.length);
    buf[address.length] = '\0';
  } else if (family == AF_INET6) {
    // uv_ip4_addr rejects %zone. Only IPv6 may drop an overlong zone
    // (unknown zone → scope_id 0, same as uv_ip6_addr).
    // uv_ip6_addr copies the address part into a 40-byte buffer
    // (39 chars + NUL). A 40+ char prefix (mixed notation) is
    // truncated and fails inet_pton — do not keep the full prefix.
    const char* percent =
        static_cast<const char*>(memchr(address.data, '%', address.length));
    if (percent == nullptr) return false;
    const size_t addr_len = static_cast<size_t>(percent - address.data);
    if (addr_len >= 40) return false;
    memcpy(buf, address.data, addr_len);
    buf[addr_len] = '\0';
  } else {
    return false;
  }
  USE(options);

  TRACK_V8_FAST_API_CALL("blocklist.checkString");
  SocketAddressBlockListWrap* wrap =
      FromJSObject<SocketAddressBlockListWrap>(receiver);
  SocketAddress addr;
  if (!SocketAddress::New(family, buf, 0, &addr)) return false;
  return wrap->blocklist_->Apply(addr);
}

CFunction SocketAddressBlockListWrap::fast_check_string_(
    CFunction::Make(&SocketAddressBlockListWrap::FastCheckString));

void SocketAddressBlockListWrap::GetRules(
    const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  Local<Array> rules;
  if (wrap->blocklist_->ListRules(env).ToLocal(&rules))
    args.GetReturnValue().Set(rules);
}

void SocketAddressBlockListWrap::GetSize(
    const FunctionCallbackInfo<Value>& args) {
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  args.GetReturnValue().Set(static_cast<double>(wrap->blocklist_->size()));
}

void SocketAddressBlockListWrap::Clear(
    const FunctionCallbackInfo<Value>& args) {
  SocketAddressBlockListWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  wrap->blocklist_->Clear();
}

void SocketAddressBlockListWrap::MemoryInfo(MemoryTracker* tracker) const {
  blocklist_->MemoryInfo(tracker);
}

std::unique_ptr<worker::TransferData>
SocketAddressBlockListWrap::CloneForMessaging() const {
  return std::make_unique<TransferData>(this);
}

bool SocketAddressBlockListWrap::HasInstance(Environment* env,
                                             Local<Value> value) {
  return GetConstructorTemplate(env)->HasInstance(value);
}

Local<FunctionTemplate> SocketAddressBlockListWrap::GetConstructorTemplate(
    Environment* env) {
  Local<FunctionTemplate> tmpl = env->blocklist_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, SocketAddressBlockListWrap::New);
    tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "BlockList"));
    tmpl->InstanceTemplate()->SetInternalFieldCount(kInternalFieldCount);
    SetProtoMethod(isolate, tmpl, "addAddress", AddAddress);
    SetProtoMethod(isolate, tmpl, "addAddresses", AddAddresses);
    SetProtoMethod(isolate, tmpl, "addRange", AddRange);
    SetProtoMethod(isolate, tmpl, "addSubnet", AddSubnet);
    SetProtoMethod(isolate, tmpl, "removeAddress", RemoveAddress);
    SetProtoMethod(isolate, tmpl, "removeRange", RemoveRange);
    SetProtoMethod(isolate, tmpl, "removeSubnet", RemoveSubnet);
    SetFastMethod(
        isolate, tmpl->PrototypeTemplate(), "check", Check, &fast_check_);
    SetFastMethod(isolate,
                  tmpl->PrototypeTemplate(),
                  "checkString",
                  CheckString,
                  &fast_check_string_);
    SetProtoMethod(isolate, tmpl, "getRules", GetRules);
    SetProtoMethodNoSideEffect(isolate, tmpl, "getSize", GetSize);
    SetProtoMethod(isolate, tmpl, "clear", Clear);
    env->set_blocklist_constructor_template(tmpl);
  }
  return tmpl;
}

void SocketAddressBlockListWrap::Initialize(Local<Object> target,
                                            Local<Value> unused,
                                            Local<Context> context,
                                            void* priv) {
  Environment* env = Environment::GetCurrent(context);

  SetConstructorFunction(context,
                         target,
                         "BlockList",
                         GetConstructorTemplate(env),
                         SetConstructorFunctionFlag::NONE);

  SocketAddressBase::Initialize(env, target);

  NODE_DEFINE_CONSTANT(target, AF_INET);
  NODE_DEFINE_CONSTANT(target, AF_INET6);
}

BaseObjectPtr<BaseObject> SocketAddressBlockListWrap::TransferData::Deserialize(
    Environment* env,
    Local<Context> context,
    std::unique_ptr<worker::TransferData> self) {
  return New(env, std::move(blocklist_));
}

void SocketAddressBlockListWrap::TransferData::MemoryInfo(
    MemoryTracker* tracker) const {
  blocklist_->MemoryInfo(tracker);
}

bool SocketAddressBase::HasInstance(Environment* env, Local<Value> value) {
  return GetConstructorTemplate(env)->HasInstance(value);
}

Local<FunctionTemplate> SocketAddressBase::GetConstructorTemplate(
    Environment* env) {
  Local<FunctionTemplate> tmpl = env->socketaddress_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, New);
    tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "SocketAddress"));
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        SocketAddressBase::kInternalFieldCount);
    SetProtoMethod(isolate, tmpl, "detail", Detail);
    SetProtoMethod(isolate, tmpl, "legacyDetail", LegacyDetail);
    SetProtoMethodNoSideEffect(isolate, tmpl, "flowlabel", GetFlowLabel);
    env->set_socketaddress_constructor_template(tmpl);
  }
  return tmpl;
}

void SocketAddressBase::Initialize(Environment* env, Local<Object> target) {
  SetConstructorFunction(env->context(),
                         target,
                         "SocketAddress",
                         GetConstructorTemplate(env),
                         SetConstructorFunctionFlag::NONE);
}

BaseObjectPtr<SocketAddressBase> SocketAddressBase::Create(
    Environment* env, std::shared_ptr<SocketAddress> address) {
  Local<Object> obj;
  if (!GetConstructorTemplate(env)
           ->InstanceTemplate()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return nullptr;
  }

  return MakeBaseObject<SocketAddressBase>(env, obj, std::move(address));
}

void SocketAddressBase::New(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK(args.IsConstructCall());
  CHECK(args[0]->IsString());  // address
  CHECK(args[1]->IsInt32());   // port
  CHECK(args[2]->IsInt32());   // family
  CHECK(args[3]->IsUint32());  // flow label

  Utf8Value address(env->isolate(), args[0]);
  int32_t port = args[1].As<Int32>()->Value();
  int32_t family = args[2].As<Int32>()->Value();
  uint32_t flow_label = args[3].As<Uint32>()->Value();

  std::shared_ptr<SocketAddress> addr = std::make_shared<SocketAddress>();

  if (!SocketAddress::New(family, *address, port, addr.get()))
    return THROW_ERR_INVALID_ADDRESS(env);

  addr->set_flow_label(flow_label);

  new SocketAddressBase(env, args.This(), std::move(addr));
}

void SocketAddressBase::Detail(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK(args[0]->IsObject());
  Local<Object> detail = args[0].As<Object>();

  SocketAddressBase* base;
  ASSIGN_OR_RETURN_UNWRAP(&base, args.This());

  Local<Value> address;
  if (!ToV8Value(env->context(), base->address_->address()).ToLocal(&address))
    return;

  if (detail->Set(env->context(), env->address_string(), address).IsJust() &&
      detail
          ->Set(env->context(),
                env->port_string(),
                Int32::New(env->isolate(), base->address_->port()))
          .IsJust() &&
      detail
          ->Set(env->context(),
                env->family_string(),
                Int32::New(env->isolate(), base->address_->family()))
          .IsJust() &&
      detail
          ->Set(env->context(),
                env->flowlabel_string(),
                Uint32::New(env->isolate(), base->address_->flow_label()))
          .IsJust()) {
    args.GetReturnValue().Set(detail);
  }
}

void SocketAddressBase::GetFlowLabel(const FunctionCallbackInfo<Value>& args) {
  SocketAddressBase* base;
  ASSIGN_OR_RETURN_UNWRAP(&base, args.This());
  args.GetReturnValue().Set(base->address_->flow_label());
}

void SocketAddressBase::LegacyDetail(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  SocketAddressBase* base;
  ASSIGN_OR_RETURN_UNWRAP(&base, args.This());
  Local<Object> address;
  if (!base->address_->ToJS(env).ToLocal(&address)) return;
  args.GetReturnValue().Set(address);
}

SocketAddressBase::SocketAddressBase(Environment* env,
                                     Local<Object> wrap,
                                     std::shared_ptr<SocketAddress> address)
    : BaseObject(env, wrap), address_(std::move(address)) {
  MakeWeak();
}

void SocketAddressBase::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("address", address_);
}

std::unique_ptr<worker::TransferData> SocketAddressBase::CloneForMessaging()
    const {
  return std::make_unique<TransferData>(this);
}

void SocketAddressBase::TransferData::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("address", address_);
}

BaseObjectPtr<BaseObject> SocketAddressBase::TransferData::Deserialize(
    Environment* env,
    v8::Local<v8::Context> context,
    std::unique_ptr<worker::TransferData> self) {
  return SocketAddressBase::Create(env, std::move(address_));
}

}  // namespace node

NODE_BINDING_CONTEXT_AWARE_INTERNAL(
    block_list, node::SocketAddressBlockListWrap::Initialize)