#pragma once

#include <arpa/inet.h>
#include <assert.h>
#include <c-list.h>
#include <c-stdaux.h>
#include <endian.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <syslog.h>
#include "n-dhcp4.h"

typedef struct NDhcp4CConnection NDhcp4CConnection;
typedef struct NDhcp4CEventNode NDhcp4CEventNode;
typedef struct NDhcp4ClientProbeOption NDhcp4ClientProbeOption;
typedef struct NDhcp4Header NDhcp4Header;
typedef struct NDhcp4Incoming NDhcp4Incoming;
typedef struct NDhcp4Message NDhcp4Message;
typedef struct NDhcp4Outgoing NDhcp4Outgoing;
typedef struct NDhcp4SConnection NDhcp4SConnection;
typedef struct NDhcp4SConnectionIp NDhcp4SConnectionIp;
typedef struct NDhcp4SEventNode NDhcp4SEventNode;
typedef struct NDhcp4LogQueue NDhcp4LogQueue;

/* specs */

#define N_DHCP4_NETWORK_IP_MAXIMUM_HEADER_SIZE (60) /* See RFC791 */
#define N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE (576) /* See RFC791 */
#define N_DHCP4_NETWORK_SERVER_PORT (67)
#define N_DHCP4_NETWORK_CLIENT_PORT (68)
#define N_DHCP4_MESSAGE_MAGIC ((uint32_t)(0x63825363))
#define N_DHCP4_MESSAGE_FLAG_BROADCAST (htons(0x8000))

enum {
        N_DHCP4_OP_BOOTREQUEST                          = 1,
        N_DHCP4_OP_BOOTREPLY                            = 2,
};

enum {
        N_DHCP4_OPTION_PAD                              = 0,
        N_DHCP4_OPTION_SUBNET_MASK                      = 1,
        N_DHCP4_OPTION_TIME_OFFSET                      = 2,
        N_DHCP4_OPTION_ROUTER                           = 3,
        N_DHCP4_OPTION_DOMAIN_NAME_SERVER               = 6,
        N_DHCP4_OPTION_HOST_NAME                        = 12,
        N_DHCP4_OPTION_BOOT_FILE_SIZE                   = 13,
        N_DHCP4_OPTION_DOMAIN_NAME                      = 15,
        N_DHCP4_OPTION_ROOT_PATH                        = 17,
        N_DHCP4_OPTION_ENABLE_IP_FORWARDING             = 19,
        N_DHCP4_OPTION_ENABLE_IP_FORWARDING_NL          = 20,
        N_DHCP4_OPTION_POLICY_FILTER                    = 21,
        N_DHCP4_OPTION_INTERFACE_MDR                    = 22,
        N_DHCP4_OPTION_INTERFACE_TTL                    = 23,
        N_DHCP4_OPTION_INTERFACE_MTU_AGING_TIMEOUT      = 24,
        N_DHCP4_OPTION_INTERFACE_MTU                    = 26,
        N_DHCP4_OPTION_BROADCAST                        = 28,
        N_DHCP4_OPTION_STATIC_ROUTE                     = 33,
        N_DHCP4_OPTION_NTP_SERVER                       = 42,
        N_DHCP4_OPTION_VENDOR_SPECIFIC                  = 43,
        N_DHCP4_OPTION_REQUESTED_IP_ADDRESS             = 50,
        N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME            = 51,
        N_DHCP4_OPTION_OVERLOAD                         = 52,
        N_DHCP4_OPTION_MESSAGE_TYPE                     = 53,
        N_DHCP4_OPTION_SERVER_IDENTIFIER                = 54,
        N_DHCP4_OPTION_PARAMETER_REQUEST_LIST           = 55,
        N_DHCP4_OPTION_ERROR_MESSAGE                    = 56,
        N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE             = 57,
        N_DHCP4_OPTION_RENEWAL_T1_TIME                  = 58,
        N_DHCP4_OPTION_REBINDING_T2_TIME                = 59,
        N_DHCP4_OPTION_VENDOR_CLASS_IDENTIFIER          = 60,
        N_DHCP4_OPTION_CLIENT_IDENTIFIER                = 61,
        N_DHCP4_OPTION_FQDN                             = 81,
        N_DHCP4_OPTION_NEW_POSIX_TIMEZONE               = 100,
        N_DHCP4_OPTION_NEW_TZDB_TIMEZONE                = 101,
        N_DHCP4_OPTION_CLASSLESS_STATIC_ROUTE           = 121,
        N_DHCP4_OPTION_PRIVATE_BASE                     = 224,
        N_DHCP4_OPTION_PRIVATE_LAST                     = 254,
        N_DHCP4_OPTION_END                              = 255,
        _N_DHCP4_OPTION_N                               = 256,
};

