/*
 *  Hamlib Interface - network communication low-level support
 *  Copyright (c) 2021-2023 by Mikael Nousiainen
 *  Copyright (c) 2000-2012 by Stephane Fillod
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/**
 * \addtogroup rig_internal
 * @{
 */

/**
 * \brief Network port IO
 * \file network.c
 */

/* Forcing WINVER in MinGW yanks in getaddrinfo(), but locks out Win95/Win98 */
/* #define WINVER 0x0501 */

#include "hamlib/config.h"

#include <stdlib.h>
#include <stdio.h>   /* Standard input/output definitions */
#include <string.h>  /* String function definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <sys/types.h>
#include <signal.h>
#include <pthread.h>

#ifdef HAVE_NETINET_IN_H
#  include <netinet/in.h>
#endif

#if HAVE_NETDB_H
#  include <netdb.h>
#endif

#ifdef HAVE_ARPA_INET_H
#  include <arpa/inet.h>
#endif

#if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_SYS_IOCTL_H)
#  include <sys/socket.h>
#  include <sys/ioctl.h>
#elif HAVE_WS2TCPIP_H
#undef _WIN32_WINNT
// We need inet_pton to get defined  and 0x0600 does it
// Eventually we should be able to get rid of this hack
#define _WIN32_WINNT 0x0600
#  include <ws2tcpip.h>
#undef _WIN32_WINNT
// Then we'll go back to Server 2003
#define _WIN32_WINNT 0x0502
#  if defined(HAVE_WSPIAPI_H)
#    include <wspiapi.h>
#  endif
#endif

#include "hamlib/rig.h"
#include "hamlib/port.h"
#include "hamlib/rig_state.h"
#include "network.h"
#include "misc.h"
#include "asyncpipe.h"
#include "snapshot_data.h"

#ifdef HAVE_WINDOWS_H
// cppcheck-suppress missingInclude
#include "io.h"
#endif

#ifdef __MINGW32__
#include <winsock2.h>
#include <windows.h>
#include <iphlpapi.h>
static int wsstarted;
static int is_networked(char *address, int address_length);
#endif

//! @cond Doxygen_Suppress
#define NET_BUFFER_SIZE 8192
//! @endcond

#define MULTICAST_PUBLISHER_DATA_PACKET_TYPE_POLL       0x01
#define MULTICAST_PUBLISHER_DATA_PACKET_TYPE_TRANSCEIVE 0x02
#define MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM   0x03

#pragma pack(push,1)
typedef struct multicast_publisher_data_packet_s
{
    uint8_t type;
    uint8_t padding;
    uint16_t data_length;
} __attribute__((packed)) multicast_publisher_data_packet;
#pragma pack(pop)

typedef struct multicast_publisher_args_s
{
    RIG *rig;
    int socket_fd;
    const char *multicast_addr;
    int multicast_port;

#if defined(WIN32) && defined(HAVE_WINDOWS_H)
    hamlib_async_pipe_t *data_pipe;
#else
    int data_write_fd;
    int data_read_fd;
#endif

    pthread_mutex_t write_lock;
} multicast_publisher_args;

typedef struct multicast_publisher_priv_data_s
{
    pthread_t thread_id;
    multicast_publisher_args args;
} multicast_publisher_priv_data;

typedef struct multicast_receiver_args_s
{
    RIG *rig;
    int socket_fd;
    const char *multicast_addr;
    int multicast_port;
} multicast_receiver_args;

typedef struct multicast_receiver_priv_data_s
{
    pthread_t thread_id;
    multicast_receiver_args args;
} multicast_receiver_priv_data;

static void handle_error(enum rig_debug_level_e lvl, const char *msg)
{
    int e;
#ifdef __MINGW32__
    LPVOID lpMsgBuf;

    lpMsgBuf = (LPVOID)"Unknown error";
    e = WSAGetLastError();

    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
                      | FORMAT_MESSAGE_FROM_SYSTEM
                      | FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL,
                      e,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      // Default language
                      (LPTSTR)&lpMsgBuf,
                      0,
                      NULL))
    {
        rig_debug(lvl, "%s: Network error %d: %s\n", msg, e, (char *)lpMsgBuf);
        LocalFree(lpMsgBuf);
    }
    else
    {
        rig_debug(lvl, "%s: Network error %d\n", msg, e);
    }

#else
    e = errno;
    rig_debug(lvl, "%s: Network error %d: %s\n", msg, e, strerror(e));
#endif
}

#define TRACE rig_debug(RIG_DEBUG_ERR, "TRACE %s(%d)\n", __func__,__LINE__);

static int network_init()
{
    int retval = -RIG_EINTERNAL;
#ifdef __MINGW32__
    WSADATA wsadata;

    if (wsstarted == 0)
    {
        retval = WSAStartup(MAKEWORD(1, 1), &wsadata);

        if (retval == 0)
        {
            wsstarted = 1;
            rig_debug(RIG_DEBUG_VERBOSE, "%s: WSAStartup OK\n", __func__);
        }
        else
        {
            rig_debug(RIG_DEBUG_ERR, "%s: error creating socket, WSAStartup ret=%d\n",
                      __func__, retval);
            return (-RIG_EIO);
        }
    }
    else // already started
    {
        retval = RIG_OK;
    }

#else
    retval = RIG_OK;

#endif
    return retval;
}

/**
 * \brief Open network port using STATE(rig) data
 *
 * Open network port using STATE(rig) data.
 * NB: The signal PIPE will be ignored for the whole application.
 *
 * \param rp Port data structure (must spec port id eg hostname:port)
 * \param default_port Default network socket port
 * \return RIG_OK or < 0 if error
 */
