#ifndef LAB_FD_TRANSFER_H
#define LAB_FD_TRANSFER_H
#include "channel.h"

static inline int descriptor_send(int peer, int fd)
{
    char tag = 'F';
    struct iovec vector = {.iov_base = &tag, .iov_len = 1};
    union {
        struct cmsghdr alignment;
        unsigned char bytes[CMSG_SPACE(sizeof(int))];
    } control = {0};
    struct msghdr message = {.msg_iov = &vector, .msg_iovlen = 1,
                            .msg_control = control.bytes,
                            .msg_controllen = sizeof(control.bytes)};
    struct cmsghdr *header = CMSG_FIRSTHDR(&message);
    header->cmsg_level = SOL_SOCKET;
    header->cmsg_type = SCM_RIGHTS;
    header->cmsg_len = CMSG_LEN(sizeof(int));
    memcpy(CMSG_DATA(header), &fd, sizeof(fd));
    ssize_t count;
    do {
        count = sendmsg(peer, &message, MSG_NOSIGNAL);
    } while (count == -1 && errno == EINTR);
    if (count == 1)
        return 0;
    if (count >= 0)
        errno = EPROTO;
    return -1;
}

static inline int descriptor_receive(int peer)
{
    if (channel_wait(peer) == -1)
        return -1;
    char tag = 0;
    struct iovec vector = {.iov_base = &tag, .iov_len = 1};
    union {
        struct cmsghdr alignment;
        unsigned char bytes[CMSG_SPACE(4 * sizeof(int))];
    } control = {0};
    struct msghdr message = {.msg_iov = &vector, .msg_iovlen = 1,
                            .msg_control = control.bytes,
                            .msg_controllen = sizeof(control.bytes)};
    ssize_t count = recvmsg(peer, &message, MSG_CMSG_CLOEXEC | MSG_DONTWAIT);
    if (count == -1)
        return -1;
    int received = -1, descriptors = 0;
    int invalid = count != 1 || tag != 'F' ||
                  (message.msg_flags & (MSG_TRUNC | MSG_CTRUNC));
    for (struct cmsghdr *header = CMSG_FIRSTHDR(&message); header;
         header = CMSG_NXTHDR(&message, header)) {
        if (header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS ||
            header->cmsg_len < CMSG_LEN(0)) {
            invalid = 1;
            continue;
        }
        size_t bytes = header->cmsg_len - CMSG_LEN(0);
        if (bytes % sizeof(int) != 0)
            invalid = 1;
        for (size_t offset = 0; offset + sizeof(int) <= bytes;
             offset += sizeof(int)) {
            int fd;
            memcpy(&fd, CMSG_DATA(header) + offset, sizeof(fd));
            if (++descriptors == 1)
                received = fd;
            else
                close(fd);
        }
    }
    if (invalid || descriptors != 1) {
        if (received != -1)
            close(received);
        errno = EPROTO;
        return -1;
    }
    return received;
}
#endif
