net_address.pony

use @ntohl[U32](netlong: U32)
use @ntohs[U16](netshort: U16)
use @pony_os_ipv4[Bool](addr: NetAddress tag)
use @pony_os_ipv6[Bool](addr: NetAddress tag)
use @ponyint_address_length[U32](addr: NetAddress tag)
use @pony_os_nameinfo[Bool](addr: NetAddress tag,
  host: Pointer[Pointer[U8] iso] tag, serv: Pointer[Pointer[U8] iso] tag,
  reverse_dns: Bool, service_name: Bool)

class val NetAddress is Equatable[NetAddress]
  """
  An IPv4 or IPv6 socket address, modelled after `sockaddr_storage`. The
  `_family` field indicates which kind; the `_addr` field is either the IPv4
  address or the IPv6 flow info, and `_addr1`–`_addr4` are the IPv6 address
  (invalid for IPv4). All address and port fields are in network byte order;
  `_scope` is in host byte order.

  Call `name()` to get the address and port as strings.
  """
  let _family: U16 = 0
  let _port: U16 = 0
    """
    Port number in network byte order.
    """
  let _addr: U32 = 0
    """
    IPv4 address in network byte order, or `0` for IPv6.
    """
  let _addr1: U32 = 0
    """
    Bits 0–32 of the IPv6 address in network byte order. `0` for IPv4.
    """
  let _addr2: U32 = 0
    """
    Bits 33–64 of the IPv6 address in network byte order. `0` for IPv4.
    """
  let _addr3: U32 = 0
    """
    Bits 65–96 of the IPv6 address in network byte order. `0` for IPv4.
    """
  let _addr4: U32 = 0
    """
    Bits 97–128 of the IPv6 address in network byte order. `0` for IPv4.
    """
  let _scope: U32 = 0
    """
    IPv6 scope zone identifier (`sin6_scope_id`) in host byte order.
    For link-local and scoped multicast addresses, the interface index that
    scopes the address. `0` for global addresses; invalid for IPv4.
    """

  fun ip4(): Bool =>
    """
    `true` when this is an IPv4 address.
    """
    @pony_os_ipv4(this)

  fun ip6(): Bool =>
    """
    `true` when this is an IPv6 address.
    """
    @pony_os_ipv6(this)

  fun name(
    reversedns: (DNSAuth | None) = None,
    servicename: Bool = false)
    : (String, String) ?
  =>
    """
    The host and service as strings. When `reversedns` is a `DNSAuth`, a DNS
    lookup returns the hostname; when it is `None` the plain IP address is
    returned. When `servicename` is `true` the port is translated to its
    service name (e.g. port 80 becomes `"http"`). Raises an error when reverse
    DNS is requested and no hostname is found. Uses `getnameinfo` internally.
    """
    var host: Pointer[U8] iso = recover Pointer[U8] end
    var serv: Pointer[U8] iso = recover Pointer[U8] end
    let reverse = reversedns isnt None

    if not
      @pony_os_nameinfo(
        this,
        addressof host,
        addressof serv,
        reverse,
        servicename)
    then
      error
    end

    (recover String.from_cstring(consume host) end,
      recover String.from_cstring(consume serv) end)

  fun eq(that: NetAddress box): Bool =>
    """
    Two addresses are equal when they have the same family, port, host, and
    scope.
    """
    (this._family == that._family) and
      (this._port == that._port) and
      (host_eq(that)) and
      (this._scope == that._scope)

  fun host_eq(that: NetAddress box): Bool =>
    """
    True when the host portion matches, ignoring port and scope.
    """
    if ip4() then
      this._addr == that._addr
    else
      (this._addr1 == that._addr1) and
        (this._addr2 == that._addr2) and
        (this._addr3 == that._addr3) and
        (this._addr4 == that._addr4)
    end

  fun length() : U8 =>
    """
    On platforms whose `sockaddr` includes a length field (macOS, FreeBSD),
    returns that length. On Linux and Windows, returns the size of
    `sockaddr_in` or `sockaddr_in6`.
    """

    ifdef linux or windows then
      (@ponyint_address_length(this)).u8()
    else
      ifdef bigendian then
        ((_family >> 8) and 0xff).u8()
      else
        (_family and 0xff).u8()
      end
    end

  fun family() : U8 =>
    """
    The address family.
    """

    ifdef linux or windows then
      ifdef bigendian then
        ((_family >> 8) and 0xff).u8()
      else
        (_family and 0xff).u8()
      end
    else
      ifdef bigendian then
        (_family and 0xff).u8()
      else
        ((_family >> 8) and 0xff).u8()
      end
    end

  fun port() : U16 =>
    """
    Port number in host byte order.
    """
    @ntohs(_port)

  fun scope() : U32 =>
    """
    IPv6 scope zone identifier (`sin6_scope_id`). For link-local and scoped
    multicast addresses, the interface index (e.g. `fe80::1%eth0`). `0` for
    global addresses; invalid for IPv4.
    """
    // The kernel and getaddrinfo keep sin6_scope_id in host byte order,
    // so no ntohl here.
    _scope

  fun ipv4_addr() : U32 =>
    """
    IPv4 address in host byte order. Valid only when `ip4()` is `true`.
    """
    @ntohl(_addr)

  fun ipv6_addr() : (U32, U32, U32, U32) =>
    """
    IPv6 address as four 32-bit words in host byte order. Valid only when
    `ip6()` is `true`.
    """
    ( @ntohl(_addr1),
      @ntohl(_addr2),
      @ntohl(_addr3),
      @ntohl(_addr4) )