int network_open(hamlib_port_t *rp, int default_port)
{
    int fd;             /* File descriptor for the port */
    int status;
    struct addrinfo hints, *res, *saved_res;
    struct in6_addr serveraddr;
    struct sockaddr_in client;
    char hoststr[256], portstr[6] = "";

#ifdef __MINGW32__
    status = network_init();

    if (status != RIG_OK) { return (status); }

#endif

    if (!rp)
    {
        return (-RIG_EINVAL);
    }

    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = NI_NUMERICSERV;
    hints.ai_family = AF_UNSPEC;

    if (rp->type.rig == RIG_PORT_UDP_NETWORK)
    {
        hints.ai_socktype = SOCK_DGRAM;
        rig_debug(RIG_DEBUG_VERBOSE, "%s: UDP connect\n", __func__);
    }
    else
    {
        hints.ai_socktype = SOCK_STREAM;
        rig_debug(RIG_DEBUG_VERBOSE, "%s: TCP connect\n", __func__);
    }

    if (rp->pathname[0] == ':' && rp->pathname[1] != ':')
    {
        SNPRINTF(portstr, sizeof(portstr) - 1, "%s", rp->pathname + 1);
    }
    else
    {
        if (strlen(rp->pathname))
        {
            status = parse_hoststr(rp->pathname, sizeof(rp->pathname), hoststr, portstr);

            if (status != RIG_OK) { return (status); }

            rig_debug(RIG_DEBUG_TRACE, "%s: hoststr=%s, portstr=%s\n", __func__, hoststr,
                      portstr);

        }

        if (strlen(portstr) == 0)
        {
            SNPRINTF(portstr, sizeof(portstr), "%d", default_port);
        }
    }

    status = inet_pton(AF_INET, hoststr, &serveraddr);

    if (status == 1) /* valid IPv4 address */
    {
        hints.ai_family = AF_INET;
        hints.ai_flags |= AI_NUMERICHOST;
    }
    else
    {
        status = inet_pton(AF_INET6, hoststr, &serveraddr);

        if (status == 1) /* valid IPv6 address */
        {
            hints.ai_family = AF_INET6;
            hints.ai_flags |= AI_NUMERICHOST;
        }
    }

    status = getaddrinfo(hoststr, portstr, &hints, &res);

    if (status == 0 && res->ai_family == AF_INET6)
    {
        rig_debug(RIG_DEBUG_TRACE, "%s: Using IPV6\n", __func__);
        //inet_pton(AF_INET6, hoststr, &h_addr.sin6_addr);
    }

    if (status != 0)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: cannot get host \"%s\": %s\n",
                  __func__,
                  rp->pathname,
                  gai_strerror(status));
        return (-RIG_ECONF);
    }

    saved_res = res;

    /* we don't want a signal when connection get broken */
#ifdef SIGPIPE
    signal(SIGPIPE, SIG_IGN);
#endif

    do
    {
        char msg[1024];
        fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

        if (fd < 0)
        {
            handle_error(RIG_DEBUG_ERR, "socket");
            freeaddrinfo(saved_res);
            return (-RIG_EIO);
        }

        if (connect(fd, res->ai_addr, res->ai_addrlen) == 0)
        {
            break;
        }

        SNPRINTF(msg, sizeof(msg), "connect to %s failed, (trying next interface)",
                 rp->pathname);
        handle_error(RIG_DEBUG_WARN, msg);

#ifdef __MINGW32__
        closesocket(fd);
#else
        close(fd);
#endif
    }
    while ((res = res->ai_next) != NULL);

    freeaddrinfo(saved_res);

    if (NULL == res)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: failed to connect to %s\n",
                  __func__,
                  rp->pathname);
        return (-RIG_EIO);
    }

    rp->fd = fd;

    socklen_t clientLen = sizeof(client);
    getsockname(rp->fd, (struct sockaddr *)&client, &clientLen);
    rig_debug(RIG_DEBUG_TRACE, "%s: client port=%d\n", __func__, client.sin_port);
    rp->client_port = client.sin_port;

    return (RIG_OK);
}


// flush and keep what gets flushed based on stopset
// Used by SmartSDR backend for example
// return # of bytes read
int network_flush2(hamlib_port_t *rp, unsigned char *stopset, char *buf,
                   int buf_len)
{
#ifdef __MINGW32__
    ULONG len;
#else
    uint len;
#endif

#ifdef __MINGW32__
    int ret = ioctlsocket(rp->fd, FIONREAD, &len);
#else
    int ret = ioctl(rp->fd, FIONREAD, &len);
#endif

    if (ret != 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: ioctl err '%s'\n", __func__, strerror(errno));
        return 0;
    }

    if (len > 0)
    {
        buf[0] = 0;

        if (len > buf_len) { len = buf_len - 1; }

        read_string(rp, (unsigned char *)buf, len + 1, (char *)stopset, 1, 0, 1);
    }

    return len;
}
/**
 * \brief Clears any data in the read buffer of the socket
 *
 * \param rp Port data structure
 */
void network_flush(hamlib_port_t *rp)
{
#ifdef __MINGW32__
    ULONG len;
#else
    uint len;
#endif

    char buffer[NET_BUFFER_SIZE] = { 0 };

    rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    for (;;)
    {
        int ret;
        len = 0;
#ifdef __MINGW32__
        ret = ioctlsocket(rp->fd, FIONREAD, &len);
#else
        ret = ioctl(rp->fd, FIONREAD, &len);
#endif

        if (ret != 0)
        {
            rig_debug(RIG_DEBUG_ERR, "%s: ioctl err '%s'\n", __func__, strerror(errno));
            break;
        }

        if (len > 0)
        {
            int len_read = 0;
            rig_debug(RIG_DEBUG_WARN,
                      "%s: network data clear d: ret=%d, len=%d, '%s'\n",
                      __func__,
                      ret, (int)len, buffer);
            len_read = recv(rp->fd, buffer, len < NET_BUFFER_SIZE ? len : NET_BUFFER_SIZE,
                            0);

            if (len_read < 0)   // -1 indicates error occurred
            {
                rig_debug(RIG_DEBUG_ERR, "%s: read error '%s'\n", __func__, strerror(errno));
                break;
            }

            rig_debug(RIG_DEBUG_WARN,
                      "%s: network data cleared: ret=%d, len_read=%d/0x%x\n",
                      __func__,
                      ret, len_read, len_read);
            dump_hex((unsigned char *)buffer, len_read);
        }
        else
        {
            break;
        }
    }
}


//! @cond Doxygen_Suppress
int network_close(hamlib_port_t *rp)
{
    int ret = 0;

    if (rp->fd > 0)
    {
#ifdef __MINGW32__
        ret = closesocket(rp->fd);
#else
        ret = close(rp->fd);
#endif
        rig_debug(RIG_DEBUG_VERBOSE, "%s: close socket ret=%d\n", __func__, ret);
        rp->fd = 0;
    }

#ifdef __MINGW32__

    if (wsstarted)
    {
        ret = WSACleanup();
        rig_debug(RIG_DEBUG_VERBOSE, "%s: WSACleanup ret=%d\n", __func__, ret);
        wsstarted = 0;
    }

#endif
    return (ret);
}
//! @endcond

//TODO See defn in rig.c
//extern void sync_callback(int lock);

//! @cond Doxygen_Suppress

#define MULTICAST_DATA_PIPE_TIMEOUT_MILLIS 1000
#define MULTICAST_DATA_PIPE_TIMEOUT_USEC 100000

#if defined(WIN32) && defined(HAVE_WINDOWS_H)

static int multicast_publisher_create_data_pipe(multicast_publisher_priv_data
        *mcast_publisher_priv)
{
    int status;

    status = async_pipe_create(&mcast_publisher_priv->args.data_pipe,
                               PIPE_BUFFER_SIZE_DEFAULT, MULTICAST_DATA_PIPE_TIMEOUT_MILLIS);

    if (status != 0)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: multicast publisher data pipe creation failed with status=%d, err=%s\n",
                  __func__,
                  status, strerror(errno));
        return (-RIG_EINTERNAL);
    }

    return (RIG_OK);
}

