lib/gsmlg/whois/server.ex

defmodule GSMLG.Whois.Server do
  @moduledoc """
  Define whois server address.
  Find whois server of Domain, IP or AS.
  """
  defstruct [:host]

  @type t :: %__MODULE__{host: String.t()}

  import GSMLG.Whois.Helper

  define_from_file(:asn16)
  define_from_file(:asn32)
  define_from_file(:ipv4)
  define_from_file(:ipv6)
  define_from_file(:tld)

  @spec for_domain(String.t()) :: {:ok, t} | :error
  def for_domain(domain) do
    [_, name] = String.split(domain, ".", parts: 2)
    Map.fetch(tld(), name)
  end

  @spec for_ipv4(String.t()) :: {:ok, t} | :error
  def for_ipv4(addr) do
    ip = InetCidr.parse_address!(addr)

    n =
      ipv4()
      |> Map.keys()
      |> Enum.find(fn net ->
        cidr = InetCidr.parse(net, true)
        InetCidr.contains?(cidr, ip)
      end)

    case n do
      nil -> :error
      n -> {:ok, Map.get(ipv4(), n)}
    end
  end

  @spec for_ipv6(String.t()) :: {:ok, t} | :error
  def for_ipv6(addr) do
    ip = InetCidr.parse_address!(addr)

    n =
      ipv6()
      |> Map.keys()
      |> Enum.find(fn net ->
        cidr = InetCidr.parse(net, true)
        InetCidr.contains?(cidr, ip)
      end)

    case n do
      nil -> :error
      n -> {:ok, Map.get(ipv6(), n)}
    end
  end

  @spec for_ip(String.t()) :: {:ok, t} | :error
  def for_ip(addr) do
    ip = InetCidr.parse_address!(addr)

    cond do
      InetCidr.v4?(ip) -> for_ipv4(addr)
      InetCidr.v6?(ip) -> for_ipv6(addr)
      true -> :error
    end
  end

  @spec for_asn16(String.t() | Integer.t()) :: {:ok, t} | :error
  def for_asn16(as) when is_binary(as), do: String.to_integer(as) |> for_asn16()

  def for_asn16(as) do
    n =
      asn16()
      |> Map.keys()
      |> Enum.find(fn asn ->
        case String.split(asn) do
          [s, e] ->
            s = String.to_integer(s)
            e = String.to_integer(e)
            as >= s and as <= e

          [n] ->
            String.to_integer(n) == as
        end
      end)

    case n do
      nil -> :error
      n -> {:ok, Map.get(asn16(), n)}
    end
  end

  def for_asn32(as) when is_binary(as), do: String.to_integer(as) |> for_asn32()

  def for_asn32(as) when is_integer(as) do
    n =
      asn32()
      |> Map.keys()
      |> Enum.find(fn asn ->
        case String.split(asn) do
          [s, e] ->
            s = String.to_integer(s)
            e = String.to_integer(e)
            as >= s and as <= e

          [n] ->
            String.to_integer(n) == as
        end
      end)

    case n do
      nil -> :error
      n -> {:ok, Map.get(asn32(), n)}
    end
  end

  @spec for_asn32(String.t() | Integer.t()) :: {:ok, t} | :error
  def for_asn(as) when is_binary(as), do: String.to_integer(as) |> for_asn()

  def for_asn(as) when is_integer(as) do
    cond do
      as > 0 and as < 2 ** 16 -> for_asn16(as)
      as >= 2 ** 16 and as < 2 ** 32 -> for_asn32(as)
      true -> :error
    end
  end
end