udp_socket.pony

use net = ".."

actor UDPSocket is (net.UDPSocketActor & net.UDPLifecycleEventReceiver)
  """
  A UDP socket actor driven by a
  [`UDPSocketNotify`](/net/notifier-UDPSocketNotify/).

  Wraps the net package's `UDPSocket` class, `UDPSocketActor` trait, and
  `UDPLifecycleEventReceiver` trait into a single actor.

  `send_to` is synchronous and returns a `SendToResult` — use it from within
  callbacks where the result matters. `write_to` is a fire-and-forget
  behavior for sending from other actors; the result is discarded.
  """
  var _udp: net.UDPSocket = net.UDPSocket.none()
  var _notify: UDPSocketNotify ref

  new create(auth: net.UDPAuth,
    notify: UDPSocketNotify iso,
    host: String,
    port: String,
    read_buffer_size: net.ReadBufferSize = net.DefaultReadBufferSize(),
    ip_version: net.IPVersion = net.DualStack,
    max_datagrams_per_turn: USize = 256)
  =>
    """
    Bind a UDP socket to `host`:`port`. Port `"0"` lets the OS assign an
    ephemeral port.
    """
    _notify = consume notify
    _udp =
      net.UDPSocket(
        auth, host, port, this, this,
        read_buffer_size, ip_version, max_datagrams_per_turn)

  // --- UDPSocketActor --------------------------------------------------------
  fun ref _socket(): net.UDPSocket =>
    _udp

  // --- Behaviors -------------------------------------------------------------
  be write_to(data: ByteSeq, to: net.NetAddress val) =>
    """
    Send a datagram. Fire-and-forget: the send is silently dropped if the
    socket is not open. Use `send_to` from within a callback when the result
    matters.
    """
    _udp.send_to(data, to)

  // --- Synchronous methods ---------------------------------------------------
  fun ref send_to(data: ByteSeq, to: net.NetAddress box): net.SendToResult =>
    """
    Send one datagram to `to`. Returns `SendToOk` when the datagram was
    handed to the OS. UDP sends are synchronous and all-or-nothing.

    Callable from any callback (where `sock` is `ref`). From outside the
    actor, use the `write_to` behavior instead.
    """
    _udp.send_to(data, to)

  fun ref close() =>
    """
    Close the socket. No graceful shutdown: the fd is closed immediately.
    """
    _udp.close()

  fun ref local_address(): net.NetAddress =>
    """
    Return the local IP address the socket is bound to.
    """
    _udp.local_address()

  fun is_open(): Bool =>
    """
    True when the socket is bound and has not been closed.
    """
    _udp.is_open()

  fun is_closed(): Bool =>
    """
    True when the socket has been closed.
    """
    _udp.is_closed()

  fun get_so_rcvbuf(): (U32, U32) =>
    """
    Get the OS receive buffer size. Returns (errno, value).
    """
    _udp.get_so_rcvbuf()

  fun set_so_rcvbuf(bufsize: U32): U32 =>
    """
    Set the OS receive buffer size. Returns 0 on success, or errno.
    """
    _udp.set_so_rcvbuf(bufsize)

  fun get_so_sndbuf(): (U32, U32) =>
    """
    Get the OS send buffer size. Returns (errno, value).
    """
    _udp.get_so_sndbuf()

  fun set_so_sndbuf(bufsize: U32): U32 =>
    """
    Set the OS send buffer size. Returns 0 on success, or errno.
    """
    _udp.set_so_sndbuf(bufsize)

  fun getsockopt_u32(level: I32, option_name: I32): (U32, U32) =>
    """
    Get a socket option as a U32. Returns (errno, value).
    """
    _udp.getsockopt_u32(level, option_name)

  fun setsockopt_u32(level: I32, option_name: I32, option: U32): U32 =>
    """
    Set a socket option as a U32. Returns 0 on success, or errno.
    """
    _udp.setsockopt_u32(level, option_name, option)

  // --- UDPLifecycleEventReceiver ---------------------------------------------
  fun ref _on_bound() =>
    _notify.on_bound(this)

  fun ref _on_bind_failure() =>
    _notify.on_bind_failure(this)

  fun ref _on_received(data: Array[U8] iso, from: net.NetAddress val)
    : net.ReadAction
  =>
    _notify.on_received(this, consume data, from)

  fun ref _on_closed() =>
    _notify.on_closed(this)