enum {
        N_DHCP4_OVERLOAD_FILE                           = 1,
        N_DHCP4_OVERLOAD_SNAME                          = 2,
};

enum {
        N_DHCP4_MESSAGE_DISCOVER                        = 1,
        N_DHCP4_MESSAGE_OFFER                           = 2,
        N_DHCP4_MESSAGE_REQUEST                         = 3,
        N_DHCP4_MESSAGE_DECLINE                         = 4,
        N_DHCP4_MESSAGE_ACK                             = 5,
        N_DHCP4_MESSAGE_NAK                             = 6,
        N_DHCP4_MESSAGE_RELEASE                         = 7,
        N_DHCP4_MESSAGE_INFORM                          = 8,
        N_DHCP4_MESSAGE_FORCERENEW                      = 9,
};

struct NDhcp4Header {
        uint8_t op;
        uint8_t htype;
        uint8_t hlen;
        uint8_t hops;
        uint32_t xid;
        uint16_t secs;
        uint16_t flags;
        uint32_t ciaddr;
        uint32_t yiaddr;
        uint32_t siaddr;
        uint32_t giaddr;
        uint8_t chaddr[16];
} _c_packed_;

struct NDhcp4Message {
        NDhcp4Header header;
        uint8_t sname[64];
        uint8_t file[128];
        uint32_t magic;
        uint8_t options[];
} _c_packed_;

/* objects */

enum {
        _N_DHCP4_E_INTERNAL = _N_DHCP4_E_N,

        N_DHCP4_E_UNEXPECTED,

        N_DHCP4_E_NO_SPACE,
        N_DHCP4_E_MALFORMED,

        N_DHCP4_E_DROPPED,
        N_DHCP4_E_DOWN,
        N_DHCP4_E_AGAIN,
};

enum {
        N_DHCP4_C_CONNECTION_STATE_INIT,
        N_DHCP4_C_CONNECTION_STATE_PACKET,
        N_DHCP4_C_CONNECTION_STATE_DRAINING,
        N_DHCP4_C_CONNECTION_STATE_UDP,
        N_DHCP4_C_CONNECTION_STATE_CLOSED,
};

enum {
        N_DHCP4_CLIENT_EPOLL_TIMER,
        N_DHCP4_CLIENT_EPOLL_IO,
};

enum {
        N_DHCP4_CLIENT_PROBE_STATE_INIT,
        N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT,
        N_DHCP4_CLIENT_PROBE_STATE_SELECTING,
        N_DHCP4_CLIENT_PROBE_STATE_REBOOTING,
        N_DHCP4_CLIENT_PROBE_STATE_REQUESTING,
        N_DHCP4_CLIENT_PROBE_STATE_GRANTED,
        N_DHCP4_CLIENT_PROBE_STATE_BOUND,
        N_DHCP4_CLIENT_PROBE_STATE_RENEWING,
        N_DHCP4_CLIENT_PROBE_STATE_REBINDING,
        N_DHCP4_CLIENT_PROBE_STATE_EXPIRED,
};

