#include <devif/queue_interface_backend.h>
#include <devif/backends/net/enet_devif.h>
#include <aos/aos.h>
#include <driverkit/driverkit.h>
#include <dev/imx8x/enet_dev.h>

#include <aos/ump_binding.h>
#include <aos/ump_chan.h>
#include <aos/ump_net.h>

#include <netutil/etharp.h>
#include <netutil/ip.h>
#include <netutil/icmp.h>
#include <netutil/udp.h>
#include <netutil/checksum.h>
#include <netutil/types.h>

#include "enet.h"

// https://datatracker.ietf.org/doc/html/rfc6335#section-6
#define EPHEMERAL_PORT_START 49152
#define EPHEMERAL_PORT_END 65535

extern struct enet_driver_state *st;

static void write_eth_ip_header (
    struct devq_buf *buf, void *vaddr,
    uint64_t eth_dst, uint32_t ip_dest, uint8_t proto, size_t payload_len
);

static void tx_alloc (struct tx_alloc_queue_entry *entry, tx_alloc_callback_t cb, void *cb_arg) {
    errval_t err = DEVQ_ERR_QUEUE_EMPTY;
    struct devq_buf buf;

    // If the queue is not empty, always append to the queue, so that FIFO order is maintained
    if (st->tx_alloc_queue_head == NULL) {
        err = devq_dequeue((struct devq*) st->txq, &buf.rid, &buf.offset,
            &buf.length, &buf.valid_data, &buf.valid_length,
            &buf.flags);
    }
    if (err_is_fail(err)) {
        if (st->tx_init_i < st->txq->size) {
            buf.rid = st->tx_rid;
            buf.offset = st->tx_init_i * 2048;
            buf.length = 2048;
            buf.valid_data = 0;
            buf.valid_length = 2048;
            buf.flags = 0;
            st->tx_init_i++;
        } else {
            entry->cb = cb;
            entry->cb_arg = cb_arg;
            entry->next = NULL;
            if (st->tx_alloc_queue_tail == NULL) {
                st->tx_alloc_queue_head = entry;
            } else {
                st->tx_alloc_queue_tail->next = entry;
            }
            st->tx_alloc_queue_tail = entry;
            return;
        }
    }

    struct region_entry *region_entry = enet_get_region(st->txq, buf.rid);
    assert(region_entry != NULL);
    void *vaddr = (void*)region_entry->mem.vbase + buf.offset;
    cb(cb_arg, &buf, vaddr);
}

static void tx_send (struct devq_buf *buf) {
    errval_t err;
    err = devq_enqueue((struct devq*) st->txq, buf->rid, buf->offset,
                        buf->length, buf->valid_data, buf->valid_length,
                        buf->flags);
    assert(err_is_ok(err));
}

static void rx_release (struct devq_buf *buf) {
    errval_t err;
    err = devq_enqueue((struct devq*) st->rxq, buf->rid, buf->offset,
                        buf->length, buf->valid_data, buf->valid_length,
                        buf->flags);
    assert(err_is_ok(err));
}

// ARP: https://datatracker.ietf.org/doc/html/rfc826

static void arp_send (
    struct devq_buf *buf, void *vaddr,
    uint64_t eth_dst, uint32_t ip_dest,
    uint16_t op
) {
    buf->valid_data = 0;
    buf->valid_length = ETH_HLEN + sizeof(struct arp_hdr);

    struct eth_hdr *eth_hdr = vaddr;
    eth_hdr->dst = eth_addr_wr(eth_dst);
    eth_hdr->src = eth_addr_wr(st->mac);
    eth_hdr->type = uint16_wr(ETH_TYPE_ARP);

    struct arp_hdr *arp_hdr = vaddr + ETH_HLEN;
    arp_hdr->hwtype = uint16_wr(ARP_HW_TYPE_ETH);
    arp_hdr->proto = uint16_wr(ETH_TYPE_IP);
    arp_hdr->hwlen = ETH_ADDR_LEN;
    arp_hdr->protolen = IP_ADDR_LEN;
    arp_hdr->opcode = uint16_wr(op);
    arp_hdr->eth_src = eth_addr_wr(st->mac);
    arp_hdr->ip_src = uint32_wr(st->my_ip);
    arp_hdr->eth_dst = eth_addr_wr(eth_dst);
    arp_hdr->ip_dst = uint32_wr(ip_dest);

    tx_send(buf);
}

