runtime_backend.pony

use @pony_os_accept[I32](event: AsioEventID)
use @pony_os_connect_tcp[U32](the_actor: AsioEventNotify,
  host: Pointer[U8] tag,
  port: Pointer[U8] tag,
  from: Pointer[U8] tag,
  asio_flags: U32,
  out_events: Pointer[AsioEventID] tag,
  max_events: I32)
use @pony_os_connect_tcp4[U32](the_actor: AsioEventNotify,
  host: Pointer[U8] tag,
  port: Pointer[U8] tag,
  from: Pointer[U8] tag,
  asio_flags: U32,
  out_events: Pointer[AsioEventID] tag,
  max_events: I32)
use @pony_os_connect_tcp6[U32](the_actor: AsioEventNotify,
  host: Pointer[U8] tag,
  port: Pointer[U8] tag,
  from: Pointer[U8] tag,
  asio_flags: U32,
  out_events: Pointer[AsioEventID] tag,
  max_events: I32)
use @pony_os_keepalive[None](fd: U32, secs: U32)
use @pony_os_listen_tcp[AsioEventID](the_actor: AsioEventNotify,
  host: Pointer[U8] tag,
  port: Pointer[U8] tag)
use @pony_os_listen_tcp4[AsioEventID](the_actor: AsioEventNotify,
  host: Pointer[U8] tag,
  port: Pointer[U8] tag)
use @pony_os_listen_tcp6[AsioEventID](the_actor: AsioEventNotify,
  host: Pointer[U8] tag,
  port: Pointer[U8] tag)
use @pony_os_peername[Bool](fd: U32, ip: NetAddress tag)
use @pony_os_recv[U8](event: AsioEventID,
  buffer: Pointer[U8] tag,
  size: USize,
  count_out: Pointer[USize])
use @pony_os_sendv[U8](ev: AsioEventID,
  iov: Pointer[(Pointer[U8] tag, USize)] tag,
  iovcnt: I32,
  count_out: Pointer[USize])
use @pony_os_socket_close[None](fd: U32)
use @pony_os_socket_shutdown[None](fd: U32)
use @pony_os_sockname[Bool](fd: U32, ip: NetAddress tag)
use @pony_os_writev_max[I32]()

class RuntimeBackend is TCPBackend
  """
  Wrappers for the runtime's `pony_os_*` TCP functions -- connect, listen,
  accept, receive, sendv, keepalive, and socket teardown.
  """
  new create() => None

  fun listen(the_actor: AsioEventNotify,
    host: String,
    port: String,
    ip_version: IPVersion = DualStack)
    : AsioEventID
  =>
    match \exhaustive\ ip_version
    | IP4 =>
      @pony_os_listen_tcp4(the_actor, host.cstring(), port.cstring())
    | IP6 =>
      @pony_os_listen_tcp6(the_actor, host.cstring(), port.cstring())
    | DualStack =>
      @pony_os_listen_tcp(the_actor, host.cstring(), port.cstring())
    end

  fun accept(event: AsioEventID): I32 =>
    @pony_os_accept(event)

  fun close(fd: U32) =>
    @pony_os_socket_close(fd)

  fun connect(the_actor: AsioEventNotify,
    host: String,
    port: String,
    from: String,
    asio_flags: U32,
    ip_version: IPVersion = DualStack)
    : Array[AsioEventID]
  =>
    """
    Start non-blocking TCP connection attempts to `host`:`port` via
    the runtime's Happy Eyeballs implementation. Returns an event per
    resolved address; an empty array means all attempts failed
    immediately.
    """
    let max: USize = 32
    let events = Array[AsioEventID].init(AsioEvent.none(), max)
    let count =
      match \exhaustive\ ip_version
      | IP4 =>
        @pony_os_connect_tcp4(
          the_actor,
          host.cstring(),
          port.cstring(),
          from.cstring(),
          asio_flags,
          events.cpointer(),
          max.i32())
      | IP6 =>
        @pony_os_connect_tcp6(
          the_actor,
          host.cstring(),
          port.cstring(),
          from.cstring(),
          asio_flags,
          events.cpointer(),
          max.i32())
      | DualStack =>
        @pony_os_connect_tcp(
          the_actor,
          host.cstring(),
          port.cstring(),
          from.cstring(),
          asio_flags,
          events.cpointer(),
          max.i32())
      end
    events.truncate(count.usize().min(max))
    events

  fun keepalive(fd: U32, secs: U32) =>
    @pony_os_keepalive(fd, secs)

  fun peername(fd: U32, ip: NetAddress tag): Bool =>
    @pony_os_peername(fd, ip)

  fun receive(event: AsioEventID,
    buffer: Pointer[U8] tag,
    size: USize)
    : (SocketResult, USize)
  =>
    """
    Receive up to `size` bytes into `buffer`. Returns the tri-state socket
    result plus the number of bytes received on `SocketResultOk`. The call is
    synchronous and non-blocking on every platform: `SocketResultRetry` means
    no data was available (`EWOULDBLOCK`/`WSAEWOULDBLOCK`), `SocketResultError`
    means an unrecoverable error or peer close.
    """
    var count: USize = 0
    let result =
      SocketResultDecoder(@pony_os_recv(event, buffer, size, addressof count))
    (result, count)

  fun shutdown(fd: U32) =>
    @pony_os_socket_shutdown(fd)

  fun sockname(fd: U32, ip: NetAddress tag): Bool =>
    @pony_os_sockname(fd, ip)

  fun sendv(event: AsioEventID,
    data: Array[ByteSeq] box,
    from: USize,
    count: USize,
    first_buffer_byte_offset: USize = 0)
    : (SocketResult, USize) ?
  =>
    """
    Send `count` buffers from `data` starting at index `from` via
    `pony_os_sendv`.
    Builds the IOV array of `(pointer, size)` entries internally; the runtime
    turns it into `iovec` (POSIX) or `WSABUF` (Windows) as needed.

    `first_buffer_byte_offset` skips bytes in `data(from)` for partial
    write resume.

    Returns the tri-state socket result plus the number of bytes sent on
    `SocketResultOk`. The call is synchronous and non-blocking on every
    platform.
    """
    var bytes_sent: USize = 0
    let iov = Array[(Pointer[U8] tag, USize)](count)
    var i = from
    while i < (from + count) do
      let entry = data(i)?
      if (i == from) and (first_buffer_byte_offset > 0) then
        iov.push((entry.cpointer(first_buffer_byte_offset),
          entry.size() - first_buffer_byte_offset))
      else
        iov.push((entry.cpointer(), entry.size()))
      end
      i = i + 1
    end
    let result =
      SocketResultDecoder(
        @pony_os_sendv(
          event, iov.cpointer(), count.i32(), addressof bytes_sent))
    (result, bytes_sent)

  fun writev_max(): I32 =>
    """
    Maximum number of `(pointer, size)` entries a single `sendv` call may
    carry. `IOV_MAX` on POSIX, 1 on Windows.
    """
    @pony_os_writev_max()