enum {
        N_DHCP4_CLIENT_LEASE_STATE_INIT,
        N_DHCP4_CLIENT_LEASE_STATE_OFFERED,
        N_DHCP4_CLIENT_LEASE_STATE_SELECTED,
        N_DHCP4_CLIENT_LEASE_STATE_DECLINED,
        N_DHCP4_CLIENT_LEASE_STATE_ACKED,
};

enum {
        N_DHCP4_SERVER_EPOLL_TIMER,
        N_DHCP4_SERVER_EPOLL_IO,
};

enum {
        _N_DHCP4_C_MESSAGE_INVALID = 0,
        N_DHCP4_C_MESSAGE_DISCOVER,
        N_DHCP4_C_MESSAGE_INFORM,
        N_DHCP4_C_MESSAGE_SELECT,
        N_DHCP4_C_MESSAGE_IGNORE,
        N_DHCP4_C_MESSAGE_RENEW,
        N_DHCP4_C_MESSAGE_REBIND,
        N_DHCP4_C_MESSAGE_REBOOT,
        N_DHCP4_C_MESSAGE_RELEASE,
        N_DHCP4_C_MESSAGE_DECLINE,
};

struct NDhcp4Outgoing {
        NDhcp4Message *message;
        size_t n_message;
        size_t i_message;
        size_t max_size;

        uint8_t overload : 2;

        struct {
                uint8_t type;
                uint8_t message_type;
                uint32_t client_addr;
                uint32_t server_id;
                uint64_t start_time;
                uint64_t base_time;
                uint64_t send_time;
                uint64_t send_jitter;
                size_t n_send;
        } userdata;
};

#define N_DHCP4_OUTGOING_NULL(_x) {                                             \
        }

struct NDhcp4Incoming {
        struct {
                uint8_t *value;
                size_t size;
        } options[_N_DHCP4_OPTION_N];

        struct {
                uint8_t type;
                uint64_t start_time;
                uint64_t base_time;
        } userdata;

        size_t n_message;
        NDhcp4Message message;
        /* @message must be the last member */
};

#define N_DHCP4_INCOMING_NULL(_x) {                                             \
        }

struct NDhcp4ClientConfig {
        int ifindex;
        unsigned int transport;
        bool request_broadcast;
        uint8_t mac[32]; /* MAX_ADDR_LEN */
        size_t n_mac;
        uint8_t broadcast_mac[32]; /* MAX_ADDR_LEN */
        size_t n_broadcast_mac;
        uint8_t *client_id;
        size_t n_client_id;
};

#define N_DHCP4_CLIENT_CONFIG_NULL(_x) {                                        \
                .transport = _N_DHCP4_TRANSPORT_N,                              \
        }

struct NDhcp4ClientProbeOption {
        uint8_t option;
        uint8_t n_data;
        uint8_t data[];
};

#define N_DHCP4_CLIENT_PROBE_OPTION_NULL(_x) {                                  \
                .option = N_DHCP4_OPTION_PAD,                                   \
        }

struct NDhcp4ClientProbeConfig {
        bool inform_only;
        bool init_reboot;
        struct in_addr requested_ip;
        unsigned short int entropy[3];
        uint64_t ms_start_delay;        /* max ms to wait before starting probe */
        NDhcp4ClientProbeOption *options[UINT8_MAX + 1];
        int8_t request_parameters[UINT8_MAX + 1];
        size_t n_request_parameters;
};

#define N_DHCP4_CLIENT_PROBE_CONFIG_NULL(_x) {                                  \
                .ms_start_delay = N_DHCP4_CLIENT_START_DELAY_RFC2131,           \
        }

struct NDhcp4CEventNode {
        CList client_link;
        CList probe_link;
        NDhcp4ClientEvent event;
        bool is_public : 1;
};

#define N_DHCP4_C_EVENT_NODE_NULL(_x) {                                         \
                .client_link = C_LIST_INIT((_x).client_link),                   \
                .probe_link = C_LIST_INIT((_x).probe_link),                     \
        }