static void arp_reply (void *cb_arg, struct devq_buf *buf, void *vaddr) {
    struct arp_tx_queue_entry *entry = &st->arp.tx_queue[st->arp.tx_queue_tail];
    assert(cb_arg == entry);

    arp_send(buf, vaddr, entry->eth_dst, entry->ip_dst, ARP_OP_REP);

    st->arp.tx_queue_tail = (st->arp.tx_queue_tail + 1) & (ARP_TX_QUEUE_LEN - 1);
    st->arp.tx_queue_len--;
}

static void arp_handle (struct devq_buf *buf, const void *vaddr, const struct eth_hdr *eth_hdr) {
    ENET_DEBUG("Received ARP packet\n");

    if (buf->valid_length < sizeof(struct arp_hdr)) {
        ENET_WARN("Received ARP packet too small\n");
        rx_release(buf);
        return;
    }
    const struct arp_hdr *arp_hdr = vaddr + buf->valid_data;
    uint16_t opcode = uint16_rd(arp_hdr->opcode);
    if (
        uint16_rd(arp_hdr->hwtype) != ARP_HW_TYPE_ETH ||
        uint16_rd(arp_hdr->proto) != ETH_TYPE_IP ||
        arp_hdr->hwlen != ETH_ADDR_LEN ||
        arp_hdr->protolen != IP_ADDR_LEN ||
        (opcode != ARP_OP_REQ && opcode != ARP_OP_REP)
    ) {
        ENET_WARN("Unknown ARP packet type\n");
        rx_release(buf);
        return;
    }

    uint32_t ip_src = uint32_rd(arp_hdr->ip_src);

    if ((st->my_ip & st->net_mask) != (ip_src & st->net_mask)) {
        ENET_WARN("Received ARP packet for address outside my network\n");
        rx_release(buf);
        return;
    }

    uint64_t eth_src = eth_addr_rd(arp_hdr->eth_src);
    uint64_t eth_old = (uintptr_t)collections_hash_find(st->arp.table, ip_src);
    // check if sender protocol address is in translation table, if so update entry
    if (eth_old != 0 && eth_src != eth_old) {
        ENET_WARN("MAC address of %d.%d.%d.%d has changed\n",
            arp_hdr->ip_src.val[0],
            arp_hdr->ip_src.val[1],
            arp_hdr->ip_src.val[2],
            arp_hdr->ip_src.val[3]
        );
        collections_hash_delete(st->arp.table, ip_src);
        collections_hash_insert(st->arp.table, ip_src, (void*)eth_src);
    }

    if (uint32_rd(arp_hdr->ip_dst) == st->my_ip) {
        ENET_DEBUG("Received ARP packet addressed to me\n");

        // if sender protocol address was not in translation table, add entry
        if (eth_old == 0) {
            collections_hash_insert(st->arp.table, ip_src, (void*)eth_src);
        }

        if (opcode == ARP_OP_REQ) {
            if (st->arp.tx_queue_len >= ARP_TX_QUEUE_LEN) {
                ENET_WARN("Too many queued ARP replies, not sending reply\n");
            } else {
                struct arp_tx_queue_entry *entry = &st->arp.tx_queue[(st->arp.tx_queue_tail + st->arp.tx_queue_len) & (ARP_TX_QUEUE_LEN - 1)];
                st->arp.tx_queue_len++;
                entry->eth_dst = eth_src;
                entry->ip_dst = ip_src;
                tx_alloc(&entry->tx_entry, arp_reply, entry);
            }
        }
    }

    rx_release(buf);
}

// ICMP: https://datatracker.ietf.org/doc/html/rfc792

