cpp2ethernet - Ethernet Abstractions

Overview

cpp2ethernet provides functionality to send and receive data through sockets. Also it contains features that help in managing network interfaces.

This module contains following features:

  • API for UDP and TCP socket handling

  • Wrapper for IP addresses

  • Registry for network interface handling

This module has the following dependencies:

Public API

UDP

AbstractDatagramSocket

The AbstractDatagramSocket is used to setup a UDP connection, reading data and sending data.

  • Receive: An application that is using an AbstractDatagramSocket and wants to receive UDP data shall implement the IDataListener interface. Additionally, setDataListener() has to be set with the implemented IDataListener object. Also a connection has to be properly set up by calling bind() or connect(). Once the UDP data is received, the dataReceived() function will be called. The application is then responsible to read the data with read().

    It is also possible to receive multicasts by joining a multicast group with join().

  • Send: In order to send UDP data, there are two options.

    1. connect() and send(slice<uint8_t& data)

    2. send(DatagramPacket& packet)

    enum
    {
        INVALID_PORT = 0xFFFF
    };

    /**
     * all ErrorCodes used by socket
     * \enum  ErrorCode
     */
    enum class ErrorCode : uint8_t
    {
        /** everything ok */
        UDP_SOCKET_OK,
        /** an error occurred */
        UDP_SOCKET_NOT_OK,
        /** no IDataListener registerd */
        UDP_SOCKET_NO_DATA_LISTENER
    };

    AbstractDatagramSocket();
    AbstractDatagramSocket(AbstractDatagramSocket const&)            = delete;
    AbstractDatagramSocket& operator=(AbstractDatagramSocket const&) = delete;

    /**
     * Binds this UDPSocket to a specific port & local address
     * \param port  local port
     * \return  status of method
     *          - UDP_SOCKET_OK when everything was fine
     *          - UDP_SOCKET_NOT_OK when something went wrong
     */
    virtual ErrorCode bind(ip::IPAddress const* pIpAddress, uint16_t port) = 0;

    virtual ErrorCode join(ip::IPAddress const& groupAddr) = 0;

    /**
     * Returns the binding state of the socket.
     * \return  binding state of the socket
     *          - false: not bound
     *          - true: bound
     */
    virtual bool isBound() const = 0;

    virtual void close() = 0;

    /**
     * Returns whether the socket is closed or not.
     * \return  state of the socket
     *          - false: not closed
     *          - true: closed
     */
    virtual bool isClosed() const = 0;

    /**
     * Connects the socket to a remote address for this socket.
     * \param   address  remote IPAddress
     * \param   port     remote port
     * \return  status of method
     *          - UDP_SOCKET_OK when everything was fine
     *          - UDP_SOCKET_NOT_OK when something went wrong
     */
    virtual ErrorCode
    connect(ip::IPAddress const& address, uint16_t port, ip::IPAddress* pLocalAddress)
        = 0;

    /**
     * Disconnects the socket
     */
    virtual void disconnect() = 0;

    /**
     * Returns the connection state of the socket.
     * \return  connection state of the socket
     *          - false: not connected
     *          - true: connected
     */
    virtual bool isConnected() const = 0;

    /**
     * reads a given number of bytes from the socket
     * \param  buffer   buffer to receive data to. If 0L is passed the n
     * bytes should be skipped, i.e. the input stream has to advance by
     * n bytes.
     * \param   n   max number of bytes to receive
     * \return  number of bytes really read
     *          - 0: an error occurred reading from the socket
     *          - else: bytes read
     */
    virtual size_t read(uint8_t* buffer, size_t n) = 0;

    /**
     * sends an amount of data
     * \param  data   data to send
     * \return  status of transmission
     *          - UDP_SOCKET_OK when data was sent to UDP stack
     *          - UDP_SOCKET_NOT_OK when socket has not been opened
     * \note
     * sending the data may be asynchronous. for this reason the
     * IDataSendNotificationListener has a appropriate callback.
     */
    virtual ErrorCode send(::etl::span<uint8_t const> const& data) = 0;

    /**
     * Sends a DatagramPacket.
     * \return  status of transmission
     *          - UDP_SOCKET_OK when data was sent to UDP stack
     *          - UDP_SOCKET_NOT_OK when socket has not been opened
     */
    virtual ErrorCode send(DatagramPacket const& packet) = 0;

    /**
     * sets the listener to this socket instance
     * \param  pListener  IDataListener to attach
     */
    void setDataListener(IDataListener* pListener);

    void setDataSentListener(IDataSentListener* pListener);

    /**
     * \return  Local IPAddress, 0L if this operation is not allowed
     */
    virtual ip::IPAddress const* getIPAddress() const = 0;

    virtual ip::IPAddress const* getLocalIPAddress() const = 0;

    /**
     * Returns remote port if connected, INVALID_PORT otherwise
     */
    virtual uint16_t getPort() const = 0;

    virtual uint16_t getLocalPort() const = 0;