struct NDhcp4LogQueue {
        CList *event_list;
        NDhcp4CEventNode nomem_node;
        int log_level;
        bool is_client : 1;
};

#define N_DHCP4_LOG_QUEUE_NULL_DEFUNCT() {                                      \
                .log_level = -1,                                                \
                .is_client = false,                                             \
        }

#define N_DHCP4_LOG_QUEUE_NULL_CLIENT(client) {                                 \
                .event_list = &((client).event_list),                           \
                .log_level = -1,                                                \
                .is_client = true,                                              \
                .nomem_node = {                                                 \
                        .client_link = C_LIST_INIT((client).log_queue.nomem_node.client_link), \
                        .probe_link = C_LIST_INIT((client).log_queue.nomem_node.probe_link), \
                        .event = {                                              \
                                .event = N_DHCP4_CLIENT_EVENT_LOG,              \
                                .log = {                                        \
                                        .level = LOG_CRIT,                      \
                                        .message = "one or more logging messages dropped due to out of memory", \
                                        .allow_steal_message = false,           \
                                },                                              \
                        },                                                      \
                        .is_public = false,                                     \
                },                                                              \
        }

struct NDhcp4CConnection {
        NDhcp4ClientConfig *client_config;
        NDhcp4ClientProbeConfig *probe_config;
        NDhcp4LogQueue *log_queue;

        int fd_epoll;

        unsigned int state;             /* current connection state */
        int fd_packet;                  /* packet socket */
        int fd_udp;                     /* udp socket */

        NDhcp4Outgoing *request;        /* current request */

        uint32_t client_ip;             /* client IP address, or 0 */
        uint32_t server_ip;             /* server IP address, or 0 */
        uint16_t mtu;                   /* client mtu, or 0 */

        /*
         * When we get DHCP packets from the kernel, we need a buffer to read
         * the data into. Since UDP packets can be up to 2^16 bytes in size, we
         * avoid placing it on the stack and instead read into this scratch
         * buffer. It is purely meant as stack replacement, no data is returned
         * through this buffer.
         */
        uint8_t scratch_buffer[UINT16_MAX];
};

#define N_DHCP4_C_CONNECTION_NULL(_x) {                                         \
                .fd_packet = -1,                                                \
                .fd_udp = -1,                                                   \
        }

struct NDhcp4Client {
        unsigned long n_refs;
        NDhcp4ClientConfig *config;
        CList event_list;

        NDhcp4LogQueue log_queue;

        int fd_epoll;
        int fd_timer;

        uint16_t mtu;
        NDhcp4ClientProbe *current_probe;
        uint64_t scheduled_timeout;

        bool preempted : 1;
};

#define N_DHCP4_CLIENT_NULL(_x) {                                               \
                .n_refs = 1,                                                    \
                .event_list = C_LIST_INIT((_x).event_list),                     \
                .fd_epoll = -1,                                                 \
                .fd_timer = -1,                                                 \
                .log_queue = N_DHCP4_LOG_QUEUE_NULL_CLIENT(_x),                 \
        }

struct NDhcp4ClientProbe {
        NDhcp4ClientProbeConfig *config;
        NDhcp4Client *client;
        CList event_list;
        CList lease_list;
        void *userdata;

        unsigned int state;                     /* current probe state */
        struct in_addr last_address;            /* last address obtained */
        uint64_t ns_deferred;                   /* timeout for deferred action */
        uint64_t ns_reinit;
        uint64_t ns_nak_restart_delay;          /* restart delay after a nak */
        NDhcp4ClientLease *current_lease;       /* current lease */

        NDhcp4CConnection connection;           /* client connection wrapper */
};

#define N_DHCP4_CLIENT_PROBE_NULL(_x) {                                         \
                .event_list = C_LIST_INIT((_x).event_list),                     \
                .lease_list = C_LIST_INIT((_x).lease_list),                     \
                .connection = N_DHCP4_C_CONNECTION_NULL((_x).connection),       \
        }