static void icmp_reply (void *cb_arg, struct devq_buf *buf, void *vaddr) {
    struct icmp_echo_reply_meta *entry = cb_arg;

    write_eth_ip_header(
        buf, vaddr,
        eth_addr_rd(entry->eth_hdr->src),
        uint32_rd(entry->ip_hdr->src),
        IP_PROTO_ICMP,
        entry->rx_buf.valid_length
    );

    struct icmp_echo_hdr *icmp_echo_hdr = vaddr + ETH_HLEN + sizeof(struct ip_hdr);
    memcpy(icmp_echo_hdr, entry->icmp_echo_hdr, entry->rx_buf.valid_length);
    icmp_echo_hdr->type = ICMP_ER;

    // Incremental update: https://datatracker.ietf.org/doc/html/rfc1071
    uint32_t chksum = uint16_rd(icmp_echo_hdr->chksum);
    chksum += (ICMP_ECHO << 8) + ((ICMP_ER << 8) ^ 0x0000ffffUL);
    chksum = (chksum >> 16) + (chksum & 0x0000ffffUL);
    icmp_echo_hdr->chksum = uint16_wr(chksum);

    rx_release(&entry->rx_buf);
    simpleslab_free(&st->rx_meta_slab, entry);
    tx_send(buf);
}

static void icmp_handle (struct devq_buf *buf, const void *vaddr, const struct eth_hdr *eth_hdr, const struct ip_hdr *ip_hdr) {
    if (buf->valid_length < 4) {
        ENET_WARN("Received ICMP packet too small\n");
        rx_release(buf);
        return;
    }

    if (inet_checksum(vaddr + buf->valid_data, buf->valid_length) != 0) {
        ENET_WARN("Received ICMP packet with bad checksum\n");
        rx_release(buf);
        return;
    }

    uint8_t type = *(const uint8_t*)(vaddr + buf->valid_data);
    if (type == ICMP_ECHO) {
        if (buf->valid_length < sizeof(struct icmp_echo_hdr)) {
            ENET_WARN("Received ICMP packet too small\n");
            rx_release(buf);
            return;
        }

        const struct icmp_echo_hdr *icmp_echo_hdr = vaddr + buf->valid_data;
        ENET_DEBUG("Received ICMP echo, seq=%d\n", uint16_rd(icmp_echo_hdr->seqno));

        struct icmp_echo_reply_meta *meta = simpleslab_alloc(&st->rx_meta_slab);
        meta->rx_buf = *buf;
        meta->eth_hdr = eth_hdr;
        meta->ip_hdr = ip_hdr;
        meta->icmp_echo_hdr = icmp_echo_hdr;

        tx_alloc(&meta->tx_entry, icmp_reply, meta);
        return;
    } else {
        ENET_WARN("Received ICMP packet with unknown type %d\n", type);
    }

    rx_release(buf);
}

// UDP: https://datatracker.ietf.org/doc/html/rfc768

static void udp_recv_ump_callback (void *arg, struct ump_send_queue_entry *entry) {
    struct udp_recv *meta = arg;
    rx_release(&meta->rx_buf);
    simpleslab_free(&st->rx_meta_slab, meta);
}

static uint16_t udp_checksum (const struct ip_hdr *ip_hdr, const struct udp_hdr *udp_hdr) {
    uint16_t udp_len = uint16_rd(udp_hdr->len);
    uint32_t chksum = inet_checksum(udp_hdr, udp_len) ^ 0x0000ffffUL;
    // add pseudo header
    chksum += (ip_hdr->src.val[0] << 8) | ip_hdr->src.val[1];
    chksum += (ip_hdr->src.val[2] << 8) | ip_hdr->src.val[3];
    chksum += (ip_hdr->dest.val[0] << 8) | ip_hdr->dest.val[1];
    chksum += (ip_hdr->dest.val[2] << 8) | ip_hdr->dest.val[3];
    chksum += IP_PROTO_UDP;
    chksum += udp_len;
    chksum = (chksum >> 16) + (chksum & 0x0000ffffUL);
    chksum = (chksum >> 16) + (chksum & 0x0000ffffUL);
    return ~(uint16_t)chksum;
}

