Protocol Layering
=================

Layering plays an important role in the design of the ``caf.net`` module. When
implementing a new protocol for ``caf.net``, this protocol should integrate
naturally with any number of other protocols. To enable this key property,
``caf.net`` establishes a set of conventions and abstract interfaces.

Concepts
--------

A *protocol layer* is a class that implements a single processing step in a
communication pipeline. Multiple layers are organized in a *protocol stack*.
Each layer may only communicate with its direct predecessor or successor in the
stack.

At the bottom of the protocol stack is usually a *transport layer*. For example,
a ``stream_transport`` manages a stream socket and provides access to input and
output buffers to the upper layer.

At the top of the protocol is an *application* that utilizes the lower layers
for communication via the network. Applications should only rely on the
*abstract interface type* when communicating with their lower layer. For
example, an application that processes a data stream should not implement
against a TCP socket interface directly. By programming against the abstract
interface types of ``caf.net``, users can instantiate an application with any
compatible protocol stack of their choosing. For example, a user may add extra
security by using application-level data encryption or combine a custom datagram
transport with protocol layers that establish ordering and reliability to
emulate a stream.

Abstract Interface Types
------------------------

By default, ``caf.net`` distinguishes between these abstract interface types:

* *stream*: A stream interface represents a sequence of Bytes, transmitted
  reliable and in order.
* *datagram*: A datagram interface provides access to some basic transfer units
  that may arrive out of order or not at all.
* *message*: A message interface provides access to high-level, structured data.
  Messages consist of a header and a payload. A single message may span multiple
  datagrams, for example.

Note that each interface type also depends on the *direction*, i.e., whether
talking to the upper or lower level. Incoming data always travels the protocol
stack *up*. Outgoing data always travels the protocol stack *down*.