static void multicast_publisher_close_data_pipe(multicast_publisher_priv_data
        *mcast_publisher_priv)
{
    if (mcast_publisher_priv->args.data_pipe != NULL)
    {
        async_pipe_close(mcast_publisher_priv->args.data_pipe);
        mcast_publisher_priv->args.data_pipe = NULL;
    }
}

static int multicast_publisher_write_data(multicast_publisher_args
        *mcast_publisher_args, size_t length, const unsigned char *data)
{
    ssize_t result;

    result = async_pipe_write(mcast_publisher_args->data_pipe, data, length,
                              MULTICAST_DATA_PIPE_TIMEOUT_MILLIS);

    if (result < 0)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: error writing to multicast publisher data pipe, result=%d\n", __func__,
                  (int)result);
        return (-RIG_EIO);
    }

    if (result != length)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: could not write to multicast publisher data pipe, expected %d bytes, wrote %d bytes\n",
                  __func__, (int)length, (int)result);
        return (-RIG_EIO);
    }

    return (RIG_OK);
}

static int multicast_publisher_read_data(multicast_publisher_args
        const *mcast_publisher_args, size_t length, unsigned char *data)
{
    ssize_t result;

    result = async_pipe_wait_for_data(mcast_publisher_args->data_pipe, 100);

    if (result < 0)
    {
        // Timeout is expected when there is no data
        if (result != -RIG_ETIMEOUT)
        {
            rig_debug(RIG_DEBUG_ERR,
                      "%s: error waiting for multicast publisher data, result=%ld\n", __func__,
                      (long) result);
        }

        return (result);
    }

    result = async_pipe_read(mcast_publisher_args->data_pipe, data, length,
                             MULTICAST_DATA_PIPE_TIMEOUT_MILLIS);

    if (result < 0)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: error reading multicast publisher data, result=%ld\n", __func__,
                  (long) result);
        return (-RIG_EIO);
    }

    if (result != length)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: could not read from multicast publisher data pipe, expected %ld bytes, read %ld bytes\n",
                  __func__, (long) length, (long) result);
        return (-RIG_EIO);
    }

    return (RIG_OK);
}

#else

static int multicast_publisher_create_data_pipe(multicast_publisher_priv_data
        *mcast_publisher_priv)
{
    int data_pipe_fds[2];
    int status;

    status = pipe(data_pipe_fds);

    if (status != 0)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: multicast publisher data pipe creation failed with status=%d, err=%s\n",
                  __func__,
                  status, strerror(errno));
        return (-RIG_EINTERNAL);
    }

    int flags = fcntl(data_pipe_fds[0], F_GETFD);
    flags |= O_NONBLOCK;

    if (fcntl(data_pipe_fds[0], F_SETFD, flags))
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error setting O_NONBLOCK on pipe=%s\n", __func__,
                  strerror(errno));
    }

    mcast_publisher_priv->args.data_read_fd = data_pipe_fds[0];
    mcast_publisher_priv->args.data_write_fd = data_pipe_fds[1];

    return (RIG_OK);
}

static void multicast_publisher_close_data_pipe(multicast_publisher_priv_data
        *mcast_publisher_priv)
{
    if (mcast_publisher_priv->args.data_read_fd != -1)
    {
        close(mcast_publisher_priv->args.data_read_fd);
        mcast_publisher_priv->args.data_read_fd = -1;
    }

    if (mcast_publisher_priv->args.data_write_fd != -1)
    {
        close(mcast_publisher_priv->args.data_write_fd);
        mcast_publisher_priv->args.data_write_fd = -1;
    }
}

static int multicast_publisher_write_data(const multicast_publisher_args
        *mcast_publisher_args, size_t length, const unsigned char *data)
{
    int fd = mcast_publisher_args->data_write_fd;
    ssize_t result;

    result = write(fd, data, length);

    if (result < 0)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: error writing to multicast publisher data pipe, result=%d, err=%s\n",
                  __func__,
                  (int)result, strerror(errno));
        return (-RIG_EIO);
    }

    if (result != length)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s: could not write to multicast publisher data pipe, expected %ld bytes, wrote %ld bytes\n",
                  __func__, (long) length, (long) result);
        return (-RIG_EIO);
    }

    return (RIG_OK);
}

static int multicast_publisher_read_data(const multicast_publisher_args
        *mcast_publisher_args, size_t length, unsigned char *data)
{
    int fd = mcast_publisher_args->data_read_fd;
    fd_set rfds, efds;
    struct timeval timeout;
    ssize_t result;
    int retval;
    int retries = 2;
    size_t offset = 0;
    size_t length_left = length;

retry:
    timeout.tv_sec = 0;
    timeout.tv_usec = MULTICAST_DATA_PIPE_TIMEOUT_USEC;

    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    efds = rfds;

    retval = select(fd + 1, &rfds, NULL, &efds, &timeout);

    if (retval == 0)
    {
        return (-RIG_ETIMEOUT);
    }

    if (retval < 0)
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s(): select() failed when reading multicast publisher data: %s\n",
                  __func__,
                  strerror(errno));

        return -RIG_EIO;
    }

    if (FD_ISSET(fd, &efds))
    {
        rig_debug(RIG_DEBUG_ERR,
                  "%s(): fd error when reading multicast publisher data: %s\n",
                  __func__,
                  strerror(errno));

        return -RIG_EIO;
    }

    result = read(fd, data + offset, length_left);

    if (result < 0)
    {
        if (errno == EAGAIN)
        {
            return (-RIG_ETIMEOUT);
        }

        rig_debug(RIG_DEBUG_ERR, "%s: error reading multicast publisher data: %s\n",
                  __func__, strerror(errno));
        return (-RIG_EIO);
    }

    offset += result;
    length_left -= result;

    if (length_left > 0)
    {
        if (retries > 0)
        {
            // Execution of this routine may time out between writes to pipe, retry to get more data
            rig_debug(RIG_DEBUG_VERBOSE,
                      "%s: could not read from multicast publisher data pipe, expected %ld bytes, read %ld bytes, retrying...\n",
                      __func__, (long) length, (long) offset);
            retries--;
            goto retry;
        }

        rig_debug(RIG_DEBUG_ERR,
                  "%s: could not read from multicast publisher data pipe even after retries, expected %ld bytes, read %ld bytes\n",
                  __func__, (long) length, (long) offset);

        return (-RIG_EIO);
    }

    return (RIG_OK);
}

#endif