static void udp_handle (struct devq_buf *buf, const void *vaddr, const struct eth_hdr *eth_hdr, const struct ip_hdr *ip_hdr) {
    if (buf->valid_length < UDP_HLEN) {
        ENET_WARN("Received UDP packet too small\n");
        rx_release(buf);
        return;
    }
    const struct udp_hdr *udp_hdr = vaddr + buf->valid_data;
    uint16_t udp_len = uint16_rd(udp_hdr->len);
    if (udp_len < UDP_HLEN || buf->valid_length < udp_len) {
        ENET_WARN("Received UDP packet too small\n");
        rx_release(buf);
        return;
    }

    if (
        uint16_rd(udp_hdr->chksum) != 0 && // 0 means no checksum
        udp_checksum(ip_hdr, udp_hdr) != 0
    ) {
        ENET_WARN("Received UDP packet with bad checksum\n");
        rx_release(buf);
        return;
    }

    uint16_t src_port = uint16_rd(udp_hdr->src);
    uint16_t dest_port = uint16_rd(udp_hdr->dest);
    const void *payload = (const void*)udp_hdr + UDP_HLEN;
    uint16_t payload_len = udp_len - UDP_HLEN;
    ENET_DEBUG("Received UDP packet from %d to %d, len %d\n", src_port, dest_port, payload_len);


    struct ump_client *client = collections_hash_find(st->udp.listen_table, dest_port);
    if (client == NULL) {
        // TODO: send ICMP unreachable
        rx_release(buf);
    } else {
        struct udp_recv *meta = simpleslab_alloc(&st->rx_meta_slab);
        meta->rx_buf = *buf;
        meta->ump_header = (struct ump_net_in_header){
            .op = UMP_NET_EV_UDP_RECV,
            .d = { .udp_recv = {
                .src_ip = uint32_rd(ip_hdr->src),
                .dest_ip = uint32_rd(ip_hdr->dest),
                .src_port = src_port,
                .dest_port = dest_port,
            } }
        };
        ump_send(client->send_chan, &meta->ump_entry,
            sizeof(struct ump_net_in_header), &meta->ump_header,
            payload_len, payload,
            udp_recv_ump_callback, meta);
        return;
    }
}

// IP: https://datatracker.ietf.org/doc/html/rfc791#section-3.1

static void ip_handle (struct devq_buf *buf, const void *vaddr, const struct eth_hdr *eth_hdr) {
    if (buf->valid_length < sizeof(struct ip_hdr)) {
        ENET_WARN("Received IP packet too small\n");
        rx_release(buf);
        return;
    }
    const struct ip_hdr *ip_hdr = vaddr + buf->valid_data;
    uint16_t ip_len = uint16_rd(ip_hdr->len);
    uint16_t header_len = IPH_HL(ip_hdr) * 4;
    if (
        IPH_V(ip_hdr) != 4 ||
        header_len < 20 ||
        header_len > ip_len ||
        ip_len > buf->valid_length
    ) {
        ENET_WARN("Received bad IP packet\n");
        rx_release(buf);
        return;
    }

    if (inet_checksum(ip_hdr, header_len) != 0) {
        ENET_WARN("Received IP packet with bad checksum\n");
        rx_release(buf);
        return;
    }

    uint16_t offset_flags = uint16_rd(ip_hdr->offset);
    if (
        (offset_flags & IP_MF) != 0 ||
        (offset_flags & IP_OFFMASK) != 0
    ) {
        ENET_WARN("Received IP fragment, dropping\n");
        rx_release(buf);
        return;
    }

    ENET_DEBUG("Received IP packet from %d.%d.%d.%d to %d.%d.%d.%d, proto %d\n",
        ip_hdr->src.val[0],
        ip_hdr->src.val[1],
        ip_hdr->src.val[2],
        ip_hdr->src.val[3],
        ip_hdr->dest.val[0],
        ip_hdr->dest.val[1],
        ip_hdr->dest.val[2],
        ip_hdr->dest.val[3],
        ip_hdr->proto
    );

    buf->valid_data += header_len;
    buf->valid_length = ip_len - header_len;

    if (uint32_rd(ip_hdr->dest) == st->my_ip) {
        if (ip_hdr->proto == IP_PROTO_ICMP) {
            icmp_handle(buf, vaddr, eth_hdr, ip_hdr);
        } else if (ip_hdr->proto == IP_PROTO_UDP) {
            udp_handle(buf, vaddr, eth_hdr, ip_hdr);
        } else {
            ENET_WARN("Received IP packet with unknown protocol %d\n", ip_hdr->proto);
            rx_release(buf);
        }
    } else {
        ENET_WARN("Received IP packet for someone else\n");
        rx_release(buf);
    }
}