struct NDhcp4ClientLease {
        unsigned long n_refs;

        NDhcp4ClientProbe *probe;
        CList probe_link;

        NDhcp4Incoming *message;

        uint64_t t1;
        uint64_t t2;
        uint64_t lifetime;
};

#define N_DHCP4_CLIENT_LEASE_NULL(_x) {                                         \
                .n_refs = 1,                                                    \
                .probe_link = C_LIST_INIT((_x).probe_link),                     \
        }

struct NDhcp4ServerConfig {
        int ifindex;
};

#define N_DHCP4_SERVER_CONFIG_NULL(_x) {                                        \
        }

struct NDhcp4SEventNode {
        CList server_link;
        NDhcp4ServerEvent event;
        bool is_public : 1;
};

#define N_DHCP4_S_EVENT_NODE_NULL(_x) {                                         \
                .server_link = C_LIST_INIT((_x).server_link),                   \
        }

struct NDhcp4SConnection {
        int ifindex;                    /* interface index */
        int fd_packet;                  /* packet socket */
        int fd_udp;                     /* udp socket */
        uint8_t buf[UINT16_MAX];        /* scratch recevie buffer */

        /* XXX: support a set of server addresses */
        NDhcp4SConnectionIp *ip;        /* server IP address, or NULL */
};

#define N_DHCP4_S_CONNECTION_NULL(_x) {                                         \
                .fd_packet = -1,                                                \
                .fd_udp = -1,                                                   \
        }

struct NDhcp4SConnectionIp {
        NDhcp4SConnection *connection;
        struct in_addr ip;
};

#define N_DHCP4_S_CONNECTION_IP_NULL(_x) {                                      \
}

struct NDhcp4Server {
        unsigned long n_refs;
        CList event_list;
        CList lease_list;

        bool preempted : 1;

        NDhcp4SConnection connection;
};

#define N_DHCP4_SERVER_NULL(_x) {                                               \
                .n_refs = 1,                                                    \
                .event_list = C_LIST_INIT((_x).event_list),                     \
                .lease_list = C_LIST_INIT((_x).lease_list),                     \
                .connection = N_DHCP4_S_CONNECTION_NULL((_x).connection),       \
        }

struct NDhcp4ServerIp {
                NDhcp4SConnectionIp ip;
};

#define N_DHCP4_SERVER_IP_NULL(_x) {                                            \
                .ip = N_DHCP4_S_CONNECTION_IP_NULL((_x).ip),                    \
        }

struct NDhcp4ServerLease {
        unsigned long n_refs;

        NDhcp4Server *server;
        CList server_link;

        NDhcp4Incoming *request;
        NDhcp4Incoming *reply;
};

#define N_DHCP4_SERVER_LEASE_NULL(_x) {                                         \
                .n_refs = 1,                                                    \
                .server_link = C_LIST_INIT((_x).server_link),                   \
        }

/* outgoing messages */

int n_dhcp4_outgoing_new(NDhcp4Outgoing **outgoingp, size_t max_size, uint8_t overload);
NDhcp4Outgoing *n_dhcp4_outgoing_free(NDhcp4Outgoing *outgoing);

NDhcp4Header *n_dhcp4_outgoing_get_header(NDhcp4Outgoing *outgoing);
size_t n_dhcp4_outgoing_get_raw(NDhcp4Outgoing *outgoing, const void **rawp);
int n_dhcp4_outgoing_append(NDhcp4Outgoing *outgoing, uint8_t option, const void *data, uint8_t n_data);