static void multicast_publisher_write_lock(RIG *rig)
{
    struct rig_state *rs = STATE(rig);
    multicast_publisher_priv_data *priv_data = (multicast_publisher_priv_data *)
            rs->multicast_publisher_priv_data;
    pthread_mutex_lock(&priv_data->args.write_lock);
}

static void multicast_publisher_write_unlock(RIG *rig)
{
    struct rig_state *rs = STATE(rig);
    multicast_publisher_priv_data *priv_data = (multicast_publisher_priv_data *)
            rs->multicast_publisher_priv_data;
    pthread_mutex_unlock(&priv_data->args.write_lock);
}

static int multicast_publisher_write_packet_header(RIG *rig,
        multicast_publisher_data_packet *packet)
{
    struct rig_state *rs = STATE(rig);
    multicast_publisher_priv_data *mcast_publisher_priv;
    multicast_publisher_args *mcast_publisher_args;
    ssize_t result;

    if (rs->multicast_publisher_priv_data == NULL)
    {
        // Silently ignore if multicast publisher is not enabled
        return RIG_OK;
    }

    mcast_publisher_priv = (multicast_publisher_priv_data *)
                           rs->multicast_publisher_priv_data;
    mcast_publisher_args = &mcast_publisher_priv->args;

    result = multicast_publisher_write_data(
                 mcast_publisher_args, sizeof(multicast_publisher_data_packet),
                 (unsigned char *) packet);

    if (result != RIG_OK)
    {
        return result;
    }

    return RIG_OK;
}

int network_publish_rig_poll_data(RIG *rig)
{
    const struct rig_state *rs = STATE(rig);
    int result;
    multicast_publisher_data_packet packet =
    {
        .type = MULTICAST_PUBLISHER_DATA_PACKET_TYPE_POLL,
        .padding = 0,
        .data_length = 0,
    };

    if (rs->multicast_publisher_priv_data == NULL)
    {
        // Silently ignore call if multicast publisher is not enabled
        return RIG_OK;
    }

    multicast_publisher_write_lock(rig);
    result = multicast_publisher_write_packet_header(rig, &packet);
    multicast_publisher_write_unlock(rig);
    return result;
}

int network_publish_rig_transceive_data(RIG *rig)
{
    const struct rig_state *rs = STATE(rig);
    int result;
    multicast_publisher_data_packet packet =
    {
        .type = MULTICAST_PUBLISHER_DATA_PACKET_TYPE_TRANSCEIVE,
        .padding = 0,
        .data_length = 0,
    };

    if (rs->multicast_publisher_priv_data == NULL)
    {
        // Silently ignore call if multicast publisher is not enabled
        return RIG_OK;
    }

    multicast_publisher_write_lock(rig);
    result = multicast_publisher_write_packet_header(rig, &packet);
    multicast_publisher_write_unlock(rig);
    return result;
}

int network_publish_rig_spectrum_data(RIG *rig, struct rig_spectrum_line *line)
{
    int result;
    struct rig_state *rs = STATE(rig);
    multicast_publisher_priv_data *mcast_publisher_priv;
    multicast_publisher_args *mcast_publisher_args;
    multicast_publisher_data_packet packet =
    {
        .type = MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM,
        .padding = 0,
        .data_length = sizeof(struct rig_spectrum_line) + line->spectrum_data_length,
    };

    if (rs->multicast_publisher_priv_data == NULL)
    {
        // Silently ignore call if multicast publisher is not enabled
        return RIG_OK;
    }

    // Acquire write lock to write all data in one go to the pipe
    multicast_publisher_write_lock(rig);

    result = multicast_publisher_write_packet_header(rig, &packet);

    if (result != RIG_OK)
    {
        multicast_publisher_write_unlock(rig);
        RETURNFUNC2(result);
    }

    mcast_publisher_priv = (multicast_publisher_priv_data *)
                           rs->multicast_publisher_priv_data;
    mcast_publisher_args = &mcast_publisher_priv->args;

    result = multicast_publisher_write_data(
                 mcast_publisher_args, sizeof(struct rig_spectrum_line), (unsigned char *) line);

    if (result != RIG_OK)
    {
        multicast_publisher_write_unlock(rig);
        RETURNFUNC2(result);
    }

    result = multicast_publisher_write_data(
                 mcast_publisher_args, line->spectrum_data_length, line->spectrum_data);

    multicast_publisher_write_unlock(rig);

    if (result != RIG_OK)
    {
        RETURNFUNC2(result);
    }

    RETURNFUNC2(RIG_OK);
}

static int multicast_publisher_read_packet(multicast_publisher_args
        const *mcast_publisher_args,
        uint8_t *type, struct rig_spectrum_line *spectrum_line,
        unsigned char *spectrum_data)
{
    int result;
    multicast_publisher_data_packet packet;

    result = multicast_publisher_read_data(mcast_publisher_args, sizeof(packet),
                                           (unsigned char *) &packet);

    if (result < 0)
    {
        return (result);
    }

    switch (packet.type)
    {
    case MULTICAST_PUBLISHER_DATA_PACKET_TYPE_POLL:
    case MULTICAST_PUBLISHER_DATA_PACKET_TYPE_TRANSCEIVE:
        break;

    case MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM:
        result = multicast_publisher_read_data(
                     mcast_publisher_args, sizeof(struct rig_spectrum_line),
                     (unsigned char *) spectrum_line);

        if (result < 0)
        {
            return (result);
        }

        if (packet.data_length - sizeof(struct rig_spectrum_line) !=
                spectrum_line->spectrum_data_length)
        {
            rig_debug(RIG_DEBUG_ERR,
                      "%s: multicast publisher data error, expected %d bytes of spectrum data, got %d bytes\n",
                      __func__, (int)spectrum_line->spectrum_data_length,
                      (int)(packet.data_length - sizeof(struct rig_spectrum_line)));
            return (-RIG_EPROTO);
        }

        spectrum_line->spectrum_data = spectrum_data;

        result = multicast_publisher_read_data(mcast_publisher_args,
                                               spectrum_line->spectrum_data_length, spectrum_data);

        if (result < 0)
        {
            return (result);
        }

        break;

    default:
        rig_debug(RIG_DEBUG_ERR,
                  "%s: unexpected multicast publisher data packet type: %d\n", __func__,
                  packet.type);
        return (-RIG_EPROTO);
    }

    *type = packet.type;

    return (RIG_OK);
}

static void *multicast_publisher(void *arg)
{
    unsigned char spectrum_data[HAMLIB_MAX_SPECTRUM_DATA];
    char snapshot_buffer[HAMLIB_MAX_SNAPSHOT_PACKET_SIZE];
#ifdef __MINGW32__
    char ip4[32];
#endif

    struct multicast_publisher_args_s *args = (struct multicast_publisher_args_s *)
            arg;
    RIG *rig = args->rig;
    struct rig_state *rs = STATE(rig);
    struct rig_spectrum_line spectrum_line;
    uint8_t packet_type = MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM;
    multicast_publisher_priv_data *mcast_publisher_priv =
        (multicast_publisher_priv_data *)
        rs->multicast_publisher_priv_data;

    struct sockaddr_in dest_addr;
    int socket_fd = args->socket_fd;
    ssize_t send_result;

    rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Starting multicast publisher\n", __FILE__,
              __LINE__);