static void write_eth_ip_header (
    struct devq_buf *buf, void *vaddr,
    uint64_t eth_dst, uint32_t ip_dest, uint8_t proto, size_t payload_len
) {
    buf->valid_data = 0;
    buf->valid_length = ETH_HLEN + sizeof(struct ip_hdr) + payload_len;
    assert(buf->valid_length <= ETH_HLEN + 1500);

    struct eth_hdr *eth_hdr = vaddr;
    eth_hdr->dst = eth_addr_wr(eth_dst);
    eth_hdr->src = eth_addr_wr(st->mac);
    eth_hdr->type = uint16_wr(ETH_TYPE_IP);

    struct ip_hdr *ip_hdr = vaddr + ETH_HLEN;
    IPH_VHL_SET(ip_hdr, 4, 5);
    ip_hdr->tos = 0;
    ip_hdr->len = uint16_wr(sizeof(struct ip_hdr) + payload_len);
    ip_hdr->id = uint16_wr(st->ip.next_id++);
    ip_hdr->offset = uint16_wr(IP_DF);
    ip_hdr->ttl = 64;
    ip_hdr->proto = proto;
    ip_hdr->chksum = uint16_wr(0);
    ip_hdr->src = uint32_wr(st->my_ip);
    ip_hdr->dest = uint32_wr(ip_dest);
    ip_hdr->chksum = uint16_wr(inet_checksum(ip_hdr, 20));
}

// Ethernet

static void rx_handle (struct devq_buf *buf) {
    struct region_entry *entry = enet_get_region(st->rxq, buf->rid);
    assert(entry != NULL);
    const void *vaddr = (void*)entry->mem.vbase + buf->offset;
    const void *eth_vaddr = vaddr + buf->valid_data;

    #if defined(ENET_DEBUG_OPTION)
    debug_printf("Received Packet of size %lu:", buf->valid_length);
    for (size_t i = 0; i < buf->valid_length; i++) {
        printf(" %02x", ((uint8_t*)eth_vaddr)[i]);
    }
    printf("\n");
    #endif

    if (buf->valid_length < ETH_HLEN) {
        ENET_WARN("Ethernet packet too small\n");
        rx_release(buf);
        return;
    }

    const struct eth_hdr *eth_hdr = eth_vaddr;
    uint16_t type = uint16_rd(eth_hdr->type);

    buf->valid_data += ETH_HLEN;
    buf->valid_length -= ETH_HLEN;

    if (type == ETH_TYPE_ARP) {
        arp_handle(buf, vaddr, eth_hdr);
    } else if (type == ETH_TYPE_IP) {
        ip_handle(buf, vaddr, eth_hdr);
    } else {
        ENET_WARN("Received packet of unknown type: %04x\n", type);
        rx_release(buf);
    }
}

// UMP

static void netump_header_handle (void *arg, size_t header_size, void *header_raw, size_t payload_size);

static void netump_next (struct ump_client *client) {
    if (client->ump_reply_slab.free != 0) {
        ump_recv_header(client->recv_chan, netump_header_handle, client);
    } else {
        client->blocked_on_reply_slab = true;
    }
}

static void netump_reply_callback (void *arg, struct ump_send_queue_entry *entry) {
    struct ump_reply_buf *reply = arg;
    struct ump_client *client = reply->client;
    simpleslab_free(&client->ump_reply_slab, reply);
    if (client->blocked_on_reply_slab) {
        client->blocked_on_reply_slab = false;
        netump_next(client);
    }
}

static void netump_payload_ignore (void *arg, size_t payload_size, void *payload) {
    netump_next(arg);
}

static void netump_send_reply (struct ump_reply_buf *reply, errval_t err) {
    reply->ump_header.ret_err = err;
    ump_send(reply->client->send_chan, &reply->ump_entry,
        sizeof(struct ump_net_in_header), &reply->ump_header,
        0, NULL,
        netump_reply_callback, reply);
}