DatagramPacket

A DatagramPacket is a wrapper for UDP payload and its destination IP and destination port. Using a DatagramPacket allows to send data over a AbstractDatagramSocket without using connect().

    /**
     * Constructs a UDP datagram packet.
     * \param  data     array of data
     * \param  length   length of data
     * \param  address  target IPAddress
     * \param  port     target port
     */
    DatagramPacket(uint8_t const data[], uint16_t length, ::ip::IPAddress address, uint16_t port);
    DatagramPacket(uint8_t const data[], uint16_t length, ::ip::IPEndpoint const& endpoint);
    DatagramPacket(DatagramPacket const&)            = delete;
    DatagramPacket& operator=(DatagramPacket const&) = delete;

    uint8_t const* getData() const { return _data; }

    uint16_t getLength() const { return _length; }

    ::ip::IPEndpoint getEndpoint() const { return _endpoint; }

    ::ip::IPAddress getAddress() const { return _endpoint.getAddress(); }

    uint16_t getPort() const { return _endpoint.getPort(); }

    bool operator==(DatagramPacket const& other) const;

IDataListener

This interface shall be used in order to receive UDP data. dataReceived() will be called once Data has arrived for the listening Socket.

Note

If dataReceived() is called, the application is responsible to read the data. Depending on the Ethernet Driver implementation, not reading all data, which is received, could lead to packet drops, because of unfreed packets in the driver.

    /**
     * callback that gets called when data has been received
     * \param  length  number of bytes received
     *
     * \note
     * It is expected that the data is read from the AbstractSocket within this
     * callback. The AbstractSocket may overwrite its internal buffers after
     * this callback is done.
     */
    virtual void dataReceived(
        AbstractDatagramSocket& socket,
        ::ip::IPAddress sourceAddress,
        uint16_t sourcePort,
        ::ip::IPAddress destinationAddress,
        uint16_t length)
        = 0;

TCP

AbstractServerSocket

An AbstractServerSocket is the base class for server sockets that may open a specific port. To accept incoming connections the AbstractServerSocket needs a ISocketProvidingConnectionListener. On an incoming connection request the AbstractServerSocket requests an AbstractSocket object from the ISocketProvidingConnectionListener to bind the connection to via its getSocket() method. In case of success the listeners connectionAccepted() method is called passing the bound AbstractSocket to the application.

    /**
     * constructor taking the AbstractServerSockets port
     * \param   port   port of socket
     * \param   providingListener  ISocketProvidingConnectionListener which
     *          provides the AbstractServerSocket with AbstractSocket objects
     *          and gets notified when a connection is successfully established
     *
     * As long as the ISocketProvidingConnectionListener returns valid pointers
     * to AbstractSocket objects connections are accpeted and delegated to the
     * ISocketProvidingConnectionListener. When NULL is returned, the connection
     * is refused.
     */
    AbstractServerSocket(uint16_t port, ISocketProvidingConnectionListener& providingListener);

    /**
     * \return  port the AbstractServerSocket is listening for
     */
    uint16_t getLocalPort() const { return _port; }

    void setPort(uint16_t const port) { _port = port; }

    /**
     * accepts a connection to the AbstractServerSockets port
     */
    virtual bool accept() = 0;

    /**
     * Binds this server socket to a given netif.
     */
    virtual bool bind(ip::IPAddress const& localIpAddress, uint16_t port) = 0;

    /**
     * closes the accepted port
     */
    virtual void close() = 0;

    virtual bool isClosed() const = 0;

AbstractSocket

The AbstractSocket is designed to be a socket abstraction to a TCP stack. The socket class can be used to connect to a remote system. It is also returned to an ISocketConnectionListener by AbstractServerSocket (using its ISocketProvider) once a connection from a remote system is accepted. An active socket is used to receive data from and transmit data to a remote system.

  • Receive: The application that wants to receive data from a socket has to register through the setDataListener method as an IDataListener. After that the application is notified through its dataReceived method.

  • Send: Sending data through a socket is usually asynchronous. Because of that it is possible to register an IDataSendNotificationListener who is notified when the data passed to the send method is written to the TCP-stack.

Note