#ifdef __MINGW32__

    if (!is_networked(ip4, sizeof(ip4)))
    {
        rig_debug(RIG_DEBUG_WARN, "%s: no IPV4 network detected...multicast disabled\n",
                  __func__);
        return NULL;
    }

#endif

    snapshot_init();

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.s_addr = inet_addr(args->multicast_addr);
    dest_addr.sin_port = htons(args->multicast_port);

    rs->multicast_publisher_run = 1;

    while (rs->multicast_publisher_run)
    {
        int result;

        result = multicast_publisher_read_packet(args, &packet_type, &spectrum_line,
                 spectrum_data);

        if (result != RIG_OK)
        {
            if (result == -RIG_ETIMEOUT)
            {
                continue;
            }

            // TODO: how to detect closing of pipe, indicate with error code
            // TODO: error handling, flush pipe in case of error?
            hl_usleep(100 * 1000);
            continue;
        }

        result = snapshot_serialize(sizeof(snapshot_buffer), snapshot_buffer, rig,
                                    packet_type == MULTICAST_PUBLISHER_DATA_PACKET_TYPE_SPECTRUM ? &spectrum_line :
                                    NULL);

        if (result != RIG_OK)
        {
            rig_debug(RIG_DEBUG_ERR, "%s: error serializing rig snapshot data, result=%d\n",
                      __func__, result);
            continue;
        }

        rig_debug(RIG_DEBUG_CACHE, "%s: sending rig snapshot data: %s\n", __func__,
                  snapshot_buffer);

        send_result = sendto(
                          socket_fd,
                          snapshot_buffer,
                          strlen(snapshot_buffer),
                          0,
                          (struct sockaddr *) &dest_addr,
                          sizeof(dest_addr)
                      );

        if (send_result < 0)
        {
            static int flag = 0;

            if (errno != 0 || flag == 0)
            {
                rig_debug(RIG_DEBUG_ERR,
                          "%s: error sending UDP packet: %s, send result=%d\n", __func__,
                          strerror(errno), (int)send_result);
                flag = 1;
            }
        }
    }

    rs->multicast_publisher_run = 0;
    mcast_publisher_priv->thread_id = 0;

    rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Stopped multicast publisher\n", __FILE__,
              __LINE__);
    return NULL;
}


#ifdef __MINGW32__

static int is_networked(char *address, int address_length)
{
    int count = 0;

    DWORD dwSize = 0;
    DWORD dwRetVal = 0;
    ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
    PIP_ADAPTER_ADDRESSES pAddresses = NULL;
    PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
    PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
    char ipString[INET6_ADDRSTRLEN]; // large enough for both IPv4 and IPv6
    address[0] = 0;

    // First call to determine actual memory size needed
    GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &dwSize);
    pAddresses = (IP_ADAPTER_ADDRESSES *)malloc(dwSize);

    // Second call to get the actual data
    dwRetVal = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &dwSize);

    if (dwRetVal == NO_ERROR)
    {
        for (pCurrAddresses = pAddresses; pCurrAddresses != NULL;
                pCurrAddresses = pCurrAddresses->Next)
        {
//            if (pCurrAddresses->IfType == IF_TYPE_IEEE80211) // Wireless adapter
            {
                char friendlyName[256];
                wcstombs(friendlyName, pCurrAddresses->FriendlyName, sizeof(friendlyName));
                rig_debug(RIG_DEBUG_VERBOSE, "%s: network IfType = %d, name=%s\n", __func__,
                          (int)pCurrAddresses->IfType, friendlyName);

                for (pUnicast = pCurrAddresses->FirstUnicastAddress; pUnicast != NULL;
                        pUnicast = pUnicast->Next)
                {
                    void *addr = NULL;

                    if (pUnicast->Address.lpSockaddr->sa_family == AF_INET) // IPv4 address
                    {
                        struct sockaddr_in *sa_in = (struct sockaddr_in *)pUnicast->Address.lpSockaddr;
                        addr = &(sa_in->sin_addr);
                    }

#if 0 // going to skip IPV6 for now -- should never need it on a local network
                    else if (pUnicast->Address.lpSockaddr->sa_family == AF_INET6) // IPv6 address
                    {
                        struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)
                                                      pUnicast->Address.lpSockaddr;
                        addr = &(sa_in6->sin6_addr);
                    }

#endif

                    // Convert IP address to string and ignore bad ones
                    if (addr)
                    {

                        if (inet_ntop(pUnicast->Address.lpSockaddr->sa_family, addr, ipString,
                                      sizeof(ipString)) != NULL)
                        {
                            // Use IP address if not 169.x.x.x
                            if (strncmp(ipString, "169", 3) != 0)
                            {
                                count++;

                                if (count > 1)
                                {
                                    rig_debug(RIG_DEBUG_VERBOSE,
                                              "%s: more than 1 address found...multicast may not work\n", __func__);
                                }

                                rig_debug(RIG_DEBUG_VERBOSE, "%s: Address: %s\n", ipString, ipString);
                                strncpy(address, ipString, address_length);
                            }
                        }
                    }
                }

                free(pAddresses);
                return 1; // Wireless and addresses printed
            }
        }
    }

    if (pAddresses)
    {
        free(pAddresses);
    }

    return 0; // Not wireless or no addresses found
}

int is_wireless()
{
    DWORD dwSize = 0;
    DWORD dwRetVal = 0;
    ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
    PIP_ADAPTER_ADDRESSES pAddresses = NULL, pCurrAddresses = NULL;

    // First call to determine actual memory size needed
    GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &dwSize);
    pAddresses = (IP_ADAPTER_ADDRESSES *)malloc(dwSize);

    // Second call to get the actual data
    dwRetVal = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &dwSize);

    if (dwRetVal == NO_ERROR)
    {
        for (pCurrAddresses = pAddresses; pCurrAddresses != NULL;
                pCurrAddresses = pCurrAddresses->Next)
        {
            // printf("Adapter name: %s\n", pCurrAddresses->AdapterName);
            // printf("Adapter description: %ls\n", pCurrAddresses->Description);
            // printf("Adapter type: ");

            if (pCurrAddresses->IfType == IF_TYPE_IEEE80211)
            {
                //       printf("Wireless\n\n");
                return 1;
            }
            else
            {
                //      printf("Not Wireless\n\n");
            }
        }
    }
    else
    {
        //printf("GetAdaptersAddresses failed with error: %lu\n", dwRetVal);
    }

    if (pAddresses)
    {
        free(pAddresses);
    }

    return 0;
}
#else
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ifaddrs.h>