static void netump_udp_tx_payload (void *arg, size_t payload_size, void *payload) {
    struct ump_client *client = arg;

    struct udp_hdr *udp_hdr = (void*)client->tx_ip_hdr + sizeof(struct ip_hdr);
    uint16_t checksum = udp_checksum(client->tx_ip_hdr, udp_hdr);
    if (checksum == 0) checksum = 0xffff;
    udp_hdr->chksum = uint16_wr(checksum);

    tx_send(&client->tx_buf);
    netump_next(client);
}

static void netump_udp_tx_allocated (void *arg, struct devq_buf *buf, void *vaddr) {
    struct ump_reply_buf *reply = arg;
    struct ump_client *client = reply->client;
    struct ump_net_out_header *header = client->recv_chan->header;
    size_t payload_size = client->recv_chan->next_payload_size;

    uint64_t eth_dest = (uintptr_t)collections_hash_find(st->arp.table, header->d.udp_send.dest_ip);
    if (eth_dest == 0) {
        ENET_WARN("IP not in ARP table, dropping packet and sending ARP request\n");
        arp_send(buf, vaddr, 0xffffffffffff,
                 header->d.udp_send.dest_ip, ARP_OP_REQ);
        netump_send_reply(reply, LIB_ERR_NET_ARP_MISS);
        ump_recv_payload(client->recv_chan, NULL, netump_payload_ignore, client);
        return;
    }
    write_eth_ip_header(
        buf, vaddr,
        eth_dest,
        header->d.udp_send.dest_ip,
        IP_PROTO_UDP,
        UDP_HLEN + payload_size
    );
    struct udp_hdr *udp_hdr = vaddr + ETH_HLEN + sizeof(struct ip_hdr);
    udp_hdr->src = uint16_wr(header->d.udp_send.src_port);
    udp_hdr->dest = uint16_wr(header->d.udp_send.dest_port);
    udp_hdr->len = uint16_wr(UDP_HLEN + payload_size);
    udp_hdr->chksum = uint16_wr(0);
    void *payload = (void*)udp_hdr + UDP_HLEN;
    client->tx_buf = *buf;
    client->tx_ip_hdr = vaddr + ETH_HLEN;

    netump_send_reply(reply, SYS_ERR_OK);
    ump_recv_payload(client->recv_chan, payload, netump_udp_tx_payload, client);
}

static void netump_header_handle (void *arg, size_t header_size, void *header_raw, size_t payload_size) {
    struct ump_client *client = arg;
    struct ump_net_out_header *header = header_raw;
    errval_t err;

    struct ump_reply_buf *reply = simpleslab_alloc(&client->ump_reply_slab);
    assert(reply != NULL);
    reply->client = client;
    reply->ump_header.op = header->op;

    if (header->op == UMP_NET_OP_UDP_SEND) {
        if (payload_size > 1500 - sizeof(struct ip_hdr) - UDP_HLEN) {
            err = LIB_ERR_NET_PACKET_TOO_BIG;
        } else {
            tx_alloc(&client->tx_entry, netump_udp_tx_allocated, reply);
            return;
        }
    } else if (header->op == UMP_NET_OP_UDP_LISTEN) {
        uint16_t port = header->d.udp_listen.dest_port;
        if (port == 0) {
            // allocate an ephemeral port
            uint16_t first = st->udp.next_ephemeral_port;
            port = first;
            while (true) {
                uint16_t next_port;
                if (port == EPHEMERAL_PORT_END) {
                    next_port = EPHEMERAL_PORT_START;
                } else {
                    next_port = port + 1;
                }
                if (collections_hash_find(st->udp.listen_table, port) == NULL) {
                    st->udp.next_ephemeral_port = next_port;
                    break;
                }
                port = next_port;
                if (port == first) {
                    port = 0;
                    break;
                }
            }
        }
        if (port == 0) {
            err = LIB_ERR_NET_ALLOC_PORT;
        } else if (collections_hash_find(st->udp.listen_table, port) != NULL) {
            err = LIB_ERR_NET_PORT_IN_USE;
        } else {
            ENET_DEBUG("Listening on UDP port %d\n", port);
            reply->ump_header.d.udp_listen.dest_port = port;
            collections_hash_insert(st->udp.listen_table, port, client);
            err = SYS_ERR_OK;
        }
    } else if (header->op == UMP_NET_OP_UDP_LISTEN_STOP) {
        uint16_t port = header->d.udp_listen_stop.dest_port;
        if (collections_hash_find(st->udp.listen_table, port) != client) {
            err = LIB_ERR_NET_NOT_LISTENING;
        } else {
            ENET_DEBUG("Stopped listening on UDP port %d\n", port);
            collections_hash_delete(st->udp.listen_table, port);
            err = SYS_ERR_OK;
        }
    } else {
        debug_printf("Error: unknown UMP op\n");
        simpleslab_free(&client->ump_reply_slab, reply);
        ump_recv_payload(client->recv_chan, NULL, netump_payload_ignore, client);
        return;
    }

    netump_send_reply(reply, err);
    ump_recv_payload(client->recv_chan, NULL, netump_payload_ignore, client);
}