..code-block:: C++

  interface base [role: upper layer] {
    /// Called whenever the underlying transport is ready to send, allowing the
    /// upper layers to produce additional output data or use the event to read
    /// from event queues, etc.
    /// @returns `true` if the lower layers may proceed, `false` otherwise
    ///          (aborts execution).
    template <class LowerLayerPtr>
    bool prepare_send(LowerLayerPtr down);

    /// Called whenever the underlying transport finished writing all buffered
    /// data for output to query whether an upper layer still has pending events
    /// or may produce output data on the next call to `prepare_send`.
    /// @returns `true` if the underlying socket may get removed from the I/O
    ///          event loop, `false` otherwise.
    template <class LowerLayerPtr>
    bool done_sending(LowerLayerPtr down);

    /// When provided, the underlying transport calls this member function
    /// before leaving `handle_read_event`. The primary use case for this
    /// callback is flushing buffers.
    template <class LowerLayerPtr>
    [[optional]] void after_reading(LowerLayerPtr down);
  }

  interface base [role: lower layer] {
    /// Returns whether the layer has output slots available.
    bool can_send_more() const noexcept;

    /// Returns the managed socket.
    auto handle() const noexcept;
  }

  interface stream_oriented [role: upper layer] {
    /// Called by the lower layer for cleaning up any state in case of an error.
    template <class LowerLayerPtr>
    void abort(LowerLayerPtr down, const error& reason);

    /// Consumes bytes from the lower layer.
    /// @param down Reference to the lower layer that received the data.
    /// @param buffer Available bytes to read.
    /// @param delta Bytes that arrived since last calling this function.
    /// @returns The number of consumed bytes. May be zero if waiting for more
    ///          input or negative to signal an error.
    /// @note When returning a negative value, clients should also call
    ///       `down.abort_reason(...)` with an appropriate error code.
    template <class LowerLayerPtr>
    ptrdiff_t consume(LowerLayerPtr down, byte_span buffer, byte_span delta);
  }

  interface stream_oriented [role: lower layer] {
    /// Configures threshold for the next receive operations. Policies remain
    /// active until calling this function again.
    /// @warning Calling this function in `consume` invalidates both byte spans.
    void configure_read(read_policy policy);

    /// Prepares the layer for outgoing traffic, e.g., by allocating an output
    /// buffer as necessary.
    void begin_output();

    /// Returns a reference to the output buffer. Users may only call this
    /// function and write to the buffer between calling `begin_output()` and
    /// `end_output()`.
    byte_buffer& output_buffer();

    /// Prepares written data for transfer, e.g., by flushing buffers or
    /// registering sockets for write events.
    void end_output();

    /// Propagates an abort reason to the lower layers. After processing the
    /// current read or write event, the lowest layer will call `abort` on its
    /// upper layer.
    void abort_reason(error reason);

    /// Returns the last recent abort reason or `none` if no error occurred.
    const error& abort_reason();
  }

  interface datagram_oriented [role: upper layer] {
    /// Consumes a datagram from the lower layer.
    /// @param down Reference to the lower layer that received the datagram.
    /// @param buffer Payload of the received datagram.
    /// @returns The number of consumed bytes or a negative value to signal an
    ///          error.
    /// @note Discarded data is lost permanently.
    /// @note When returning a negative value for the number of consumed bytes,
    ///       clients must also call `down.set_read_error(...)` with an
    ///       appropriate error code.
    template <class LowerLayerPtr>
    ptrdiff_t consume(LowerLayerPtr down, byte_span buffer);
  }

  interface datagram_oriented [role: lower layer] {
    /// Prepares the layer for an outgoing datagram, e.g., by allocating an
    /// output buffer as necessary.
    void begin_datagram();

    /// Returns a reference to the buffer for assembling the current datagram.
    /// Users may only call this function and write to the buffer between
    /// calling `begin_datagram()` and `end_datagram()`.
    /// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
    ///       headers.
    byte_buffer& datagram_buffer();

    /// Seals and prepares a datagram for transfer.
    void end_datagram();
  }

  interface message_oriented [role: upper layer] {
    /// Consumes a message from the lower layer.
    /// @param down Reference to the lower layer that received the message.
    /// @param buffer Payload of the received message.
    /// @returns The number of consumed bytes or a negative value to signal an
    ///          error.
    /// @note Discarded data is lost permanently.
    /// @note When returning a negative value for the number of consumed bytes,
    ///       clients must also call `down.set_read_error(...)` with an
    ///       appropriate error code.
    template <class LowerLayerPtr>
    ptrdiff_t consume(LowerLayerPtr down, byte_span buffer);
  }

  interface message_oriented [role: lower layer] {
    /// Prepares the layer for an outgoing message, e.g., by allocating an
    /// output buffer as necessary.
    void begin_message();

    /// Returns a reference to the buffer for assembling the current message.
    /// Users may only call this function and write to the buffer between
    /// calling `begin_message()` and `end_message()`.
    /// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
    ///       headers.
    byte_buffer& message_buffer();

    /// Seals and prepares a message for transfer.
    /// @note When returning `false`, clients must also call
    ///       `down.set_read_error(...)` with an appropriate error code.
    template <class LowerLayerPtr>
    bool end_message();
  }

  interface mixed_message_oriented [role: upper layer] {
    /// Consumes a binary message from the lower layer.
    /// @param down Reference to the lower layer that received the message.
    /// @param buffer Payload of the received message.
    /// @returns The number of consumed bytes or a negative value to signal an
    ///          error.
    /// @note Discarded data is lost permanently.
    /// @note When returning a negative value for the number of consumed bytes,
    ///       clients must also call `down.set_read_error(...)` with an
    ///       appropriate error code.
    template <class LowerLayerPtr>
    ptrdiff_t consume_binary(LowerLayerPtr down, byte_span buffer);

    /// Consumes a text message from the lower layer.
    /// @param down Reference to the lower layer that received the message.
    /// @param text Payload of the received message. The encoding depends on the
    ///             application.
    /// @returns The number of consumed characters or a negative value to signal
    ///          an error.
    /// @note Discarded data is lost permanently.
    /// @note When returning a negative value for the number of consumed
    ///       characters, clients must also call `down.set_read_error(...)` with
    ///       an appropriate error code.
    template <class LowerLayerPtr>
    ptrdiff_t consume_text(LowerLayerPtr down, string_view text);
  }

  interface mixed_message_oriented [role: lower layer] {
    /// Prepares the layer for an outgoing binary message, e.g., by allocating
    /// an output buffer as necessary.
    void begin_binary_message();

    /// Returns a reference to the buffer for assembling the current message.
    /// Users may only call this function and write to the buffer between
    /// calling `begin_binary_message()` and `end_binary_message()`.
    /// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
    ///       headers.
    byte_buffer& binary_message_buffer();

    /// Seals and prepares a binary message for transfer.
    /// @note When returning `false`, clients must also call
    ///       `down.set_read_error(...)` with an appropriate error code.
    template <class LowerLayerPtr>
    bool end_binary_message();

    /// Prepares the layer for an outgoing text message, e.g., by allocating
    /// an output buffer as necessary.
    void begin_text_message();

    /// Returns a reference to the buffer for assembling the current message.
    /// Users may only call this function and write to the buffer between
    /// calling `begin_text_message()` and `end_text_message()`.
    /// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
    ///       headers.
    std::vector<char>& text_message_buffer();

    /// Seals and prepares a text message for transfer.
    /// @note When returning `false`, clients must also call
    ///       `down.set_read_error(...)` with an appropriate error code.
    template <class LowerLayerPtr>
    bool end_text_message();
  }