int n_dhcp4_outgoing_append_t1(NDhcp4Outgoing *message, uint32_t t1);
int n_dhcp4_outgoing_append_t2(NDhcp4Outgoing *message, uint32_t t2);
int n_dhcp4_outgoing_append_lifetime(NDhcp4Outgoing *message, uint32_t lifetime);
int n_dhcp4_outgoing_append_server_identifier(NDhcp4Outgoing *message, struct in_addr addr);
int n_dhcp4_outgoing_append_requested_ip(NDhcp4Outgoing *message, struct in_addr addr);

void n_dhcp4_outgoing_set_secs(NDhcp4Outgoing *message, uint16_t secs);
void n_dhcp4_outgoing_set_xid(NDhcp4Outgoing *message, uint32_t xid);
void n_dhcp4_outgoing_set_yiaddr(NDhcp4Outgoing *message, struct in_addr yiaddr);

void n_dhcp4_outgoing_get_xid(NDhcp4Outgoing *message, uint32_t *xidp);

/* incoming messages */

int n_dhcp4_incoming_new(NDhcp4Incoming **incomingp, const void *raw, size_t n_raw);
NDhcp4Incoming *n_dhcp4_incoming_free(NDhcp4Incoming *incoming);

NDhcp4Header *n_dhcp4_incoming_get_header(NDhcp4Incoming *incoming);
size_t n_dhcp4_incoming_get_raw(NDhcp4Incoming *incoming, const void **rawp);
int n_dhcp4_incoming_query(NDhcp4Incoming *incoming, uint8_t option, uint8_t **datap, size_t *n_datap);

int n_dhcp4_incoming_query_message_type(NDhcp4Incoming *message, uint8_t *typep);
int n_dhcp4_incoming_query_lifetime(NDhcp4Incoming *message, uint32_t *lifetimep);
int n_dhcp4_incoming_query_t2(NDhcp4Incoming *message, uint32_t *t2p);
int n_dhcp4_incoming_query_t1(NDhcp4Incoming *message, uint32_t *t1p);
int n_dhcp4_incoming_query_server_identifier(NDhcp4Incoming *message, struct in_addr *idp);
int n_dhcp4_incoming_query_max_message_size(NDhcp4Incoming *message, uint16_t *max_message_sizep);
int n_dhcp4_incoming_query_requested_ip(NDhcp4Incoming *message, struct in_addr *requested_ipp);

void n_dhcp4_incoming_get_xid(NDhcp4Incoming *message, uint32_t *xidp);
void n_dhcp4_incoming_get_yiaddr(NDhcp4Incoming *message, struct in_addr *yiaddr);

/* sockets */

int n_dhcp4_c_socket_packet_new(int *sockfdp, int ifindex);
int n_dhcp4_c_socket_udp_new(int *sockfdp,
                             int ifindex,
                             const struct in_addr *client_addr,
                             const struct in_addr *server_addr);
int n_dhcp4_s_socket_packet_new(int *sockfdp);
int n_dhcp4_s_socket_udp_new(int *sockfdp, int ifindex);

int n_dhcp4_c_socket_packet_send(int sockfd,
                                 int ifindex,
                                 const unsigned char *dest_haddr,
                                 unsigned char halen,
                                 NDhcp4Outgoing *message);
int n_dhcp4_c_socket_udp_send(int sockfd, NDhcp4Outgoing *message);
int n_dhcp4_c_socket_udp_broadcast(int sockfd, NDhcp4Outgoing *message);
int n_dhcp4_s_socket_packet_send(int sockfd,
                                 int ifindex,
                                 const struct in_addr *src_inaddr,
                                 const unsigned char *dest_haddr,
                                 unsigned char halen,
                                 const struct in_addr *dest_inaddr,
                                 NDhcp4Outgoing *message);
int n_dhcp4_s_socket_udp_send(int sockfd,
                              const struct in_addr *inaddr_src,
                              const struct in_addr *inaddr_dest,
                              NDhcp4Outgoing *message);
int n_dhcp4_s_socket_udp_broadcast(int sockfd,
                                   const struct in_addr *inaddr_src,
                                   NDhcp4Outgoing *message);

