// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/dns/public/resolv_reader.h"

#include <netinet/in.h>
#include <resolv.h>
#include <sys/types.h>

#include <memory>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "build/build_config.h"
#include "net/base/ip_endpoint.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace net {

std::unique_ptr<ScopedResState> ResolvReader::GetResState() {
  auto res = std::make_unique<ScopedResState>();
  if (!res->IsValid())
    return nullptr;
  return res;
}

absl::optional<std::vector<IPEndPoint>> GetNameservers(
    const struct __res_state& res) {
  std::vector<IPEndPoint> nameservers;

  if (!(res.options & RES_INIT))
    return absl::nullopt;

#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_FREEBSD)
  union res_sockaddr_union addresses[MAXNS];
  int nscount = res_getservers(const_cast<res_state>(&res), addresses, MAXNS);
  DCHECK_GE(nscount, 0);
  DCHECK_LE(nscount, MAXNS);
  for (int i = 0; i < nscount; ++i) {
    IPEndPoint ipe;
    if (!ipe.FromSockAddr(
            reinterpret_cast<const struct sockaddr*>(&addresses[i]),
            sizeof addresses[i])) {
      return absl::nullopt;
    }
    nameservers.push_back(ipe);
  }
#elif BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
  static_assert(std::extent<decltype(res.nsaddr_list)>() >= MAXNS &&
                    std::extent<decltype(res._u._ext.nsaddrs)>() >= MAXNS,
                "incompatible libresolv res_state");
  DCHECK_LE(res.nscount, MAXNS);
  // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
  // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
  // but we have to combine the two arrays ourselves.
  for (int i = 0; i < res.nscount; ++i) {
    IPEndPoint ipe;
    const struct sockaddr* addr = nullptr;
    size_t addr_len = 0;
    if (res.nsaddr_list[i].sin_family) {  // The indicator used by res_nsend.
      addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
      addr_len = sizeof res.nsaddr_list[i];
    } else if (res._u._ext.nsaddrs[i]) {
      addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
      addr_len = sizeof *res._u._ext.nsaddrs[i];
    } else {
      return absl::nullopt;
    }
    if (!ipe.FromSockAddr(addr, addr_len))
      return absl::nullopt;
    nameservers.push_back(ipe);
  }
#else  // !(BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_APPLE)
       // || BUILDFLAG(IS_FREEBSD))
  DCHECK_LE(res.nscount, MAXNS);
  for (int i = 0; i < res.nscount; ++i) {
    IPEndPoint ipe;
    if (!ipe.FromSockAddr(
            reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]),
            sizeof res.nsaddr_list[i])) {
      return absl::nullopt;
    }
    nameservers.push_back(ipe);
  }
#endif

  return nameservers;
}

}  // namespace net