static int is_networked(char *ipv4, int ipv4_length)
{
    struct ifaddrs *interfaces, *iface;
    char addr_str[INET_ADDRSTRLEN];

    // Get a list of all network interfaces
    if (getifaddrs(&interfaces) == -1)
    {
        perror("getifaddrs");
        exit(EXIT_FAILURE);
    }

    // Iterate through the list of interfaces
    for (iface = interfaces; iface != NULL; iface = iface->ifa_next)
    {
        if (iface->ifa_addr
                && iface->ifa_addr->sa_family == AF_INET)   // Check it is IP4
        {
            // Convert the linked list of interfaces to a human readable string
            struct sockaddr_in *sa = (struct sockaddr_in *) iface->ifa_addr;
            inet_ntop(AF_INET, &(sa->sin_addr), addr_str, INET_ADDRSTRLEN);

            if (strncmp(addr_str, "127", 3) == 0 && ipv4[0] == 0)
            {
                strncpy(ipv4, addr_str, ipv4_length);
                rig_debug(RIG_DEBUG_VERBOSE, "%s: Can use %s\n", __func__, ipv4);
            }
            else if (strncmp(addr_str, "127", 3) != 0)
            {
                strncpy(ipv4, addr_str, ipv4_length);
                rig_debug(RIG_DEBUG_VERBOSE, "%s: Will use %s\n", __func__, ipv4);
            }
        }
    }

    freeifaddrs(interfaces); // Free the linked list
    return strlen(ipv4) > 0 ;
}


#ifdef __linux__
#include <linux/wireless.h>
int is_wireless_linux(const char *ifname)
{
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct iwreq pwrq;
    memset(&pwrq, 0, sizeof(pwrq));
    strncpy(pwrq.ifr_name, ifname, IFNAMSIZ - 1);

    if (ioctl(sock, SIOCGIWNAME, &pwrq) != -1)
    {
        close(sock);
        return 1;  // Wireless
    }

    close(sock);
    return 0;  // Not wireless
}

int is_wireless()
{
    struct ifaddrs *ifaddr, *ifa;

    if (getifaddrs(&ifaddr) == -1)
    {
        perror("getifaddrs");
        return 0;
    }

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
    {
        if (ifa->ifa_addr == NULL)
        {
            continue;
        }

        int iswireless = is_wireless_linux(ifa->ifa_name);

        //printf("%s is %s\n", ifa->ifa_name, iswireless ? "wireless" : "not wireless");
        if (iswireless) {freeifaddrs(ifaddr); return 1;}
    }

    freeifaddrs(ifaddr);
    return 0;
}
#endif
#endif

static void *multicast_receiver(void *arg)
{
    char data[4096];
    char ip4[INET6_ADDRSTRLEN] = "";

    struct multicast_receiver_args_s *args = (struct multicast_receiver_args_s *)
            arg;
    RIG *rig = args->rig;
    struct rig_state *rs = STATE(rig);
    multicast_receiver_priv_data *mcast_receiver_priv =
        (multicast_receiver_priv_data *)
        rs->multicast_receiver_priv_data;

    struct sockaddr_in dest_addr;
    int socket_fd = args->socket_fd;

    rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Starting multicast receiver\n", __FILE__,
              __LINE__);

    if (!is_networked(ip4, sizeof(ip4)))
    {
        rig_debug(RIG_DEBUG_WARN,
                  "%s: no network detected...disabling multicast receive\n", __func__);
        return NULL;
    }

    int optval = 1;

#ifdef __MINGW32__

    if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (PCHAR)&optval,
                   sizeof(optval)) < 0)
#else
    if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
                   sizeof(optval)) < 0)
#endif
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error enabling UDP address reuse: %s\n", __func__,
                  strerror(errno));
        return NULL;
    }

    // Windows does not have SO_REUSEPORT. However, SO_REUSEADDR works in a similar way.
#if defined(SO_REUSEPORT)

    if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &optval,
                   sizeof(optval)) < 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error enabling UDP port reuse: %s\n", __func__,
                  strerror(errno));
        return NULL;
    }


#endif

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
#ifdef __MINGW32__

    // Windows wireless cannot bind to multicast group addresses for some unknown reason
    // Update: it's not wireless causing the error we see but we'll leave the detection in place
    if (is_wireless())
    {
        rig_debug(RIG_DEBUG_VERBOSE,
                  "%s: wireless detected\n", __func__);

//        dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    }
    else
    {
        rig_debug(RIG_DEBUG_VERBOSE,
                  "%s: no wireless detected so INADDR_ANY is being used\n", __func__);
    }

#else
//    dest_addr.sin_addr.s_addr = inet_addr(args->multicast_addr);
    dest_addr.sin_addr.s_addr = inet_addr(args->multicast_addr);
#endif
    dest_addr.sin_port = htons(args->multicast_port);

    if (bind(socket_fd, (struct sockaddr *) &dest_addr, sizeof(dest_addr)) < 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error binding UDP socket to %s:%d: %s\n",
                  __func__,
                  args->multicast_addr, args->multicast_port, strerror(errno));
        return NULL;
    }

    struct ip_mreq mreq;

    memset(&mreq, 0, sizeof(mreq));

    mreq.imr_multiaddr.s_addr = inet_addr(args->multicast_addr);

#ifdef __MINGW32__

    // we're not worrying about IPV6 right now as that will likely never occur on home network
    if (strlen(ip4) > 0)
    {
        rig_debug(RIG_DEBUG_VERBOSE, "%s: multicast binding to %s\n", __func__, ip4);
        mreq.imr_interface.s_addr = inet_addr(ip4);
    }
    else
#endif
    {
        rig_debug(RIG_DEBUG_VERBOSE, "%s: multicast binding to INADDR_ANY\n", __func__);
        mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    }

#ifdef __MINGW32__

    if (setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (PCHAR)&mreq,
                   sizeof(mreq)) < 0)
#else
    if (setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
                   sizeof(mreq)) < 0)