int n_dhcp4_c_socket_packet_recv(int sockfd,
                                 uint8_t *buf,
                                 size_t n_buf,
                                 NDhcp4Incoming **messagep);
int n_dhcp4_c_socket_udp_recv(int sockfd,
                              uint8_t *buf,
                              size_t n_buf,
                              NDhcp4Incoming **messagep);
int n_dhcp4_s_socket_udp_recv(int sockfd,
                              uint8_t *buf,
                              size_t n_buf,
                              NDhcp4Incoming **messagep,
                              struct sockaddr_in *dest);

/* client configs */

int n_dhcp4_client_config_dup(NDhcp4ClientConfig *config,
                              NDhcp4ClientConfig **dupp);

/* client probe configs */

int n_dhcp4_client_probe_config_dup(NDhcp4ClientProbeConfig *config,
                                    NDhcp4ClientProbeConfig **dupp);
uint32_t n_dhcp4_client_probe_config_get_random(NDhcp4ClientProbeConfig *config);

/* client events */

int n_dhcp4_c_event_node_new(NDhcp4CEventNode **nodep);
NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node);

/* client connections */

int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
                              NDhcp4ClientConfig *client_config,
                              NDhcp4ClientProbeConfig *probe_config,
                              NDhcp4LogQueue *log_queue,
                              int fd_epoll);
void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection);

int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection);
int n_dhcp4_c_connection_connect(NDhcp4CConnection *connection,
                                 const struct in_addr *client,
                                 const struct in_addr *server);
void n_dhcp4_c_connection_close(NDhcp4CConnection *connection);

void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
                                      uint64_t *timeoutp);

int n_dhcp4_c_connection_discover_new(NDhcp4CConnection *connection,
                                      NDhcp4Outgoing **request);
int n_dhcp4_c_connection_select_new(NDhcp4CConnection *connection,
                                    NDhcp4Outgoing **request,
                                    NDhcp4Incoming *offer);
int n_dhcp4_c_connection_reboot_new(NDhcp4CConnection *connection,
                                    NDhcp4Outgoing **request,
                                    const struct in_addr *client);
int n_dhcp4_c_connection_renew_new(NDhcp4CConnection *connection,
                                   NDhcp4Outgoing **request);
int n_dhcp4_c_connection_rebind_new(NDhcp4CConnection *connection,
                                    NDhcp4Outgoing **request);
int n_dhcp4_c_connection_decline_new(NDhcp4CConnection *connection,
                                     NDhcp4Outgoing **request,
                                     NDhcp4Incoming *ack,
                                     const char *error);
int n_dhcp4_c_connection_inform_new(NDhcp4CConnection *connection,
                                    NDhcp4Outgoing **request);
int n_dhcp4_c_connection_release_new(NDhcp4CConnection *connection,
                                     NDhcp4Outgoing **request,
                                     const char *error);

int n_dhcp4_c_connection_start_request(NDhcp4CConnection *connection,
                                       NDhcp4Outgoing *request,
                                       uint64_t timestamp);
int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection,
                                        uint64_t timestamp);
int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection,
                                     NDhcp4Incoming **messagep);

/* clients */

int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigned int event);
void n_dhcp4_client_arm_timer(NDhcp4Client *client);

/* client probes */

int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep,
                             NDhcp4ClientProbeConfig *config,
                             NDhcp4Client *client,
                             uint64_t ns_now);

int n_dhcp4_client_probe_raise(NDhcp4ClientProbe *probe, NDhcp4CEventNode **nodep, unsigned int event);
void n_dhcp4_client_probe_get_timeout(NDhcp4ClientProbe *probe, uint64_t *timeoutp);
int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_now);
int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events);
int n_dhcp4_client_probe_transition_select(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, uint64_t ns_now);
int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incoming *ack);
int n_dhcp4_client_probe_transition_decline(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, const char *error, uint64_t ns_now);
int n_dhcp4_client_probe_update_mtu(NDhcp4ClientProbe *probe, uint16_t mtu);

