/* libhw/generic/net.h - Device-independent network definitions
 *
 * Copyright (C) 2024  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#ifndef _LIBHW_GENERIC_NET_H_
#define _LIBHW_GENERIC_NET_H_

#include <inttypes.h>  /* for PRI{u,x}{n} */
#include <stdbool.h>   /* for bool */
#include <stddef.h>    /* for size_t */
#include <stdint.h>    /* for uint{n}_t} */
#include <sys/types.h> /* for ssize_t */

/* Errnos *********************************************************************/

#define NET_EOTHER              1
#define NET_EARP_TIMEOUT        2
#define NET_EACK_TIMEOUT        3
#define NET_ERECV_TIMEOUT       4
#define NET_ETHREAD             5
#define NET_ECLOSED             6
#define NET_EMSGSIZE            7

const char *net_strerror(int net_errno);

/* Address types **************************************************************/

struct net_ip4_addr {
	unsigned char   octets[4];
};

static const struct net_ip4_addr net_ip4_addr_broadcast = {{255, 255, 255, 255}};
static const struct net_ip4_addr net_ip4_addr_zero      = {{0, 0, 0, 0}};

#define PRI_net_ip4_addr       "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8
#define ARG_net_ip4_addr(addr) (addr).octets[0], \
                               (addr).octets[1], \
                               (addr).octets[2], \
                               (addr).octets[3]

struct net_eth_addr {
	unsigned char   octets[6];
};

#define PRI_net_eth_addr       "%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8
#define ARG_net_eth_addr(addr) (addr).octets[0], \
                               (addr).octets[1], \
                               (addr).octets[2], \
                               (addr).octets[3], \
                               (addr).octets[4], \
                               (addr).octets[5]

/* Streams (e.g. TCP) *********************************************************/

struct net_stream_listener_vtable;
struct net_stream_conn_vtable;

typedef struct {
	struct net_stream_listener_vtable       *vtable;
} implements_net_stream_listener;

typedef struct {
	struct net_stream_conn_vtable           *vtable;
} implements_net_stream_conn;

struct net_stream_listener_vtable {
	/**
	 * It is invalid to accept() a new connection if an existing
	 * connection is still open.
	 */
	implements_net_stream_conn *(*accept)(implements_net_stream_listener *self);

	/**
	 * The net_stream_conn returned from accept() may still be
	 * valid after the listener is closed.
	 *
	 * Return 0 on success, -errno on error.
	 */
	int                         (*close)(implements_net_stream_listener *self);
};

struct net_stream_conn_vtable {
	/**
	 * Return bytes-read on success, 0 on EOF, -errno on error; a
	 * short read is *not* an error.
	 */
	ssize_t                  (*read)(implements_net_stream_conn *self,
	                                 void *buf, size_t count);

	/**
	 * Set a timestamp after which calls to read() will return
	 * NET_ETIMEDOUT.  The timestamp is in nanoseconds on the
	 * system monotonic clock, which is usually (on pico-sdk and
	 * on the Linux kernel) nanoseconds-since-boot.
	 *
	 * A zero value disables the deadline.
	 *
	 * (2⁶⁴-1 nanoseconds is more than 500 years; there is little
	 * risk of this overflowing)
	 */
	void                     (*set_read_deadline)(implements_net_stream_conn *self,
	                                              uint64_t ns_since_boot);

	/**
	 * Return `count` on success, -errno on error; a short write *is* an
	 * error.
	 *
	 * Writes are *not* guaranteed to be atomic (as this would be
	 * expensive to implement), so if you have concurrent writers then you
	 * should arrange for a mutex to protect the connection.
	 */
	ssize_t                  (*write)(implements_net_stream_conn *self,
	                                  void *buf, size_t count);

	/**
	 * Return 0 on success, -errno on error.
	 */
	int                      (*close)(implements_net_stream_conn *self,
	                                  bool rd, bool wr);
};

/* Packets (e.g. UDP) *********************************************************/

struct net_packet_conn_vtable;

typedef struct {
	struct net_packet_conn_vtable           *vtable;
} implements_net_packet_conn;

struct net_packet_conn_vtable {
	ssize_t (*sendto  )(implements_net_packet_conn *self,
	                    void *buf, size_t len,
	                    struct net_ip4_addr node, uint16_t port);
	/**
	 * @return The full length of the message, which may be more
	 * than the given `len` (as if the Linux MSG_TRUNC flag were
	 * given).
	 */
	ssize_t (*recvfrom)(implements_net_packet_conn *self,
	                    void *buf, size_t len,
	                    struct net_ip4_addr *ret_node, uint16_t *ret_port);
	void    (*set_read_deadline)(implements_net_packet_conn *self,
	                             uint64_t ns_since_boot);
	int     (*close   )(implements_net_packet_conn *self);
};

/* Interfaces *****************************************************************/

struct net_iface_config {
	struct net_ip4_addr addr;
	struct net_ip4_addr gateway_addr;
	struct net_ip4_addr subnet_mask;
};

struct net_iface_vtable;

typedef struct {
	struct net_iface_vtable        *vtable;
} implements_net_iface;

struct net_iface_vtable {
	struct net_eth_addr             (*hwaddr    )(implements_net_iface *);
	void                            (*ifup      )(implements_net_iface *, struct net_iface_config);
	void                            (*ifdown    )(implements_net_iface *);

	implements_net_stream_listener *(*tcp_listen)(implements_net_iface *, uint16_t local_port);
	implements_net_stream_conn     *(*tcp_dial  )(implements_net_iface *, struct net_ip4_addr remote_node, uint16_t remote_port);
	implements_net_packet_conn     *(*udp_conn  )(implements_net_iface *, uint16_t local_port);
};

#endif /* _LIBHW_GENERIC_NET_H_ */