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]
  """
  Represents an IPv4 or IPv6 address. The family field indicates the address
  type. The addr field is either the IPv4 address or the IPv6 flow info. The
  addr1-4 fields are the IPv6 address, or invalid for an IPv4 address. The
  scope field is the IPv6 scope zone identifier, or invalid for an IPv4
  address.

  This class is modelled after the C data structure for holding socket
  addresses for both IPv4 and IPv6 `sockaddr_storage`.

  Use the `name` method to obtain address/hostname and port/service 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.
    Will be `0` for IPv6 addresses. Check with `ipv4()` and `ipv6()`.
    """

  let _addr1: U32 = 0
    """
    Bits 0-32 of the IPv6 address in network byte order.

    `0` if this is an IPv4 address. Check with `ipv4()` and `ipv6()`.
    """

  let _addr2: U32 = 0
    """
    Bits 33-64 of the IPv6 address in network byte order.

    `0` if this is an IPv4 address. Check with `ipv4()` and `ipv6()`.
    """
  let _addr3: U32 = 0
    """
    Bits 65-96 of the IPv6 address in network byte order.

    `0` if this is an IPv4 address. Check with `ipv4()` and `ipv6()`.
    """
  let _addr4: U32 = 0
    """
    Bits 97-128 of the IPv6 address in network byte order.

    `0` if this is an IPv4 address. Check with `ipv4()` and `ipv6()`.
    """

  let _scope: U32 = 0
    """
    IPv6 scope zone identifier (`sin6_scope_id`) in host byte order.

    For scoped addresses -- link-local and scoped multicast -- this is the
    interface index that scopes the address; `0` for global addresses and
    invalid for an IPv4 address. Unlike the address and port fields, this is
    kept in host byte order, not network order.
    """

  fun ip4(): Bool =>
    """
    Returns true for an IPv4 address.
    """
    @pony_os_ipv4(this)

  fun ip6(): Bool =>
    """
    Returns true for an IPv6 address.
    """
    @pony_os_ipv6(this)

  fun name(
    reversedns: (DNSAuth | None) = None,
    servicename: Bool = false)
    : (String, String) ?
  =>
    """
    Returns the host and service name.

    If `reversedns` is `DNSAuth`,
    a DNS lookup will be executed and the hostname
    for this address is returned as first element of the result tuple.
    If no hostname could be found, an error is raised.
    If `reversedns` is `None` the plain IP address is given
    and no DNS lookup is executed.

    If `servicename` is `false` the numeric port is returned
    as second element of the result tuple.
    If it is `true` the port is translated into its
    corresponding servicename (e.g. port 80 is returned as `"http"`).

    Internally this method uses the POSIX C function `getnameinfo`.
    """
    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 =>
      (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 =>
    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 =>
    """
    For platforms (OSX/FreeBSD) with `length` field as part of
    its `struct sockaddr` definition, returns the `length`.
    Else (Linux/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 =>
      """
        Returns the `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 =>
      """
        Returns port number in host byte order.
      """
      @ntohs(_port)

    fun scope() : U32 =>
      """
        Returns the IPv6 scope zone identifier (`sin6_scope_id`).

        For scoped addresses -- link-local and scoped multicast -- this is
        the interface index that scopes the address (e.g. the zone of
        `fe80::1%eth0`). It is `0` for global addresses and invalid for an
        IPv4 address.
      """

      // No `@ntohl` here, unlike `port`/`ipv4_addr`/`ipv6_addr`: the kernel
      // and `getaddrinfo` keep `sin6_scope_id` in host byte order, so byte
      // swapping would mangle the value on little-endian platforms.
      _scope

    fun ipv4_addr() : U32 =>
      """
        Returns IPV4 address (`_addr` field in the class) if `ip4()` is `True`.
        If `ip4()` is `False` then the contents are invalid.
      """
      @ntohl(_addr)

    fun ipv6_addr() : (U32, U32, U32, U32) =>
      """
        Returns IPV6 address as the 4-tuple (say `a`).
        `a._1 = _addr1` // Bits 0-32 of the IPv6 address in host byte order.
        `a._2 = _addr2  // Bits 33-64 of the IPv6 address in host byte order.
        `a._3 = _addr3  // Bits 65-96 of the IPv6 address in host byte order.
        `a._4 = _addr4  // Bits 97-128 of the IPv6 address in host byte order.

        The contents of the 4-tuple returned are valid only if `ip6()` is `True`.
      """
      (@ntohl(_addr1),
       @ntohl(_addr2),
       @ntohl(_addr3),
       @ntohl(_addr4)
      )