/* client leases */

int n_dhcp4_client_lease_new(NDhcp4ClientLease **leasep, NDhcp4Incoming *message);
void n_dhcp4_client_lease_link(NDhcp4ClientLease *lease, NDhcp4ClientProbe *probe);
void n_dhcp4_client_lease_unlink(NDhcp4ClientLease *lease);

/* server connections */

int n_dhcp4_s_connection_init(NDhcp4SConnection *connection, int ifindex);
void n_dhcp4_s_connection_deinit(NDhcp4SConnection *connection);

void n_dhcp4_s_connection_get_fd(NDhcp4SConnection *connection, int *fdp);
int n_dhcp4_s_connection_dispatch_io(NDhcp4SConnection *connection, NDhcp4Incoming **messagep);

int n_dhcp4_s_connection_offer_new(NDhcp4SConnection *connection,
                                   NDhcp4Outgoing **replyp,
                                   NDhcp4Incoming *request,
                                   const struct in_addr *server_address,
                                   const struct in_addr *client_address,
                                   uint32_t lifetime);
int n_dhcp4_s_connection_ack_new(NDhcp4SConnection *connection,
                                   NDhcp4Outgoing **replyp,
                                   NDhcp4Incoming *request,
                                   const struct in_addr *server_address,
                                   const struct in_addr *client_address,
                                   uint32_t lifetime);
int n_dhcp4_s_connection_nak_new(NDhcp4SConnection *connection,
                                 NDhcp4Outgoing **replyp,
                                 NDhcp4Incoming *request,
                                 const struct in_addr *server_address);

int n_dhcp4_s_connection_send_reply(NDhcp4SConnection *connection,
                                    const struct in_addr *server_addr,
                                    NDhcp4Outgoing *reply);

/* server connection ips */

void n_dhcp4_s_connection_ip_init(NDhcp4SConnectionIp *ip, struct in_addr addr);
void n_dhcp4_s_connection_ip_deinit(NDhcp4SConnectionIp *ip);

void n_dhcp4_s_connection_ip_link(NDhcp4SConnectionIp *ip, NDhcp4SConnection *connection);
void n_dhcp4_s_connection_ip_unlink(NDhcp4SConnectionIp *ip);

/* inline helpers */

static inline void n_dhcp4_outgoing_freep(NDhcp4Outgoing **outgoing) {
        if (*outgoing)
                n_dhcp4_outgoing_free(*outgoing);
}

static inline void n_dhcp4_incoming_freep(NDhcp4Incoming **incoming) {
        if (*incoming)
                n_dhcp4_incoming_free(*incoming);
}

static inline uint64_t n_dhcp4_gettime(clockid_t clock) {
        struct timespec ts;
        int r;

        r = clock_gettime(clock, &ts);
        c_assert(r >= 0);

        return ts.tv_sec * 1000ULL * 1000ULL * 1000ULL + ts.tv_nsec;
}

void n_dhcp4_log_queue_fmt(NDhcp4LogQueue *log_queue,
                           int level,
                           const char *fmt,
                           ...) _c_printf_(3, 4);

/**
 * n_dhcp4_log() - append a logging event
 * @x_log_queue:   the logging event queue
 * @x_level:       the syslog logging level for the message.
 * @...:           the format string and arguments.
 *
 * Warning: this macro only evaluates the format arguments if the logging
 * level is enabled.
 */
#define n_dhcp4_log(x_log_queue, x_level, ...)                                 \
        do {                                                                   \
                NDhcp4LogQueue *const _log_queue = (x_log_queue);              \
                const int _level = (x_level);                                  \
                                                                               \
                if (_level <= _log_queue->log_level) {                         \
                        n_dhcp4_log_queue_fmt(_log_queue,                      \
                                              _level,                          \
                                              __VA_ARGS__);                    \
                }                                                              \
        } while (0)