This is not the time when the ACK package from the remote system is received!

    /**
     * all ErrorCodes used by socket
     * \enum   ErrorCode
     */
    enum class ErrorCode : uint8_t
    {
        /** everything ok */
        SOCKET_ERR_OK,
        /** an error occurred */
        SOCKET_ERR_NOT_OK,
        /** socket not open to send */
        SOCKET_ERR_NOT_OPEN,
        /** no more outgoing buffer available */
        SOCKET_ERR_NO_MORE_BUFFER,
        /** socket is full, flush before continuing */
        SOCKET_FLUSH
    };

    using ConnectedDelegate = ::etl::delegate<void(ErrorCode)>;

    /**
     * constructor
     * \post    fpDataListener == 0L
     * \post    fpSendNotificationListener == 0L
     */
    AbstractSocket();

    AbstractSocket(AbstractSocket const&)            = delete;
    AbstractSocket& operator=(AbstractSocket const&) = delete;

    /**
     * binds to a local address and port
     * \param   ipAddr  local ip address
     * \param   port    local port
     */
    virtual ErrorCode bind(ip::IPAddress const& ipAddr, uint16_t port) = 0;

    /**
     * connects to a remote host
     * \param   ipAddr  remote ip address
     * \param   port    remote port
     */
    virtual ErrorCode
    connect(ip::IPAddress const& ipAddr, uint16_t port, ConnectedDelegate delegate)
        = 0;

    /**
     * closes the socket by sending a FIN packet
     */
    virtual ErrorCode close() = 0;

    /**
     * aborts the socket by sending a RST packet
     */
    virtual void abort() = 0;

    /**
     * flushes enqueued data
     */
    virtual ErrorCode flush() = 0;

    /**
     * discards any received data without ack
     */
    virtual void discardData() = 0;

    /**
     * \return  number of bytes available in outgoing buffer
     */
    virtual size_t available() = 0;

    /**
     * reads a single byte
     * \param   byte   byte to read
     * \return  number of bytes read
     *          - 0: an error occurred reading from the socket
     *          - 1: byte read
     */
    virtual uint8_t read(uint8_t& byte) = 0;

    /**
     * reads a given number of bytes from the socket
     * \param   buffer  buffer to receive data to. If 0L is passed the n
     * bytes should be skipped, i.e. the input stream has to advance by
     * n bytes.
     * \param   n    number of bytes to receive
     * \return  number of bytes really read
     *          - 0: an error occurred reading from the socket
     *          - else: bytes read
     */
    virtual size_t read(uint8_t* buffer, size_t n) = 0;

    /**
     * sends an amount of data
     * \param   data  data to send
     * \return  status of transmission
     *          - SOCKET_OK when data was sent to TCP stack
     *          - SOCKET_CLOSED when socket has not been opened
     * \note
     * Sending the data may be asynchronous. For this reason the
     * IDataSendNotificationListener has an appropriate callback.
     */
    virtual ErrorCode send(::etl::span<uint8_t const> const& data) = 0;

    /**
     * sets the listener to this socket instance
     * \param  pListener  IDataListener to attach
     */
    void setDataListener(IDataListener* pListener);

    IDataListener* getDataListener() const;

    /**
     * sets the IDataSendNotificationListener of this socket
     * \param  pListener   listener that gets notified when data has been
     * copied completely to TCP stack
     */
    void setSendNotificationListener(IDataSendNotificationListener* pListener);

    IDataSendNotificationListener* getSendNotificationListener() const;

    virtual ip::IPAddress getRemoteIPAddress() const = 0;

    virtual ip::IPAddress getLocalIPAddress() const = 0;

    virtual uint16_t getRemotePort() const = 0;

    virtual uint16_t getLocalPort() const = 0;

    virtual bool isClosed() const = 0;

    virtual bool isEstablished() const = 0;

    /**
     * Permanently disables NAGLE algorithm for this socket.
     */
    virtual void disableNagleAlgorithm() = 0;

    /**
     * Enables TCP keepalive for this socket.
     * \param idle the length of time to keep an idle TCP connection active
     * \param interval the interval between packets sent to validate the TCP connection
     * \param probes the number of keepalive probes to be sent before terminating the connection
     */
    virtual void enableKeepAlive(uint32_t idle, uint32_t interval, uint32_t probes) = 0;

    /**
     * Disables TCP keepalive for this socket.
     */
    virtual void disableKeepAlive() = 0;

ISocketProvidingConnectionListener

Incoming connection requests for an AbstractServerSocket will need a preallocated socket from the application. It can be provided by the getSocket() method. The connection request can be rejected, because of e.g. no more available sockets in the application, with a return of a nullptr.

    /**
     * returns a pointer to a AbstractSocket object
     * \param ipAddr Ip address of client which tries to connect.
     * \param port Port of client from which it tries to connect.
     * \return
     *          - pointer to AbstractSocket
     *          - NULL if no more sockets are available
     */
    virtual AbstractSocket* getSocket(ip::IPAddress const& ipAddr, uint16_t port) = 0;

    /**
     * callback being called when a TCP connection gets accepted
     * \param  socket  the AbstractSocket representing the connection
     *
     * Usually the ISocketConnectionListener sets an IDataListener to the
     * socket when the connection is established.
     */
    virtual void connectionAccepted(AbstractSocket& socket) = 0;