static errval_t netump_connect (void *arg, struct capref cap) {
    errval_t err;

    ENET_DEBUG("Incoming UMP connection\n");

    struct ump_client *client = malloc(sizeof(struct ump_client));
    if (client == NULL) return LIB_ERR_MALLOC_FAIL;

    err = ump_chan_init(UMP_ROLE_SERVER, &client->send_chan, &client->recv_chan,
        sizeof(struct ump_net_out_header), cap, get_default_waitset());
    if (err_is_fail(err)) return err;

    simpleslab_init(&client->ump_reply_slab, sizeof(struct ump_reply_buf),
        &client->ump_reply_slab_buf, sizeof(client->ump_reply_slab_buf));
    client->blocked_on_reply_slab = false;

    netump_next(client);

    return SYS_ERR_OK;
}

void enet_loop (void) {
    errval_t err;

    st->arp.tx_queue_len = 0;
    st->arp.tx_queue_tail = 0;
    collections_hash_create(&st->arp.table, NULL);

    st->ip.next_id = 0;

    collections_hash_create_with_buckets(&st->udp.listen_table, 100, NULL);
    st->udp.next_ephemeral_port = EPHEMERAL_PORT_START;

    simpleslab_init(&st->rx_meta_slab, sizeof(union rx_meta_slab_data),
        &st->rx_meta_slab_buf, sizeof(st->rx_meta_slab_buf));

    struct ump_binding_server server;
    err = ump_binding_register(&server, UMP_SERVER_NET, netump_connect, NULL);
    if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to register UMP server");

    struct devq_buf buf;
    struct waitset *default_ws = get_default_waitset();
    while (true) {
        bool made_progress = false;

        err = event_dispatch_non_block(default_ws);
        if (err_is_fail(err) && err != LIB_ERR_NO_EVENT) {
            DEBUG_ERR(err, "in event_dispatch");
            abort();
        }
        // TODO: set made_progress if we handled an event. Currently,
        // UMP always registers a callback, so we would always see an event.
        // See TODO in poll_channels_disabled.

        err = devq_dequeue((struct devq*) st->rxq, &buf.rid, &buf.offset,
                           &buf.length, &buf.valid_data, &buf.valid_length,
                           &buf.flags);
        if (err_is_ok(err)) {
            made_progress = true;
            rx_handle(&buf);
        }

        if (st->tx_alloc_queue_head != NULL) {
            err = devq_dequeue((struct devq*) st->txq, &buf.rid, &buf.offset,
                               &buf.length, &buf.valid_data, &buf.valid_length,
                               &buf.flags);
            if (err_is_ok(err)) {
                made_progress = true;
                struct tx_alloc_queue_entry *entry = st->tx_alloc_queue_head;
                st->tx_alloc_queue_head = entry->next;
                if (entry->next == NULL) {
                    st->tx_alloc_queue_tail = NULL;
                }
                struct region_entry *region_entry = enet_get_region(st->txq, buf.rid);
                assert(region_entry != NULL);
                void *vaddr = (void*)region_entry->mem.vbase + buf.offset;
                entry->cb(entry->cb_arg, &buf, vaddr);
            }
        }

        if (!made_progress) {
            thread_yield();
        }
    }
}