#endif
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error joining multicast group %s:%d: %s\n",
                  __func__,
                  args->multicast_addr, args->multicast_port, strerror(errno));

        if (errno != 0)
        {
            return NULL;
        }

        rig_debug(RIG_DEBUG_VERBOSE, "%s: errno==0 so trying to continue\n", __func__);
    }

    rs->multicast_receiver_run = 1;

    while (rs->multicast_receiver_run)
    {
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        fd_set rfds, efds;
        struct timeval timeout;
        int select_result;
        ssize_t result;

        timeout.tv_sec = 0;
        timeout.tv_usec = MULTICAST_DATA_PIPE_TIMEOUT_USEC;
        FD_ZERO(&rfds);
        FD_SET(socket_fd, &rfds);
        efds = rfds;

        select_result = select(socket_fd + 1, &rfds, NULL, &efds, &timeout);

        if (!rs->multicast_receiver_run)
        {
            rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): pselect signal\n", __func__, __LINE__);
            break;
        }

        if (select_result == 0)
        {
            // Select timed out
            //rig_debug(RIG_DEBUG_ERR, "%s: select timeout\n", __FILE__);
            continue;
        }

        if (select_result <= 0)
        {
            rig_debug(RIG_DEBUG_ERR,
                      "%s((%d): select() failed when reading UDP multicast socket data: %s\n",
                      __func__,
                      __LINE__,
                      strerror(errno));

            break;
        }

        if ((result = FD_ISSET(socket_fd, &efds)))
        {
            rig_debug(RIG_DEBUG_ERR,
                      "%s(%d): fd error when reading UDP multicast socket data: (%d)=%s\n", __func__,
                      __LINE__, (int)result, strerror(errno));
            break;
        }

        result = recvfrom(socket_fd, data, sizeof(data), 0,
                          (struct sockaddr *) &client_addr, &client_len);

        if (result <= 0)
        {
            if (result < 0)
            {
                if (errno == 0 || errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    continue;
                }

                rig_debug(RIG_DEBUG_ERR, "%s: error receiving from UDP socket %s:%d: %s\n",
                          __func__,
                          args->multicast_addr, args->multicast_port, strerror(errno));
            }

            break;
        }

        // TODO: handle commands from multicast clients
        rig_debug(RIG_DEBUG_VERBOSE, "%s: received %ld bytes of data: %.*s\n", __func__,
                  (long) result, (int) result, data);

        // TODO: if a new snapshot needs to be sent, call network_publish_rig_poll_data() and the publisher routine will send out a snapshot
        // TODO: new logic in publisher needs to be written for other types of responses
    }

    rs->multicast_receiver_run = 0;
    mcast_receiver_priv->thread_id = 0;

    rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Stopped multicast receiver\n", __FILE__,
              __LINE__);
    return NULL;
}

//! @endcond

/**
 * \brief Start multicast publisher
 *
 * Start multicast publisher.
 *
 * \param multicast_addr UDP address
 * \param multicast_port UDP socket port
 * \return RIG_OK or < 0 if error
 */
int network_multicast_publisher_start(RIG *rig, const char *multicast_addr,
                                      int multicast_port, enum multicast_item_e items)
{
    struct rig_state *rs = STATE(rig);
    multicast_publisher_priv_data *mcast_publisher_priv;
    int socket_fd;
    int status;
    int mutex_status;
#ifdef __MINGW32__
    char ip4[32];
#endif

    ENTERFUNC;

    if (rs->multicast_publisher_priv_data != NULL)
    {
        rig_debug(RIG_DEBUG_WARN, "%s(%d): multicast publisher already running\n",
                  __FILE__, __LINE__);
        RETURNFUNC(-RIG_EINVAL);
    }

    rig_debug(RIG_DEBUG_VERBOSE,
              "%s(%d): multicast publisher address=%s, port=%d\n", __FILE__,
              __LINE__,
              multicast_addr, multicast_port);

#ifdef __MINGW32__

    if (!is_networked(ip4, sizeof(ip4)))
    {
        rig_debug(RIG_DEBUG_WARN, "%s: No network found...multicast disabled\n",
                  __func__);
        RETURNFUNC(RIG_OK);
    }

#endif

    if (multicast_addr == NULL || strcmp(multicast_addr, "0.0.0.0") == 0)
    {
        rig_debug(RIG_DEBUG_TRACE, "%s(%d): not starting multicast publisher\n",
                  __FILE__, __LINE__);
        RETURNFUNC(RIG_OK);
    }

    status = network_init();

#ifdef __MINGW32__ // always RIG_OK if not Windows

    if (status != RIG_OK)
    {
        RETURNFUNC(status);
    }

#endif

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (socket_fd < 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error opening new UDP socket: %s", __func__,
                  strerror(errno));
        RETURNFUNC(-RIG_EIO);
    }

    // Enable non-blocking mode
    u_long mode = 1;
#ifdef __MINGW32__

    if (ioctlsocket(socket_fd, FIONBIO, &mode) == SOCKET_ERROR)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error enabling non-blocking mode for socket: %s",
                  __func__,
                  strerror(errno));
        RETURNFUNC(-RIG_EIO);
    }

#else

    if (ioctl(socket_fd, FIONBIO, &mode) < 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error enabling non-blocking mode for socket: %s",
                  __func__,
                  strerror(errno));
        RETURNFUNC(-RIG_EIO);
    }

#endif

    if (items & RIG_MULTICAST_TRANSCEIVE)
    {
        rig_debug(RIG_DEBUG_VERBOSE, "%s(%d) MULTICAST_TRANSCEIVE enabled\n", __FILE__,
                  __LINE__);
    }

    if (items & RIG_MULTICAST_SPECTRUM)
    {
        rig_debug(RIG_DEBUG_VERBOSE, "%s(%d) MULTICAST_SPECTRUM enabled\n", __FILE__,
                  __LINE__);
    }

    rs->snapshot_packet_sequence_number = 0;
    rs->multicast_publisher_run = 0;
    rs->multicast_publisher_priv_data = calloc(1,
                                        sizeof(multicast_publisher_priv_data));

    if (rs->multicast_publisher_priv_data == NULL)
    {
        close(socket_fd);
        RETURNFUNC(-RIG_ENOMEM);
    }

    mcast_publisher_priv = (multicast_publisher_priv_data *)
                           rs->multicast_publisher_priv_data;
    mcast_publisher_priv->args.socket_fd = socket_fd;
    mcast_publisher_priv->args.multicast_addr = multicast_addr;
    mcast_publisher_priv->args.multicast_port = multicast_port;
    mcast_publisher_priv->args.rig = rig;

    mutex_status = pthread_mutex_init(&mcast_publisher_priv->args.write_lock, NULL);

    status = multicast_publisher_create_data_pipe(mcast_publisher_priv);

    if (status < 0 || mutex_status != 0)
    {
        free(rs->multicast_publisher_priv_data);
        rs->multicast_publisher_priv_data = NULL;
        close(socket_fd);
        rig_debug(RIG_DEBUG_ERR,
                  "%s: multicast publisher data pipe creation failed, result=%d\n", __func__,
                  status);
        RETURNFUNC(-RIG_EINTERNAL);
    }

    int err = pthread_create(&mcast_publisher_priv->thread_id, NULL,
                             multicast_publisher,
                             &mcast_publisher_priv->args);

    if (err)
    {
        rig_debug(RIG_DEBUG_ERR, "%s(%d) pthread_create error %s\n", __FILE__, __LINE__,
                  strerror(errno));
        multicast_publisher_close_data_pipe(mcast_publisher_priv);
        free(mcast_publisher_priv);
        rs->multicast_publisher_priv_data = NULL;
        close(socket_fd);
        RETURNFUNC(-RIG_EINTERNAL);
    }

    RETURNFUNC(RIG_OK);
}

/**
 * \brief Stop multicast publisher
 *
 * Stop multicast publisher
 *
 * \return RIG_OK or < 0 if error
 */
int network_multicast_publisher_stop(RIG *rig)
{
    struct rig_state *rs = STATE(rig);
    multicast_publisher_priv_data *mcast_publisher_priv;

    ENTERFUNC;

    rs->multicast_publisher_run = 0;

    mcast_publisher_priv = (multicast_publisher_priv_data *)
                           rs->multicast_publisher_priv_data;

    if (mcast_publisher_priv == NULL)
    {
        RETURNFUNC(RIG_OK);
    }

    if (mcast_publisher_priv->thread_id != 0)
    {
        int err = pthread_join(mcast_publisher_priv->thread_id, NULL);

        if (err)
        {
            rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error %s\n", __FILE__, __LINE__,
                      strerror(errno));
            // just ignore it
        }

        mcast_publisher_priv->thread_id = 0;
    }

    multicast_publisher_close_data_pipe(mcast_publisher_priv);

    if (mcast_publisher_priv->args.socket_fd >= 0)
    {
        close(mcast_publisher_priv->args.socket_fd);
        mcast_publisher_priv->args.socket_fd = -1;
    }

    pthread_mutex_destroy(&mcast_publisher_priv->args.write_lock);

    free(rs->multicast_publisher_priv_data);
    rs->multicast_publisher_priv_data = NULL;

    RETURNFUNC(RIG_OK);
}


/**
 * \brief Start multicast receiver
 *
 * Start multicast receiver.
 *
 * \param multicast_addr UDP address
 * \param multicast_port UDP socket port
 * \return RIG_OK or < 0 if error
 */
int network_multicast_receiver_start(RIG *rig, const char *multicast_addr,
                                     int multicast_port)
{
    struct rig_state *rs = STATE(rig);
    multicast_receiver_priv_data *mcast_receiver_priv;
    int socket_fd;
    int status;

    ENTERFUNC;

    if (rs->multicast_receiver_priv_data != NULL)
    {
        rig_debug(RIG_DEBUG_ERR, "%s(%d): multicast receiver already running\n",
                  __FILE__,
                  __LINE__);
        RETURNFUNC(-RIG_EINVAL);
    }

    rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): multicast receiver address=%s, port=%d\n",
              __FILE__,
              __LINE__,
              multicast_addr, multicast_port);

    if (multicast_addr == NULL || strcmp(multicast_addr, "0.0.0.0") == 0)
    {
        rig_debug(RIG_DEBUG_TRACE, "%s(%d): not starting multicast receiver\n",
                  __FILE__, __LINE__);
        RETURNFUNC(RIG_OK);
    }

    status = network_init();

    if (status != RIG_OK)
    {
        RETURNFUNC(status);
    }

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (socket_fd < 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error opening new UDP socket: %s", __func__,
                  strerror(errno));
        RETURNFUNC(-RIG_EIO);
    }

    // Enable non-blocking mode
    u_long mode = 1;
#ifdef __MINGW32__

    if (ioctlsocket(socket_fd, FIONBIO, &mode) == SOCKET_ERROR)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error enabling non-blocking mode for socket: %s",
                  __func__,
                  strerror(errno));
        RETURNFUNC(-RIG_EIO);
    }

#else

    if (ioctl(socket_fd, FIONBIO, &mode) < 0)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: error enabling non-blocking mode for socket: %s",
                  __func__,
                  strerror(errno));
        RETURNFUNC(-RIG_EIO);
    }

#endif

    rs->multicast_receiver_run = 0;
    rs->multicast_receiver_priv_data = calloc(1,
                                       sizeof(multicast_receiver_priv_data));

    if (rs->multicast_receiver_priv_data == NULL)
    {
        close(socket_fd);
        RETURNFUNC(-RIG_ENOMEM);
    }

    mcast_receiver_priv = (multicast_receiver_priv_data *)
                          rs->multicast_receiver_priv_data;
    mcast_receiver_priv->args.socket_fd = socket_fd;
    mcast_receiver_priv->args.multicast_addr = multicast_addr;
    mcast_receiver_priv->args.multicast_port = multicast_port;
    mcast_receiver_priv->args.rig = rig;

    int err = pthread_create(&mcast_receiver_priv->thread_id, NULL,
                             multicast_receiver,
                             &mcast_receiver_priv->args);

    if (err)
    {
        rig_debug(RIG_DEBUG_ERR, "%s(%d) pthread_create error %s\n", __FILE__, __LINE__,
                  strerror(errno));
        free(mcast_receiver_priv);
        rs->multicast_receiver_priv_data = NULL;
        close(socket_fd);
        RETURNFUNC(-RIG_EINTERNAL);
    }

    RETURNFUNC(RIG_OK);
}

/**
 * \brief Stop multicast receiver
 *
 * Stop multicast receiver
 *
 * \return RIG_OK or < 0 if error
 */
int network_multicast_receiver_stop(RIG *rig)
{
    struct rig_state *rs = STATE(rig);
    multicast_receiver_priv_data *mcast_receiver_priv;

    ENTERFUNC;

    rs->multicast_receiver_run = 0;

    mcast_receiver_priv = (multicast_receiver_priv_data *)
                          rs->multicast_receiver_priv_data;

    if (mcast_receiver_priv == NULL)
    {
        RETURNFUNC(RIG_OK);
    }

    // Close the socket first to stop the routine
    if (mcast_receiver_priv->args.socket_fd >= 0)
    {
#ifdef __MINGW32__
        shutdown(mcast_receiver_priv->args.socket_fd, SD_BOTH);
#else
        shutdown(mcast_receiver_priv->args.socket_fd, SHUT_RDWR);
#endif
        close(mcast_receiver_priv->args.socket_fd);
    }

    if (mcast_receiver_priv->thread_id != 0)
    {
        int err = pthread_join(mcast_receiver_priv->thread_id, NULL);

        if (err)
        {
            rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error %s\n", __FILE__, __LINE__,
                      strerror(errno));
            // just ignore it
        }

        mcast_receiver_priv->thread_id = 0;
    }

    if (mcast_receiver_priv->args.socket_fd >= 0)
    {
        mcast_receiver_priv->args.socket_fd = -1;
    }

    free(rs->multicast_receiver_priv_data);
    rs->multicast_receiver_priv_data = NULL;

    RETURNFUNC(RIG_OK);
}

/** @} */
