/**
 * (C) 2007-22 - ntop.org and contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not see see <http://www.gnu.org/licenses/>
 *
 */

#include "n2n.h"

#define HASH_FIND_COMMUNITY(head, name, out) HASH_FIND_STR(head, name, out)

int resolve_create_thread (n2n_resolve_parameter_t **param, struct peer_info *sn_list);
int resolve_check (n2n_resolve_parameter_t *param, uint8_t resolution_request, time_t now);
int resolve_cancel_thread (n2n_resolve_parameter_t *param);


static ssize_t sendto_peer (n2n_sn_t *sss,
                            const struct peer_info *peer,
                            const uint8_t *pktbuf,
                            size_t pktsize);

static uint16_t reg_lifetime (n2n_sn_t *sss);

static int update_edge (n2n_sn_t *sss,
                        const n2n_common_t* cmn,
                        const n2n_REGISTER_SUPER_t* reg,
                        struct sn_community *comm,
                        const n2n_sock_t *sender_sock,
                        const SOCKET socket_fd,
                        n2n_auth_t *answer_auth,
                        int skip_add,
                        time_t now);

static int re_register_and_purge_supernodes (n2n_sn_t *sss,
                                             struct sn_community *comm,
                                             time_t *p_last_re_reg_and_purge,
                                             time_t now,
                                             uint8_t forced);

static int purge_expired_communities (n2n_sn_t *sss,
                                      time_t* p_last_purge,
                                      time_t now);

static int sort_communities (n2n_sn_t *sss,
                             time_t* p_last_sort,
                             time_t now);

int process_mgmt (n2n_sn_t *sss,
                         const struct sockaddr_in *sender_sock,
                         char *mgmt_buf,
                         size_t mgmt_size,
                         time_t now);

static int process_udp (n2n_sn_t *sss,
                        const struct sockaddr_in *sender_sock,
                        const SOCKET socket_fd,
                        uint8_t *udp_buf,
                        size_t udp_size,
                        time_t now);


/* ************************************** */


void close_tcp_connection (n2n_sn_t *sss, n2n_tcp_connection_t *conn) {

    struct sn_community *comm, *tmp_comm;
    struct peer_info *edge, *tmp_edge;

    if(!conn)
        return;

    // find peer by file descriptor
    HASH_ITER(hh, sss->communities, comm, tmp_comm) {
        HASH_ITER(hh, comm->edges, edge, tmp_edge) {
            if(edge->socket_fd == conn->socket_fd) {
                // remove peer
                HASH_DEL(comm->edges, edge);
                free(edge);
                goto close_conn; /* break - level 2 */
            }
        }
    }

 close_conn:
    // close the connection
    shutdown(conn->socket_fd, SHUT_RDWR);
    closesocket(conn->socket_fd);
    // forget about the connection, will be deleted later
    conn->inactive = 1;
}


/* *************************************************** */


// generate shared secrets for user authentication; can be done only after
// federation name is known (-F) and community list completely read (-c)
void calculate_shared_secrets (n2n_sn_t *sss) {

    struct sn_community *comm, *tmp_comm;
    sn_user_t *user, *tmp_user;

    traceEvent(TRACE_INFO, "started shared secrets calculation for edge authentication");

    generate_private_key(sss->private_key, sss->federation->community + 1); /* skip '*' federation leading character */
    HASH_ITER(hh, sss->communities, comm, tmp_comm) {
        if(comm->is_federation) {
            continue;
        }
        HASH_ITER(hh, comm->allowed_users, user, tmp_user) {
            // calculate common shared secret (ECDH)
            generate_shared_secret(user->shared_secret, sss->private_key, user->public_key);
            // prepare for use as key
            user->shared_secret_ctx = (he_context_t*)calloc(1, sizeof(speck_context_t));
            speck_init((speck_context_t**)&user->shared_secret_ctx, user->shared_secret, 128);
        }
    }

    traceEvent(TRACE_NORMAL, "calculated shared secrets for edge authentication");
}


// calculate dynamic keys
void calculate_dynamic_keys (n2n_sn_t *sss) {

    struct sn_community *comm, *tmp_comm = NULL;

    traceEvent(TRACE_INFO, "calculating dynamic keys");
    HASH_ITER(hh, sss->communities, comm, tmp_comm) {
        // skip federation
        if(comm->is_federation) {
            continue;
        }

        // calculate dynamic keys if this is a user/pw auth'ed community
        if(comm->allowed_users) {
            calculate_dynamic_key(comm->dynamic_key,           /* destination */
                                  sss->dynamic_key_time,       /* time - same for all */
                                  (uint8_t *)comm->community,  /* community name */
                                  (uint8_t *)sss->federation->community); /* federation name */
            packet_header_change_dynamic_key(comm->dynamic_key,
                                             &(comm->header_encryption_ctx_dynamic),
                                             &(comm->header_iv_ctx_dynamic));
            traceEvent(TRACE_DEBUG, "calculated dynamic key for community '%s'", comm->community);
        }
    }
}


// send RE_REGISTER_SUPER to all edges from user/pw auth'ed communites
void send_re_register_super (n2n_sn_t *sss) {

    struct sn_community *comm, *tmp_comm = NULL;
    struct peer_info *edge, *tmp_edge = NULL;
    n2n_common_t   cmn;
    uint8_t        rereg_buf[N2N_SN_PKTBUF_SIZE];
    size_t         encx = 0;
    n2n_sock_str_t sockbuf;

    HASH_ITER(hh, sss->communities, comm, tmp_comm) {
        if(comm->is_federation) {
            continue;
        }

        // send RE_REGISTER_SUPER to edges if this is a user/pw auth community
        if(comm->allowed_users) {
            // prepare
            cmn.ttl = N2N_DEFAULT_TTL;
            cmn.pc = n2n_re_register_super;
            cmn.flags = N2N_FLAGS_FROM_SUPERNODE;
            memcpy(cmn.community, comm->community, N2N_COMMUNITY_SIZE);

            HASH_ITER(hh, comm->edges, edge, tmp_edge) {
                // encode
                encx = 0;
                encode_common(rereg_buf, &encx, &cmn);

                // send
                traceEvent(TRACE_DEBUG, "send RE_REGISTER_SUPER to %s",
                                         sock_to_cstr(sockbuf, &(edge->sock)));

                packet_header_encrypt(rereg_buf, encx, encx,
                                      comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
                                      time_stamp());

                /* sent = */ sendto_peer(sss, edge, rereg_buf, encx);
             }
        }
    }
}


/** Load the list of allowed communities. Existing/previous ones will be removed,
 *  return 0 on success, -1 if file not found, -2 if no valid entries found
 */
int load_allowed_sn_community (n2n_sn_t *sss) {

    char buffer[4096], *line, *cmn_str, net_str[20], format[20];

    sn_user_t *user, *tmp_user;
    n2n_desc_t username;
    n2n_private_public_key_t public_key;
    char ascii_public_key[(N2N_PRIVATE_PUBLIC_KEY_SIZE * 8 + 5) / 6 + 1];

    dec_ip_str_t ip_str = {'\0'};
    uint8_t bitlen;
    in_addr_t net;
    uint32_t mask;
    FILE *fd = fopen(sss->community_file, "r");

    struct sn_community *comm, *tmp_comm, *last_added_comm = NULL;
    struct peer_info *edge, *tmp_edge;
    node_supernode_association_t *assoc, *tmp_assoc;
    n2n_tcp_connection_t *conn;
    time_t any_time = 0;

    uint32_t num_communities = 0;

    struct sn_community_regular_expression *re, *tmp_re;
    uint32_t num_regex = 0;
    int has_net;

    if(fd == NULL) {
        traceEvent(TRACE_WARNING, "File %s not found", sss->community_file);
        return -1;
    }

    // reset data structures ------------------------------

    // send RE_REGISTER_SUPER to all edges from user/pw auth communites, this is safe because
    // follow-up REGISTER_SUPER cannot be handled before this function ends
    send_re_register_super(sss);

    // remove communities (not: federation)
    HASH_ITER(hh, sss->communities, comm, tmp_comm) {
        if(comm->is_federation) {
            continue;
        }

        // remove all edges from community
        HASH_ITER(hh, comm->edges, edge, tmp_edge) {
            // remove all edge associations (with other supernodes)
            HASH_ITER(hh, comm->assoc, assoc, tmp_assoc) {
                HASH_DEL(comm->assoc, assoc);
                free(assoc);
            }

            // close TCP connections, if any (also causes reconnect)
            // and delete edge from list
            if((edge->socket_fd != sss->sock) && (edge->socket_fd >= 0)) {
                HASH_FIND_INT(sss->tcp_connections, &(edge->socket_fd), conn);
                close_tcp_connection(sss, conn); /* also deletes the edge */
            } else {
                HASH_DEL(comm->edges, edge);
                free(edge);
            }
        }

        // remove allowed users from community
        HASH_ITER(hh, comm->allowed_users, user, tmp_user) {
            free(user->shared_secret_ctx);
            HASH_DEL(comm->allowed_users, user);
            free(user);
        }

        // remove community
        HASH_DEL(sss->communities, comm);
        if(NULL != comm->header_encryption_ctx_static) {
            // remove header encryption keys
            free(comm->header_encryption_ctx_static);
            free(comm->header_iv_ctx_static);
            free(comm->header_encryption_ctx_dynamic);
            free(comm->header_iv_ctx_dynamic);
        }
        free(comm);
    }

    // remove all regular expressions for allowed communities
    HASH_ITER(hh, sss->rules, re, tmp_re) {
        HASH_DEL(sss->rules, re);
        free(re);
    }

    // prepare reading data -------------------------------

    // new key_time for all communities, requires dynamic keys to be recalculated (see further below),
    // and  edges to re-register (see above) and ...
    sss->dynamic_key_time = time(NULL);
    // ... federated supernodes to re-register
    re_register_and_purge_supernodes(sss, sss->federation, &any_time, any_time, 1 /* forced */);

    // format definition for possible user-key entries
    sprintf(format, "%c %%%ds %%%lds", N2N_USER_KEY_LINE_STARTER, N2N_DESC_SIZE - 1, sizeof(ascii_public_key)-1);

    while((line = fgets(buffer, sizeof(buffer), fd)) != NULL) {
        int len = strlen(line);

        if((len < 2) || line[0] == '#') {
            continue;
        }

        len--;
        while(len > 0) {
            if((line[len] == '\n') || (line[len] == '\r')) {
                line[len] = '\0';
                len--;
            } else {
                break;
            }
        }
        // the loop above does not always determine correct 'len'
        len = strlen(line);

        // user-key line for edge authentication?
        if(line[0] == N2N_USER_KEY_LINE_STARTER) { /* special first character */
            if(sscanf(line, format, username, ascii_public_key) == 2) { /* correct format */
                if(last_added_comm) { /* is there a valid community to add users to */
                    user = (sn_user_t*)calloc(1, sizeof(sn_user_t));
                    if(user) {
                        // username
                        memcpy(user->name, username, sizeof(username));
                        // public key
                        ascii_to_bin(public_key, ascii_public_key);
                        memcpy(user->public_key, public_key, sizeof(public_key));
                        // common shared secret will be calculated later
                        // add to list
                        HASH_ADD(hh, last_added_comm->allowed_users, public_key, sizeof(n2n_private_public_key_t), user);
                        traceEvent(TRACE_INFO, "added user '%s' with public key '%s' to community '%s'",
                                               user->name, ascii_public_key, last_added_comm->community);
                        // enable header encryption
                        last_added_comm->header_encryption = HEADER_ENCRYPTION_ENABLED;
                        packet_header_setup_key(last_added_comm->community,
                                                &(last_added_comm->header_encryption_ctx_static),
                                                &(last_added_comm->header_encryption_ctx_dynamic),
                                                &(last_added_comm->header_iv_ctx_static),
                                                &(last_added_comm->header_iv_ctx_dynamic));
                        // dynamic key setup follows at a later point in code
                    }
                    continue;
                }
            }
        }

        // --- community name or regular expression

        // cut off any IP sub-network upfront
        cmn_str = (char*)calloc(len + 1, sizeof(char));
        has_net = (sscanf(line, "%s %s", cmn_str, net_str) == 2);

        // if it contains typical characters...
        if(NULL != strpbrk(cmn_str, ".*+?[]\\")) {
            // ...it is treated as regular expression
            re = (struct sn_community_regular_expression*)calloc(1, sizeof(struct sn_community_regular_expression));
            if(re) {
                re->rule = re_compile(cmn_str);
                HASH_ADD_PTR(sss->rules, rule, re);
                num_regex++;
                traceEvent(TRACE_INFO, "added regular expression for allowed communities '%s'", cmn_str);
                free(cmn_str);
                last_added_comm = NULL;
                continue;
            }
        }

        comm = (struct sn_community*)calloc(1,sizeof(struct sn_community));

        if(comm != NULL) {
            comm_init(comm, cmn_str);
            /* loaded from file, this community is unpurgeable */
            comm->purgeable = COMMUNITY_UNPURGEABLE;
            /* we do not know if header encryption is used in this community,
             * first packet will show. just in case, setup the key. */
            comm->header_encryption = HEADER_ENCRYPTION_UNKNOWN;
            packet_header_setup_key(comm->community,
                                    &(comm->header_encryption_ctx_static),
                                    &(comm->header_encryption_ctx_dynamic),
                                    &(comm->header_iv_ctx_static),
                                    &(comm->header_iv_ctx_dynamic));
            HASH_ADD_STR(sss->communities, community, comm);
            last_added_comm = comm;

            num_communities++;
            traceEvent(TRACE_INFO, "added allowed community '%s' [total: %u]",
                       (char*)comm->community, num_communities);

            // check for sub-network address
            if(has_net) {
                if(sscanf(net_str, "%15[^/]/%hhu", ip_str, &bitlen) != 2) {
                    traceEvent(TRACE_WARNING, "bad net/bit format '%s' for community '%c', ignoring; see comments inside community.list file",
                                           net_str, cmn_str);
                    has_net = 0;
                }
                net = inet_addr(ip_str);
                mask = bitlen2mask(bitlen);
                if((net == (in_addr_t)(-1)) || (net == INADDR_NONE) || (net == INADDR_ANY)
                         || ((ntohl(net) & ~mask) != 0)) {
                    traceEvent(TRACE_WARNING, "bad network '%s/%u' in '%s' for community '%s', ignoring",
                                           ip_str, bitlen, net_str, cmn_str);
                    has_net = 0;
                }
                if((bitlen > 30) || (bitlen == 0)) {
                    traceEvent(TRACE_WARNING, "bad prefix '%hhu' in '%s' for community '%s', ignoring",
                                           bitlen, net_str, cmn_str);
                    has_net = 0;
                }
            }
            if(has_net) {
                comm->auto_ip_net.net_addr = ntohl(net);
                comm->auto_ip_net.net_bitlen = bitlen;
                traceEvent(TRACE_INFO, "assigned sub-network %s/%u to community '%s'",
                                       inet_ntoa(*(struct in_addr *) &net),
                           comm->auto_ip_net.net_bitlen,
                           comm->community);
            } else {
                assign_one_ip_subnet(sss, comm);
            }
        }
        free(cmn_str);
    }

    fclose(fd);

    if((num_regex + num_communities) == 0) {
        traceEvent(TRACE_WARNING, "file %s does not contain any valid community names or regular expressions", sss->community_file);
        return -2;
    }

    traceEvent(TRACE_NORMAL, "loaded %u fixed-name communities from %s",
                     num_communities, sss->community_file);

    traceEvent(TRACE_NORMAL, "loaded %u regular expressions for community name matching from %s",
                     num_regex, sss->community_file);

    // calculate allowed user's shared secrets (shared with federation)
    calculate_shared_secrets(sss);

    // calculcate communties' dynamic keys
    calculate_dynamic_keys(sss);

    // no new communities will be allowed
    sss->lock_communities = 1;

    return 0;
}


/* *************************************************** */


/** Send a datagram to a file descriptor socket.
 *
 *    @return -1 on error otherwise number of bytes sent
 */
static ssize_t sendto_fd (n2n_sn_t *sss,
                          SOCKET socket_fd,
                          const struct sockaddr *socket,
                          const uint8_t *pktbuf,
                          size_t pktsize) {

    ssize_t sent = 0;
    n2n_tcp_connection_t *conn;

    sent = sendto(socket_fd, (void *)pktbuf, pktsize, 0 /* flags */,
                  socket, sizeof(struct sockaddr_in));

    if((sent <= 0) && (errno)) {
        char * c = strerror(errno);
        traceEvent(TRACE_ERROR, "sendto failed (%d) %s", errno, c);
#ifdef WIN32
        traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", WSAGetLastError());
#endif
        // if the erroneous connection is tcp, i.e. not the regular sock...
        if((socket_fd >= 0) && (socket_fd != sss->sock)) {
            // ...forget about the corresponding peer and the connection
            HASH_FIND_INT(sss->tcp_connections, &socket_fd, conn);
            close_tcp_connection(sss, conn);
            return -1;
        }
    } else {
            traceEvent(TRACE_DEBUG, "sendto sent=%d to ", (signed int)sent);
    }

    return sent;
}


/** Send a datagram to a network order socket of type struct sockaddr.
 *
 *    @return -1 on error otherwise number of bytes sent
 */
static ssize_t sendto_sock(n2n_sn_t *sss,
                           SOCKET socket_fd,
                           const struct sockaddr *socket,
                           const uint8_t *pktbuf,
                           size_t pktsize) {

    ssize_t sent = 0;
    int value = 0;

    // if the connection is tcp, i.e. not the regular sock...
    if((socket_fd >= 0) && (socket_fd != sss->sock)) {

        setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
        value = 1;
#ifdef LINUX
        setsockopt(socket_fd, IPPROTO_TCP, TCP_CORK, &value, sizeof(value));
#endif

        // prepend packet length...
        uint16_t pktsize16 = htobe16(pktsize);
        sent = sendto_fd(sss, socket_fd, socket, (uint8_t*)&pktsize16, sizeof(pktsize16));

        if(sent <= 0)
            return -1;
        // ...before sending the actual data
    }

    sent = sendto_fd(sss, socket_fd, socket, pktbuf, pktsize);

    // if the connection is tcp, i.e. not the regular sock...
    if((socket_fd >= 0) && (socket_fd != sss->sock)) {
        value = 1; /* value should still be set to 1 */
        setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
#ifdef LINUX
        value = 0;
        setsockopt(socket_fd, IPPROTO_TCP, TCP_CORK, &value, sizeof(value));
#endif
    }

    return sent;
}


/** Send a datagram to a peer whose destination socket is embodied in its sock field of type n2n_sock_t.
 *  It calls sendto_sock to do the final send.
 *
 *    @return -1 on error otherwise number of bytes sent
 */
static ssize_t sendto_peer (n2n_sn_t *sss,
                            const struct peer_info *peer,
                            const uint8_t *pktbuf,
                            size_t pktsize) {

    n2n_sock_str_t sockbuf;

    if(AF_INET == peer->sock.family) {

        // network order socket
        struct sockaddr_in socket;
        fill_sockaddr((struct sockaddr *)&socket, sizeof(socket), &(peer->sock));

        traceEvent(TRACE_DEBUG, "sent %lu bytes to [%s]",
                   pktsize,
                   sock_to_cstr(sockbuf, &(peer->sock)));

        return sendto_sock(sss,
                           (peer->socket_fd >= 0) ? peer->socket_fd : sss->sock,
                           (const struct sockaddr*)&socket, pktbuf, pktsize);
    } else {
        /* AF_INET6 not implemented */
        errno = EAFNOSUPPORT;
        return -1;
    }
}


/** Try and broadcast a message to all edges in the community.
 *
 *    This will send the exact same datagram to zero or more edges registered to
 *    the supernode.
 */
static int try_broadcast (n2n_sn_t * sss,
                          const struct sn_community *comm,
                          const n2n_common_t * cmn,
                          const n2n_mac_t srcMac,
                          uint8_t from_supernode,
                          const uint8_t * pktbuf,
                          size_t pktsize,
                          time_t now) {

    struct peer_info        *scan, *tmp;
    macstr_t                mac_buf;
    n2n_sock_str_t          sockbuf;

    traceEvent(TRACE_DEBUG, "try_broadcast");

    /* We have to make sure that a broadcast reaches the other supernodes and edges
     * connected to them. try_broadcast needs a from_supernode parameter: if set,
     * do forward to edges of community only. If unset, forward to all locally known
     * nodes of community AND all supernodes associated with the community */

    if (!from_supernode) {
        HASH_ITER(hh, sss->federation->edges, scan, tmp) {
            int data_sent_len;

            // only forward to active supernodes
            if(scan->last_seen + LAST_SEEN_SN_INACTIVE > now) {

                data_sent_len = sendto_peer(sss, scan, pktbuf, pktsize);

                if(data_sent_len != pktsize) {
                    ++(sss->stats.errors);
                    traceEvent(TRACE_WARNING, "multicast %lu to supernode [%s] %s failed %s",
                               pktsize,
                               sock_to_cstr(sockbuf, &(scan->sock)),
                               macaddr_str(mac_buf, scan->mac_addr),
                               strerror(errno));
                 } else {
                     ++(sss->stats.broadcast);
                     traceEvent(TRACE_DEBUG, "multicast %lu to supernode [%s] %s",
                                pktsize,
                                sock_to_cstr(sockbuf, &(scan->sock)),
                                macaddr_str(mac_buf, scan->mac_addr));
                 }
            }
        }
    }

    if(comm) {
        HASH_ITER(hh, comm->edges, scan, tmp) {
            if(memcmp(srcMac, scan->mac_addr, sizeof(n2n_mac_t)) != 0) {
                /* REVISIT: exclude if the destination socket is where the packet came from. */
                int data_sent_len;

                data_sent_len = sendto_peer(sss, scan, pktbuf, pktsize);

                if(data_sent_len != pktsize) {
                    ++(sss->stats.errors);
                    traceEvent(TRACE_WARNING, "multicast %lu to [%s] %s failed %s",
                               pktsize,
                               sock_to_cstr(sockbuf, &(scan->sock)),
                               macaddr_str(mac_buf, scan->mac_addr),
                               strerror(errno));
                } else {
                    ++(sss->stats.broadcast);
                    traceEvent(TRACE_DEBUG, "multicast %lu to [%s] %s",
                               pktsize,
                               sock_to_cstr(sockbuf, &(scan->sock)),
                               macaddr_str(mac_buf, scan->mac_addr));
                }
            }
        }
    }

    return 0;
}


static int try_forward (n2n_sn_t * sss,
                        const struct sn_community *comm,
                        const n2n_common_t * cmn,
                        const n2n_mac_t dstMac,
                        uint8_t from_supernode,
                        const uint8_t * pktbuf,
                        size_t pktsize,
                        time_t now) {

    struct peer_info *             scan;
    node_supernode_association_t   *assoc;
    macstr_t                       mac_buf;
    n2n_sock_str_t                 sockbuf;

    HASH_FIND_PEER(comm->edges, dstMac, scan);

    if(NULL != scan) {
        int data_sent_len;
        data_sent_len = sendto_peer(sss, scan, pktbuf, pktsize);

        if(data_sent_len == pktsize) {
            ++(sss->stats.fwd);
            traceEvent(TRACE_DEBUG, "unicast %lu to [%s] %s",
                       pktsize,
                       sock_to_cstr(sockbuf, &(scan->sock)),
                       macaddr_str(mac_buf, scan->mac_addr));
        } else {
            ++(sss->stats.errors);
            traceEvent(TRACE_ERROR, "unicast %lu to [%s] %s FAILED (%d: %s)",
                       pktsize,
                       sock_to_cstr(sockbuf, &(scan->sock)),
                       macaddr_str(mac_buf, scan->mac_addr),
                       errno, strerror(errno));
            return -1;
        }
    } else {
        if(!from_supernode) {
            // check if target edge is associated with a certain supernode
            HASH_FIND(hh, comm->assoc, dstMac, sizeof(n2n_mac_t), assoc);
            if(assoc) {
                traceEvent(TRACE_DEBUG, "found mac address associated with a known supernode, forwarding packet to that supernode");
                sendto_sock(sss, sss->sock,
                            (const struct sockaddr*)&(assoc->sock),
                            pktbuf, pktsize);
            } else {
                // forwarding packet to all federated supernodes
                traceEvent(TRACE_DEBUG, "unknown mac address, broadcasting packet to all federated supernodes");
                try_broadcast(sss, NULL, cmn, sss->mac_addr, from_supernode, pktbuf, pktsize, now);
            }
        } else {
            traceEvent(TRACE_DEBUG, "unknown mac address in packet from a supernode, dropping the packet");
            /* Not a known MAC so drop. */
            return -2;
        }
    }

    return 0;
}


/** Initialise some fields of the community structure **/
int comm_init (struct sn_community *comm, char *cmn) {

    strncpy((char*)comm->community, cmn, N2N_COMMUNITY_SIZE);
    comm->community[N2N_COMMUNITY_SIZE - 1] = '\0';
    comm->is_federation = IS_NO_FEDERATION;

    return 0; /* OK */
}


/** Initialise the supernode structure */
int sn_init_defaults (n2n_sn_t *sss) {

    char *tmp_string;

#ifdef WIN32
    initWin32();
#endif

    pearson_hash_init();

    memset(sss, 0, sizeof(n2n_sn_t));

    strncpy(sss->version, PACKAGE_VERSION, sizeof(n2n_version_t));
    sss->version[sizeof(n2n_version_t) - 1] = '\0';
    sss->daemon = 1; /* By defult run as a daemon. */
    sss->lport = N2N_SN_LPORT_DEFAULT;
    sss->mport = N2N_SN_MGMT_PORT;
    sss->sock = -1;
    sss->mgmt_sock = -1;
    sss->min_auto_ip_net.net_addr = inet_addr(N2N_SN_MIN_AUTO_IP_NET_DEFAULT);
    sss->min_auto_ip_net.net_addr = ntohl(sss->min_auto_ip_net.net_addr);
    sss->min_auto_ip_net.net_bitlen = N2N_SN_AUTO_IP_NET_BIT_DEFAULT;
    sss->max_auto_ip_net.net_addr = inet_addr(N2N_SN_MAX_AUTO_IP_NET_DEFAULT);
    sss->max_auto_ip_net.net_addr = ntohl(sss->max_auto_ip_net.net_addr);
    sss->max_auto_ip_net.net_bitlen = N2N_SN_AUTO_IP_NET_BIT_DEFAULT;

    sss->federation = (struct sn_community *)calloc(1, sizeof(struct sn_community));
    /* Initialize the federation */
    if(sss->federation) {
        if(getenv("N2N_FEDERATION"))
            snprintf(sss->federation->community, N2N_COMMUNITY_SIZE - 1 ,"*%s", getenv("N2N_FEDERATION"));
        else
            strncpy(sss->federation->community, (char*)FEDERATION_NAME, N2N_COMMUNITY_SIZE);
        sss->federation->community[N2N_COMMUNITY_SIZE - 1] = '\0';
        /* enable the flag for federation */
        sss->federation->is_federation = IS_FEDERATION;
        sss->federation->purgeable = COMMUNITY_UNPURGEABLE;
        /* header encryption enabled by default */
        sss->federation->header_encryption = HEADER_ENCRYPTION_ENABLED;
        /*setup the encryption key */
        packet_header_setup_key(sss->federation->community,
                                &(sss->federation->header_encryption_ctx_static),
                                &(sss->federation->header_encryption_ctx_dynamic),
                                &(sss->federation->header_iv_ctx_static),
                                &(sss->federation->header_iv_ctx_dynamic));
        sss->federation->edges = NULL;
    }

    n2n_srand(n2n_seed());

    /* Random auth token */
    sss->auth.scheme = n2n_auth_simple_id;
    memrnd(sss->auth.token, N2N_AUTH_ID_TOKEN_SIZE);
    sss->auth.token_size = N2N_AUTH_ID_TOKEN_SIZE;

    /* Random MAC address */
    memrnd(sss->mac_addr, N2N_MAC_SIZE);
    sss->mac_addr[0] &= ~0x01; /* Clear multicast bit */
    sss->mac_addr[0] |= 0x02;    /* Set locally-assigned bit */

    tmp_string = calloc(1, strlen(N2N_MGMT_PASSWORD) + 1);
    if(tmp_string) {
        strncpy((char*)tmp_string, N2N_MGMT_PASSWORD, strlen(N2N_MGMT_PASSWORD) + 1);
        sss->mgmt_password_hash = pearson_hash_64((uint8_t*)tmp_string, strlen(N2N_MGMT_PASSWORD));
        free(tmp_string);
    }

    return 0; /* OK */
}


/** Initialise the supernode */
void sn_init (n2n_sn_t *sss) {

    if(resolve_create_thread(&(sss->resolve_parameter), sss->federation->edges) == 0) {
         traceEvent(TRACE_NORMAL, "successfully created resolver thread");
    }
}


/** Deinitialise the supernode structure and deallocate any memory owned by
 *    it. */
void sn_term (n2n_sn_t *sss) {

    struct sn_community *community, *tmp;
    struct sn_community_regular_expression *re, *tmp_re;
    n2n_tcp_connection_t *conn, *tmp_conn;
    node_supernode_association_t *assoc, *tmp_assoc;

    resolve_cancel_thread(sss->resolve_parameter);

    if(sss->sock >= 0) {
        closesocket(sss->sock);
    }
    sss->sock = -1;

    HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
        shutdown(conn->socket_fd, SHUT_RDWR);
        closesocket(conn->socket_fd);
        HASH_DEL(sss->tcp_connections, conn);
        free(conn);
    }

    if(sss->tcp_sock >= 0) {
        shutdown(sss->tcp_sock, SHUT_RDWR);
        closesocket(sss->tcp_sock);
    }
    sss->tcp_sock = -1;

    if(sss->mgmt_sock >= 0) {
        closesocket(sss->mgmt_sock);
    }
    sss->mgmt_sock = -1;

    HASH_ITER(hh, sss->communities, community, tmp) {
        clear_peer_list(&community->edges);
        if(NULL != community->header_encryption_ctx_static) {
            free(community->header_encryption_ctx_static);
            free(community->header_encryption_ctx_dynamic);
        }
        // remove all associations
        HASH_ITER(hh, community->assoc, assoc, tmp_assoc) {
            HASH_DEL(community->assoc, assoc);
            free(assoc);
        }
        HASH_DEL(sss->communities, community);
        free(community);
    }

    HASH_ITER(hh, sss->rules, re, tmp_re) {
        HASH_DEL(sss->rules, re);
        if (NULL != re->rule) {
            free(re->rule);
        }
        free(re);
    }

    if(sss->community_file)
        free(sss->community_file);
#ifdef WIN32
    destroyWin32();
#endif
}

void update_node_supernode_association (struct sn_community *comm,
                                        n2n_mac_t *edgeMac, const struct sockaddr_in *sender_sock,
                                        time_t now) {

    node_supernode_association_t *assoc;

    HASH_FIND(hh, comm->assoc, edgeMac, sizeof(n2n_mac_t), assoc);
    if(!assoc) {
        // create a new association
        assoc = (node_supernode_association_t*)malloc(sizeof(node_supernode_association_t));
        if(assoc) {
            memcpy(&(assoc->mac), edgeMac, sizeof(n2n_mac_t));
            memcpy((struct sockaddr_in*)&(assoc->sock), sender_sock, sizeof(struct sockaddr_in));
            assoc->last_seen = now;
            HASH_ADD(hh, comm->assoc, mac, sizeof(n2n_mac_t), assoc);
        } else {
            // already there, update socket and time only
            memcpy((struct sockaddr_in*)&(assoc->sock), sender_sock, sizeof(struct sockaddr_in));
            assoc->last_seen = now;
        }
    }
}


/** Determine the appropriate lifetime for new registrations.
 *
 *    If the supernode has been put into a pre-shutdown phase then this lifetime
 *    should not allow registrations to continue beyond the shutdown point.
 */
static uint16_t reg_lifetime (n2n_sn_t *sss) {

    /* NOTE: UDP firewalls usually have a 30 seconds timeout */
    return 15;
}


/** Verifies authentication tokens from known edges.
 *
 *  It is called by update_edge and during UNREGISTER_SUPER handling
 *  to verify the stored auth token.
 */
static int auth_edge (const n2n_auth_t *present, const n2n_auth_t *presented, n2n_auth_t *answer, struct sn_community *community) {

    sn_user_t *user = NULL;

    if(present->scheme == n2n_auth_none) {
        // n2n_auth_none scheme (set at supernode if cli option '-M')
        // if required, zero_token answer (not for NAK)
        if(answer)
            memset(answer, 0, sizeof(n2n_auth_t));
        // 0 == (always) successful
        return 0;
    }

    if((present->scheme == n2n_auth_simple_id) && (presented->scheme == n2n_auth_simple_id)) {
        // n2n_auth_simple_id scheme: if required, zero_token answer (not for NAK)
        if(answer)
            memset(answer, 0, sizeof(n2n_auth_t));

        // 0 = success (tokens are equal)
        return (memcmp(present, presented, sizeof(n2n_auth_t)));
    }

    if((present->scheme == n2n_auth_user_password) && (presented->scheme == n2n_auth_user_password)) {
        // check if submitted public key is in list of allowed users
        HASH_FIND(hh, community->allowed_users, &presented->token, sizeof(n2n_private_public_key_t), user);
        if(user) {
            if(answer) {
                memcpy(answer, presented, sizeof(n2n_auth_t));

                // return a double-encrypted challenge (just encrypt again) in the (first half of) public key field so edge can verify
                memcpy(answer->token, answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, N2N_AUTH_CHALLENGE_SIZE);
                speck_128_encrypt(answer->token, (speck_context_t*)user->shared_secret_ctx);

                // decrypt the challenge using user's shared secret
                speck_128_decrypt(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
                // xor-in the community dynamic key
                memxor(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, community->dynamic_key, N2N_AUTH_CHALLENGE_SIZE);
                // xor-in the user's shared secret
                memxor(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, user->shared_secret, N2N_AUTH_CHALLENGE_SIZE);
                // encrypt it using user's shared secret
                speck_128_encrypt(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
                // user in list? success! (we will see if edge can handle the key for further com)
            }
            return 0;
        }
    }

    // if not successful earlier: failure
    return -1;
}


// provides the current / a new local auth token
// REVISIT: behavior should depend on some local auth scheme setting (to be implemented)
static int get_local_auth (n2n_sn_t *sss, n2n_auth_t *auth) {

    // n2n_auth_simple_id scheme
    memcpy(auth, &(sss->auth), sizeof(n2n_auth_t));

    return 0;
}


// handles an incoming (remote) auth token from a so far unknown edge,
// takes action as required by auth scheme, and
// could provide an answer auth token for use in REGISTER_SUPER_ACK
static int handle_remote_auth (n2n_sn_t *sss, const n2n_auth_t *remote_auth,
                                              n2n_auth_t *answer_auth,
                                              struct sn_community *community) {

    sn_user_t *user = NULL;

    if((NULL == community->allowed_users) != (remote_auth->scheme != n2n_auth_user_password)) {
        // received token's scheme does not match expected scheme
        return -1;
    }

    switch(remote_auth->scheme) {
        // we do not handle n2n_auth_none because the edge always edge always uses either id or user/password
        // auth_none is sn-internal only (skipping MAC/IP address spoofing protection)
        case n2n_auth_none:
        case n2n_auth_simple_id:
            // zero_token answer
            memset(answer_auth, 0, sizeof(n2n_auth_t));
            return 0;
        case n2n_auth_user_password:
            // check if submitted public key is in list of allowed users
            HASH_FIND(hh, community->allowed_users, &remote_auth->token, sizeof(n2n_private_public_key_t), user);
            if(user) {
                memcpy(answer_auth, remote_auth, sizeof(n2n_auth_t));

                // return a double-encrypted challenge (just encrypt again) in the (first half of) public key field so edge can verify
                memcpy(answer_auth->token, answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, N2N_AUTH_CHALLENGE_SIZE);
                speck_128_encrypt(answer_auth->token, (speck_context_t*)user->shared_secret_ctx);

                // wrap dynamic key for transmission
                // decrypt the challenge using user's shared secret
                speck_128_decrypt(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
                // xor-in the community dynamic key
                memxor(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, community->dynamic_key, N2N_AUTH_CHALLENGE_SIZE);
                // xor-in the user's shared secret
                memxor(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, user->shared_secret, N2N_AUTH_CHALLENGE_SIZE);
                // encrypt it using user's shared secret
                speck_128_encrypt(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
                return 0;
            }
            break;
        default:
            break;
    }

    // if not successful earlier: failure
    return -1;
}


/** Update the edge table with the details of the edge which contacted the
 *    supernode. */
static int update_edge (n2n_sn_t *sss,
                        const n2n_common_t* cmn,
                        const n2n_REGISTER_SUPER_t* reg,
                        struct sn_community *comm,
                        const n2n_sock_t *sender_sock,
                        const SOCKET socket_fd,
                        n2n_auth_t *answer_auth,
                        int skip_add,
                        time_t now) {

    macstr_t mac_buf;
    n2n_sock_str_t sockbuf;
    struct peer_info *scan, *iter, *tmp;
    int ret;

    traceEvent(TRACE_DEBUG, "update_edge for %s [%s]",
               macaddr_str(mac_buf, reg->edgeMac),
               sock_to_cstr(sockbuf, sender_sock));

    HASH_FIND_PEER(comm->edges, reg->edgeMac, scan);

    // if unknown, make sure it is also not known by IP address
    if(NULL == scan) {
        HASH_ITER(hh,comm->edges,iter,tmp) {
            if(iter->dev_addr.net_addr == reg->dev_addr.net_addr) {
                scan = iter;
                HASH_DEL(comm->edges, scan);
                memcpy(scan->mac_addr, reg->edgeMac, sizeof(n2n_mac_t));
                HASH_ADD_PEER(comm->edges, scan);
                break;
            }
        }
    }

    if(NULL == scan) {
    /* Not known */
        if(handle_remote_auth(sss, &(reg->auth), answer_auth, comm) == 0) {
            if(skip_add == SN_ADD) {
                scan = (struct peer_info *) calloc(1, sizeof(struct peer_info)); /* deallocated in purge_expired_nodes */
                memcpy(&(scan->mac_addr), reg->edgeMac, sizeof(n2n_mac_t));
                scan->dev_addr.net_addr = reg->dev_addr.net_addr;
                scan->dev_addr.net_bitlen = reg->dev_addr.net_bitlen;
                memcpy((char*)scan->dev_desc, reg->dev_desc, N2N_DESC_SIZE);
                memcpy(&(scan->sock), sender_sock, sizeof(n2n_sock_t));
                scan->socket_fd = socket_fd;
                scan->last_cookie = reg->cookie;
                scan->last_valid_time_stamp = initial_time_stamp();
                // eventually, store edge's preferred local socket from REGISTER_SUPER
                if(cmn->flags & N2N_FLAGS_SOCKET)
                    memcpy(&scan->preferred_sock, &reg->sock, sizeof(n2n_sock_t));
                else
                    scan->preferred_sock.family = AF_INVALID;

                // store the submitted auth token
                memcpy(&(scan->auth), &(reg->auth), sizeof(n2n_auth_t));
                // manually set to type 'auth_none' if cli option disables MAC/IP address spoofing protection
                // for id based auth communities. This will be obsolete when handling public keys only (v4.0?)
                if((reg->auth.scheme == n2n_auth_simple_id) && (sss->override_spoofing_protection))
                    scan->auth.scheme = n2n_auth_none;

                HASH_ADD_PEER(comm->edges, scan);

                traceEvent(TRACE_INFO, "created edge  %s ==> %s",
                           macaddr_str(mac_buf, reg->edgeMac),
                           sock_to_cstr(sockbuf, sender_sock));
            }
            ret = update_edge_new_sn;
        } else {
            traceEvent(TRACE_INFO, "authentication failed");
            ret = update_edge_auth_fail;
        }
    } else {
        /* Known */
        if(auth_edge(&(scan->auth), &(reg->auth), answer_auth, comm) == 0) {
            if(!sock_equal(sender_sock, &(scan->sock))) {
                memcpy(&(scan->sock), sender_sock, sizeof(n2n_sock_t));
                scan->socket_fd = socket_fd;
                scan->last_cookie = reg->cookie;
                // eventually, update edge's preferred local socket from REGISTER_SUPER
                if(cmn->flags & N2N_FLAGS_SOCKET)
                    memcpy(&scan->preferred_sock, &reg->sock, sizeof(n2n_sock_t));
                else
                    scan->preferred_sock.family = AF_INVALID;

                traceEvent(TRACE_INFO, "updated edge  %s ==> %s",
                           macaddr_str(mac_buf, reg->edgeMac),
                           sock_to_cstr(sockbuf, sender_sock));
                ret = update_edge_sock_change;
            } else {
                scan->last_cookie = reg->cookie;

                traceEvent(TRACE_DEBUG, "edge unchanged %s ==> %s",
                           macaddr_str(mac_buf, reg->edgeMac),
                           sock_to_cstr(sockbuf, sender_sock));

                ret = update_edge_no_change;
            }
        } else {
            traceEvent(TRACE_INFO, "authentication failed");
            ret = update_edge_auth_fail;
        }
    }

    if((scan != NULL) && (ret != update_edge_auth_fail)) {
        scan->last_seen = now;
    }

    return ret;
}


/** checks if a certain ip address is still available, i.e. not used by any other edge of a given community */
static int ip_addr_available (struct sn_community *comm, n2n_ip_subnet_t *ip_addr) {

    int success = 1;
    struct peer_info *peer, *tmp_peer;

    // prerequisite: list of peers is sorted according to peer's tap ip address
    HASH_ITER(hh, comm->edges, peer, tmp_peer) {
        if(peer->dev_addr.net_addr  > ip_addr->net_addr) {
            break;
        }
        if(peer->dev_addr.net_addr == ip_addr->net_addr) {
            success = 0;
            break;
        }
    }

    return success;
}


static signed int peer_tap_ip_sort (struct peer_info *a, struct peer_info *b) {

    uint32_t a_host_id = a->dev_addr.net_addr & (~bitlen2mask(a->dev_addr.net_bitlen));
    uint32_t b_host_id = b->dev_addr.net_addr & (~bitlen2mask(b->dev_addr.net_bitlen));

    return ((signed int)a_host_id - (signed int)b_host_id);
}


/** The IP address assigned to the edge by the auto ip address function of sn. */
static int assign_one_ip_addr (struct sn_community *comm, n2n_desc_t dev_desc, n2n_ip_subnet_t *ip_addr) {

    uint32_t tmp, success, net_id, mask, max_host, host_id = 1;
    dec_ip_bit_str_t ip_bit_str = {'\0'};

    mask = bitlen2mask(comm->auto_ip_net.net_bitlen);
    net_id = comm->auto_ip_net.net_addr & mask;
    max_host = ~mask;

    // sorting is a prerequisite for more efficient availabilitiy check
    HASH_SORT(comm->edges, peer_tap_ip_sort);

    // first proposal derived from hash of mac address
    tmp = pearson_hash_32(dev_desc, sizeof(n2n_desc_t)) & max_host;
    if(tmp == 0)        tmp++; /* avoid 0 host */
    if(tmp == max_host) tmp--; /* avoid broadcast address */
    tmp |= net_id;

    // candidate
    ip_addr->net_bitlen = comm->auto_ip_net.net_bitlen;

    // check for availability starting from proposal, then downwards, ...
    for(host_id = tmp; host_id > net_id; host_id--) {
        ip_addr->net_addr = host_id;
        success = ip_addr_available(comm, ip_addr);
        if(success) {
            break;
        }
    }
    // ... then upwards
    if(!success) {
        for(host_id = tmp + 1; host_id < (net_id + max_host); host_id++) {
            ip_addr->net_addr = host_id;
            success = ip_addr_available(comm, ip_addr);
            if(success) {
                break;
            }
        }
    }

    if(success) {
        traceEvent(TRACE_INFO, "assign IP %s to tap adapter of edge", ip_subnet_to_str(ip_bit_str, ip_addr));
        return 0;
    } else {
        traceEvent(TRACE_WARNING, "no assignable IP to edge tap adapter");
        return -1;
    }
}


/** checks if a certain sub-network is still available, i.e. does not cut any other community's sub-network */
int subnet_available (n2n_sn_t *sss,
                      struct sn_community *comm,
                      uint32_t net_id,
                      uint32_t mask) {

    struct sn_community *cmn, *tmpCmn;
    int success = 1;

    HASH_ITER(hh, sss->communities, cmn, tmpCmn) {
        if(cmn == comm) {
            continue;
        }
        if(cmn->is_federation == IS_FEDERATION) {
            continue;
        }
        if((net_id <= (cmn->auto_ip_net.net_addr + ~bitlen2mask(cmn->auto_ip_net.net_bitlen)))
           &&(net_id + ~mask >= cmn->auto_ip_net.net_addr)) {
            success = 0;
            break;
        }
    }

    return success;
}


/** The IP address range (subnet) assigned to the community by the auto ip address function of sn. */
int assign_one_ip_subnet (n2n_sn_t *sss,
                          struct sn_community *comm) {

    uint32_t net_id, net_id_i, mask, net_increment;
    uint32_t no_subnets;
    uint8_t success;
    in_addr_t net;

    mask = bitlen2mask(sss->min_auto_ip_net.net_bitlen);
    // number of possible sub-networks
    no_subnets   = (sss->max_auto_ip_net.net_addr - sss->min_auto_ip_net.net_addr);
    no_subnets >>= (32 - sss->min_auto_ip_net.net_bitlen);
    no_subnets  += 1;

    // proposal for sub-network to choose
    net_id    = pearson_hash_32((const uint8_t *)comm->community, N2N_COMMUNITY_SIZE) % no_subnets;
    net_id    = sss->min_auto_ip_net.net_addr + (net_id << (32 - sss->min_auto_ip_net.net_bitlen));

    // check for availability starting from net_id, then downwards, ...
    net_increment = (~mask+1);
    for(net_id_i = net_id; net_id_i >= sss->min_auto_ip_net.net_addr; net_id_i -= net_increment) {
        success = subnet_available(sss, comm, net_id_i, mask);
        if(success) {
            break;
        }
    }
    // ... then upwards
    if(!success) {
        for(net_id_i = net_id + net_increment; net_id_i <= sss->max_auto_ip_net.net_addr; net_id_i += net_increment) {
            success = subnet_available(sss, comm, net_id_i, mask);
            if(success) {
                break;
            }
        }
    }

    if(success) {
        comm->auto_ip_net.net_addr = net_id_i;
        comm->auto_ip_net.net_bitlen = sss->min_auto_ip_net.net_bitlen;
        net = htonl(comm->auto_ip_net.net_addr);
        traceEvent(TRACE_INFO, "assigned sub-network %s/%u to community '%s'",
                   inet_ntoa(*(struct in_addr *) &net),
                   comm->auto_ip_net.net_bitlen,
                   comm->community);
        return 0;
    } else {
        comm->auto_ip_net.net_addr = 0;
        comm->auto_ip_net.net_bitlen = 0;
        traceEvent(TRACE_WARNING, "no assignable sub-network left for community '%s'",
                   comm->community);
        return -1;
    }
}


/***
 *
 * For a given packet, find the apporopriate internal last valid time stamp for lookup
 * and verify it (and also update, if applicable).
 */
static int find_edge_time_stamp_and_verify (struct peer_info * edges,
                                            peer_info_t *sn, n2n_mac_t mac,
                                            uint64_t stamp, int allow_jitter) {

    uint64_t *previous_stamp = NULL;

    if(sn) {
        previous_stamp = &(sn->last_valid_time_stamp);
    } else {
        struct peer_info *edge;
        HASH_FIND_PEER(edges, mac, edge);

        if(edge) {
            // time_stamp_verify_and_update allows the pointer a previous stamp to be NULL
            // if it is a (so far) unknown edge
            previous_stamp = &(edge->last_valid_time_stamp);
        }
    }

    // failure --> 0;    success --> 1
    return time_stamp_verify_and_update(stamp, previous_stamp, allow_jitter);
}


static int re_register_and_purge_supernodes (n2n_sn_t *sss, struct sn_community *comm, time_t *p_last_re_reg_and_purge, time_t now, uint8_t forced) {

    time_t time;
    struct peer_info *peer, *tmp;

    if(!forced) {
        if((now - (*p_last_re_reg_and_purge)) < RE_REG_AND_PURGE_FREQUENCY) {
            return 0;
        }

        // purge long-time-not-seen supernodes
        purge_expired_nodes(&(comm->edges), sss->sock, &sss->tcp_connections, p_last_re_reg_and_purge,
                            RE_REG_AND_PURGE_FREQUENCY, LAST_SEEN_SN_INACTIVE);
    }

    if(comm != NULL) {
        HASH_ITER(hh,comm->edges,peer,tmp) {

            time = now - peer->last_seen;

            if(!forced) {
                if(time <= LAST_SEEN_SN_ACTIVE) {
                    continue;
                }
            }

            /* re-register (send REGISTER_SUPER) */
            uint8_t pktbuf[N2N_PKT_BUF_SIZE] = {0};
            size_t idx;
            /* ssize_t sent; */
            n2n_common_t cmn;
            n2n_REGISTER_SUPER_t reg;
            n2n_sock_str_t sockbuf;

            memset(&cmn, 0, sizeof(cmn));
            memset(&reg, 0, sizeof(reg));

            cmn.ttl = N2N_DEFAULT_TTL;
            cmn.pc = n2n_register_super;
            cmn.flags = N2N_FLAGS_FROM_SUPERNODE;
            memcpy(cmn.community, comm->community, N2N_COMMUNITY_SIZE);

            reg.cookie = n2n_rand();
            peer->last_cookie = reg.cookie;

            reg.dev_addr.net_addr = ntohl(peer->dev_addr.net_addr);
            reg.dev_addr.net_bitlen = mask2bitlen(ntohl(peer->dev_addr.net_bitlen));
            get_local_auth(sss, &(reg.auth));

            reg.key_time = sss->dynamic_key_time;

            idx = 0;
            encode_mac(reg.edgeMac, &idx, sss->mac_addr);

            idx = 0;
            encode_REGISTER_SUPER(pktbuf, &idx, &cmn, &reg);

            traceEvent(TRACE_DEBUG, "send REGISTER_SUPER to %s",
                                     sock_to_cstr(sockbuf, &(peer->sock)));

            packet_header_encrypt(pktbuf, idx, idx,
                                  comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
                                  time_stamp());

            /* sent = */ sendto_peer(sss, peer, pktbuf, idx);
        }
    }

    return 0; /* OK */
}


static int purge_expired_communities (n2n_sn_t *sss,
                                      time_t* p_last_purge,
                                      time_t now) {

    struct sn_community *comm, *tmp_comm;
    node_supernode_association_t *assoc, *tmp_assoc;
    size_t num_reg = 0;
    size_t num_assoc = 0;

    if((now - (*p_last_purge)) < PURGE_REGISTRATION_FREQUENCY) {
        return 0;
    }

    traceEvent(TRACE_DEBUG, "purging old communities and edges");

    HASH_ITER(hh, sss->communities, comm, tmp_comm) {
        // federation is taken care of in re_register_and_purge_supernodes()
        if(comm->is_federation == IS_FEDERATION)
            continue;

        // purge the community's local peers
        num_reg += purge_peer_list(&comm->edges, sss->sock, &sss->tcp_connections, now - REGISTRATION_TIMEOUT);

        // purge the community's associated peers (connected to other supernodes)
        HASH_ITER(hh, comm->assoc, assoc, tmp_assoc) {
            if(comm->assoc->last_seen < (now - 3 * REGISTRATION_TIMEOUT)) {
                HASH_DEL(comm->assoc, assoc);
                free(assoc);
                num_assoc++;
            }
        }

        if((comm->edges == NULL) && (comm->purgeable == COMMUNITY_PURGEABLE)) {
            traceEvent(TRACE_INFO, "purging idle community %s", comm->community);
            if(NULL != comm->header_encryption_ctx_static) {
                /* this should not happen as 'purgeable' and thus only communities w/o encrypted header here */
                free(comm->header_encryption_ctx_static);
                free(comm->header_iv_ctx_static);
                free(comm->header_encryption_ctx_dynamic);
                free(comm->header_iv_ctx_dynamic);
            }
            // remove all associations
            HASH_ITER(hh, comm->assoc, assoc, tmp_assoc) {
                HASH_DEL(comm->assoc, assoc);
                free(assoc);
            }
            HASH_DEL(sss->communities, comm);
            free(comm);
        }
    }
    (*p_last_purge) = now;

    traceEvent(TRACE_DEBUG, "purge_expired_communities removed %ld locally registered edges and %ld remotely associated edges",
                            num_reg, num_assoc);

    return 0;
}


static int number_enc_packets_sort (struct sn_community *a, struct sn_community *b) {

    // comparison function for sorting communities in descending order of their
    // number_enc_packets-fields
    return (b->number_enc_packets - a->number_enc_packets);
}


static int sort_communities (n2n_sn_t *sss,
                             time_t* p_last_sort,
                             time_t now) {

    struct sn_community *comm, *tmp;

    if((now - (*p_last_sort)) < SORT_COMMUNITIES_INTERVAL) {
        return 0;
    }

    // this routine gets periodically called as defined in SORT_COMMUNITIES_INTERVAL
    // it sorts the communities in descending order of their number_enc_packets-fields...
    HASH_SORT(sss->communities, number_enc_packets_sort);

    // ... and afterward resets the number_enc__packets-fields to zero
    // (other models could reset it to half of their value to respect history)
    HASH_ITER(hh, sss->communities, comm, tmp) {
        comm->number_enc_packets = 0;
    }

    (*p_last_sort) = now;

    return 0;
}


/** Examine a datagram and determine what to do with it.
 *
 */
static int process_udp (n2n_sn_t * sss,
                        const struct sockaddr_in *sender_sock,
                        const SOCKET socket_fd,
                        uint8_t * udp_buf,
                        size_t udp_size,
                        time_t now) {

    n2n_common_t        cmn; /* common fields in the packet header */
    size_t              rem;
    size_t              idx;
    size_t              msg_type;
    uint8_t             from_supernode;
    peer_info_t         *sn = NULL;
    n2n_sock_t          sender;
    macstr_t            mac_buf;
    macstr_t            mac_buf2;
    n2n_sock_str_t      sockbuf;
    char                buf[32];
    uint8_t             hash_buf[16] = {0}; /* always size of 16 (max) despite the actual value of N2N_REG_SUP_HASH_CHECK_LEN (<= 16) */

    struct sn_community *comm, *tmp;
    uint32_t            header_enc = 0; /* 1 == encrypted by static key, 2 == encrypted by dynamic key */
    uint64_t            stamp;
    int                 skip_add;
    time_t              any_time = 0;

    traceEvent(TRACE_DEBUG, "processing incoming UDP packet [len: %lu][sender: %s:%u]",
               udp_size, intoa(ntohl(sender_sock->sin_addr.s_addr), buf, sizeof(buf)),
               ntohs(sender_sock->sin_port));

    /* check if header is unencrypted. the following check is around 99.99962 percent reliable.
     * it heavily relies on the structure of packet's common part
     * changes to wire.c:encode/decode_common need to go together with this code */
    if(udp_size < 24) {
        traceEvent(TRACE_DEBUG, "dropped a packet too short to be valid");
        return -1;
    }
    if((udp_buf[23] == (uint8_t)0x00) // null terminated community name
       && (udp_buf[00] == N2N_PKT_VERSION) // correct packet version
       && ((be16toh(*(uint16_t*)&(udp_buf[02])) & N2N_FLAGS_TYPE_MASK) <= MSG_TYPE_MAX_TYPE) // message type
       && ( be16toh(*(uint16_t*)&(udp_buf[02])) < N2N_FLAGS_OPTIONS) // flags
       ) {
        /* most probably unencrypted */
        /* make sure, no downgrading happens here and no unencrypted packets can be
         * injected in a community which definitely deals with encrypted headers */
        HASH_FIND_COMMUNITY(sss->communities, (char *)&udp_buf[04], comm);
        if(comm) {
            if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                traceEvent(TRACE_DEBUG, "dropped a packet with unencrypted header "
                           "addressed to community '%s' which uses encrypted headers",
                           comm->community);
                return -1;
            }
            if(comm->header_encryption == HEADER_ENCRYPTION_UNKNOWN) {
                traceEvent(TRACE_INFO, "locked community '%s' to "
                           "unencrypted headers", comm->community);
                /* set 'no encryption' in case it is not set yet */
                comm->header_encryption = HEADER_ENCRYPTION_NONE;
                comm->header_encryption_ctx_static = NULL;
                comm->header_encryption_ctx_dynamic = NULL;
            }
        }
    } else {
        /* most probably encrypted */
        /* cycle through the known communities (as keys) to eventually decrypt */
        HASH_ITER(hh, sss->communities, comm, tmp) {
            /* skip the definitely unencrypted communities */
            if(comm->header_encryption == HEADER_ENCRYPTION_NONE) {
                continue;
            }

            // match with static (1) or dynamic (2) ctx?
            // check dynamic first as it is identical to static in normal header encryption mode
            if(packet_header_decrypt(udp_buf, udp_size,
                                     comm->community,
                                     comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
                                     &stamp)) {
                    header_enc = 2;
            }
            if(!header_enc) {
                pearson_hash_128(hash_buf, udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN));
                header_enc = packet_header_decrypt(udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), comm->community,
                                                   comm->header_encryption_ctx_static, comm->header_iv_ctx_static, &stamp);
            }

            if(header_enc) {
                // time stamp verification follows in the packet specific section as it requires to determine the
                // sender from the hash list by its MAC, this all depends on packet type and packet structure
                // (MAC is not always in the same place)

                if(comm->header_encryption == HEADER_ENCRYPTION_UNKNOWN) {
                    traceEvent(TRACE_INFO, "locked community '%s' to "
                               "encrypted headers", comm->community);
                    /* set 'encrypted' in case it is not set yet */
                    comm->header_encryption = HEADER_ENCRYPTION_ENABLED;
                }
                // count the number of encrypted packets for sorting the communities from time to time
                // for the HASH_ITER a few lines above gets faster for the more busy communities
                (comm->number_enc_packets)++;
                // no need to test further communities
                break;
            }
        }
        if(!header_enc) {
            // no matching key/community
            traceEvent(TRACE_DEBUG, "dropped a packet with seemingly encrypted header "
                       "for which no matching community which uses encrypted headers was found");
            return -1;
        }
    }

    /* Use decode_common() to determine the kind of packet then process it:
     *
     * REGISTER_SUPER adds an edge and generate a return REGISTER_SUPER_ACK
     *
     * REGISTER, REGISTER_ACK and PACKET messages are forwarded to their
     * destination edge. If the destination is not known then PACKETs are
     * broadcast.
     */

    rem = udp_size; /* Counts down bytes of packet to protect against buffer overruns. */
    idx = 0; /* marches through packet header as parts are decoded. */

    if(decode_common(&cmn, udp_buf, &rem, &idx) < 0) {
        traceEvent(TRACE_ERROR, "failed to decode common section");
        return -1; /* failed to decode packet */
    }

    msg_type = cmn.pc; /* packet code */

    // special case for user/pw auth
    // community's auth scheme and message type need to match the used key (dynamic)
    if(comm) {
        if((comm->allowed_users)
        && (msg_type != MSG_TYPE_REGISTER_SUPER)
        && (msg_type != MSG_TYPE_REGISTER_SUPER_ACK)
        && (msg_type != MSG_TYPE_REGISTER_SUPER_NAK)) {
            if(header_enc != 2) {
                traceEvent(TRACE_WARNING, "dropped packet encrypted with static key where expecting dynamic key");
                return -1;
            }
        }
    }

    /* REVISIT: when UDP/IPv6 is supported we will need a flag to indicate which
     * IP transport version the packet arrived on. May need to UDP sockets. */
    memset(&sender, 0, sizeof(n2n_sock_t));
    sender.family = AF_INET; /* UDP socket was opened PF_INET v4 */
    sender.port = ntohs(sender_sock->sin_port);
    memcpy(&(sender.addr.v4), &(sender_sock->sin_addr.s_addr), IPV4_SIZE);

    from_supernode = cmn.flags & N2N_FLAGS_FROM_SUPERNODE;
    if(from_supernode) {
        skip_add = SN_ADD_SKIP;
        sn = add_sn_to_list_by_mac_or_sock (&(sss->federation->edges), &sender, null_mac, &skip_add);
        // only REGISTER_SUPER allowed from unknown supernodes
        if((!sn) && (msg_type != MSG_TYPE_REGISTER_SUPER)) {
            traceEvent(TRACE_DEBUG, "dropped incoming data from unknown supernode");
            return -1;
        }
    }

    if(cmn.ttl < 1) {
        traceEvent(TRACE_WARNING, "expired TTL");
        return 0; /* Don't process further */
    }

    --(cmn.ttl); /* The value copied into all forwarded packets. */

    switch(msg_type) {
        case MSG_TYPE_PACKET: {
            /* PACKET from one edge to another edge via supernode. */

            /* pkt will be modified in place and recoded to an output of potentially
             * different size due to addition of the socket.*/
            n2n_PACKET_t  pkt;
            n2n_common_t  cmn2;
            uint8_t       encbuf[N2N_SN_PKTBUF_SIZE];
            size_t        encx = 0;
            int           unicast; /* non-zero if unicast */
            uint8_t *     rec_buf; /* either udp_buf or encbuf */

            if(!comm) {
                traceEvent(TRACE_DEBUG, "PACKET with unknown community %s", cmn.community);
                return -1;
            }

            sss->stats.last_fwd = now;
            decode_PACKET(&pkt, &cmn, udp_buf, &rem, &idx);

            // already checked for valid comm
            if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                if(!find_edge_time_stamp_and_verify(comm->edges, sn, pkt.srcMac, stamp, TIME_STAMP_ALLOW_JITTER)) {
                    traceEvent(TRACE_DEBUG, "dropped PACKET due to time stamp error");
                    return -1;
                }
            }

            unicast = (0 == is_multi_broadcast(pkt.dstMac));

            traceEvent(TRACE_DEBUG, "RX PACKET (%s) %s -> %s %s",
                       (unicast ? "unicast" : "multicast"),
                       macaddr_str(mac_buf, pkt.srcMac),
                       macaddr_str(mac_buf2, pkt.dstMac),
                       (from_supernode ? "from sn" : "local"));

            if(!from_supernode) {
                memcpy(&cmn2, &cmn, sizeof(n2n_common_t));

                /* We are going to add socket even if it was not there before */
                cmn2.flags |= N2N_FLAGS_SOCKET | N2N_FLAGS_FROM_SUPERNODE;

                pkt.sock.family = AF_INET;
                pkt.sock.port = ntohs(sender_sock->sin_port);
                memcpy(pkt.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);

                rec_buf = encbuf;
                /* Re-encode the header. */
                encode_PACKET(encbuf, &encx, &cmn2, &pkt);

                uint16_t oldEncx = encx;

                /* Copy the original payload unchanged */
                encode_buf(encbuf, &encx, (udp_buf + idx), (udp_size - idx));

                if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                    // in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size
                    packet_header_encrypt(rec_buf, oldEncx + (NULL != comm->allowed_users) * min(encx - oldEncx, N2N_SPECK_IVEC_SIZE), encx,
                                          comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
                                          time_stamp());
                }
            } else {
                /* Already from a supernode. Nothing to modify, just pass to
                 * destination. */

                traceEvent(TRACE_DEBUG, "Rx PACKET fwd unmodified");

                rec_buf = udp_buf;
                encx = udp_size;

                if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                    // in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size
                    packet_header_encrypt(rec_buf, idx + (NULL != comm->allowed_users) * min(encx - idx, N2N_SPECK_IVEC_SIZE), encx,
                                          comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
                                          time_stamp());
                }
            }

            /* Common section to forward the final product. */
            if(unicast) {
                try_forward(sss, comm, &cmn, pkt.dstMac, from_supernode, rec_buf, encx, now);
            } else {
                try_broadcast(sss, comm, &cmn, pkt.srcMac, from_supernode, rec_buf, encx, now);
            }
            break;
        }

        case MSG_TYPE_REGISTER: {
            /* Forwarding a REGISTER from one edge to the next */

            n2n_REGISTER_t  reg;
            n2n_common_t    cmn2;
            uint8_t         encbuf[N2N_SN_PKTBUF_SIZE];
            size_t          encx = 0;
            int             unicast; /* non-zero if unicast */
            uint8_t *       rec_buf; /* either udp_buf or encbuf */

            if(!comm) {
                traceEvent(TRACE_DEBUG, "REGISTER from unknown community %s", cmn.community);
                return -1;
            }

            sss->stats.last_fwd = now;
            decode_REGISTER(&reg, &cmn, udp_buf, &rem, &idx);

            // already checked for valid comm
            if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                if(!find_edge_time_stamp_and_verify(comm->edges, sn, reg.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
                    traceEvent(TRACE_DEBUG, "dropped REGISTER due to time stamp error");
                    return -1;
                }
            }

            unicast = (0 == is_multi_broadcast(reg.dstMac));

            if(unicast) {
                traceEvent(TRACE_DEBUG, "Rx REGISTER %s -> %s %s",
                           macaddr_str(mac_buf, reg.srcMac),
                           macaddr_str(mac_buf2, reg.dstMac),
                           ((cmn.flags & N2N_FLAGS_FROM_SUPERNODE) ? "from sn" : "local"));

                if(0 == (cmn.flags & N2N_FLAGS_FROM_SUPERNODE)) {
                    memcpy(&cmn2, &cmn, sizeof(n2n_common_t));

                    /* We are going to add socket even if it was not there before */
                    cmn2.flags |= N2N_FLAGS_SOCKET | N2N_FLAGS_FROM_SUPERNODE;

                    reg.sock.family = AF_INET;
                    reg.sock.port = ntohs(sender_sock->sin_port);
                    memcpy(reg.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);

                    /* Re-encode the header. */
                    encode_REGISTER(encbuf, &encx, &cmn2, &reg);

                    rec_buf = encbuf;
                } else {
                    /* Already from a supernode. Nothing to modify, just pass to
                     * destination. */

                    rec_buf = udp_buf;
                    encx = udp_size;
                }

                if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                    packet_header_encrypt(rec_buf, encx, encx,
                                          comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
                                          time_stamp());
                }
                try_forward(sss, comm, &cmn, reg.dstMac, from_supernode, rec_buf, encx, now); /* unicast only */
            } else {
                traceEvent(TRACE_ERROR, "Rx REGISTER with multicast destination");
            }
            break;
        }

        case MSG_TYPE_REGISTER_ACK: {
            traceEvent(TRACE_DEBUG, "Rx REGISTER_ACK (not implemented) should not be via supernode");
            break;
        }

        case MSG_TYPE_REGISTER_SUPER: {
            n2n_REGISTER_SUPER_t                   reg;
            n2n_REGISTER_SUPER_ACK_t               ack;
            n2n_REGISTER_SUPER_NAK_t               nak;
            n2n_common_t                           cmn2;
            uint8_t                                ackbuf[N2N_SN_PKTBUF_SIZE];
            uint8_t                                payload_buf[REG_SUPER_ACK_PAYLOAD_SPACE];
            n2n_REGISTER_SUPER_ACK_payload_t       *payload;
            size_t                                 encx = 0;
            struct sn_community_regular_expression *re, *tmp_re;
            struct peer_info                       *peer, *tmp_peer, *p;
            int8_t                                 allowed_match = -1;
            uint8_t                                match = 0;
            int                                    match_length = 0;
            n2n_ip_subnet_t                        ipaddr;
            int                                    num = 0;
            int                                    skip;
            int                                    ret_value;
            sn_user_t                              *user = NULL;

            memset(&ack, 0, sizeof(n2n_REGISTER_SUPER_ACK_t));
            memset(&nak, 0, sizeof(n2n_REGISTER_SUPER_NAK_t));

            /* Edge/supernode requesting registration with us.    */
            sss->stats.last_reg_super=now;
            ++(sss->stats.reg_super);
            decode_REGISTER_SUPER(&reg, &cmn, udp_buf, &rem, &idx);

            if(comm) {
                if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                    if(!find_edge_time_stamp_and_verify(comm->edges, sn, reg.edgeMac, stamp, TIME_STAMP_NO_JITTER)) {
                        traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER due to time stamp error");
                        return -1;
                    }
                }
            }

            /*
                Before we move any further, we need to check if the requested
                community is allowed by the supernode. In case it is not we do
                not report any message back to the edge to hide the supernode
                existance (better from the security standpoint)
            */

            if(!comm && sss->lock_communities) {
                HASH_ITER(hh, sss->rules, re, tmp_re) {
                    allowed_match = re_matchp(re->rule, (const char *)cmn.community, &match_length);

                    if((allowed_match != -1)
                       && (match_length == strlen((const char *)cmn.community)) // --- only full matches allowed (remove, if also partial matches wanted)
                       && (allowed_match == 0)) { // --- only full matches allowed (remove, if also partial matches wanted)
                        match = 1;
                        break;
                    }
                }
                if(match != 1) {
                    traceEvent(TRACE_INFO, "discarded registration with unallowed community '%s'",
                               (char*)cmn.community);
                    return -1;
                }
            }

            if(!comm && (!sss->lock_communities || (match == 1))) {
                comm = (struct sn_community*)calloc(1, sizeof(struct sn_community));

                if(comm) {
                    comm_init(comm, (char *)cmn.community);
                    /* new communities introduced by REGISTERs could not have had encrypted header... */
                    comm->header_encryption = HEADER_ENCRYPTION_NONE;
                    comm->header_encryption_ctx_static = NULL;
                    comm->header_encryption_ctx_dynamic = NULL;
                    /* ... and also are purgeable during periodic purge */
                    comm->purgeable = COMMUNITY_PURGEABLE;
                    comm->number_enc_packets = 0;
                    HASH_ADD_STR(sss->communities, community, comm);

                    traceEvent(TRACE_INFO, "new community: %s", comm->community);
                    assign_one_ip_subnet(sss, comm);
                }
            }

            if(!comm) {
                traceEvent(TRACE_INFO, "discarded registration with unallowed community '%s'",
                                       (char*)cmn.community);
                return -1;
            }

            // hash check (user/pw auth only)
            if(comm->allowed_users) {
                // check if submitted public key is in list of allowed users
                HASH_FIND(hh, comm->allowed_users, &reg.auth.token, sizeof(n2n_private_public_key_t), user);
                if(user) {
                    speck_128_encrypt(hash_buf, (speck_context_t*)user->shared_secret_ctx);
                    if(memcmp(hash_buf, udp_buf + udp_size - N2N_REG_SUP_HASH_CHECK_LEN /* length has already been checked */, N2N_REG_SUP_HASH_CHECK_LEN)) {
                        traceEvent(TRACE_INFO, "Rx REGISTER_SUPER with wrong hash");
                        return -1;
                    }
                } else {
                    traceEvent(TRACE_INFO, "Rx REGISTER_SUPER from unknown user");
                    // continue and let auth check do the rest (otherwise, no NAK is sent)
               }
            }

            if(!memcmp(reg.edgeMac, sss->mac_addr, sizeof(n2n_mac_t))) {
                traceEvent(TRACE_DEBUG, "Rx REGISTER_SUPER from self, ignoring");
                return -1;
            }

            cmn2.ttl = N2N_DEFAULT_TTL;
            cmn2.pc = n2n_register_super_ack;
            cmn2.flags = N2N_FLAGS_SOCKET | N2N_FLAGS_FROM_SUPERNODE;
            memcpy(cmn2.community, cmn.community, sizeof(n2n_community_t));

            ack.cookie = reg.cookie;
            memcpy(ack.srcMac, sss->mac_addr, sizeof(n2n_mac_t));

            if(comm->is_federation != IS_FEDERATION) { /* alternatively, do not send zero tap ip address in federation REGISTER_SUPER */
                if((reg.dev_addr.net_addr == 0) || (reg.dev_addr.net_addr == 0xFFFFFFFF) || (reg.dev_addr.net_bitlen == 0) ||
                   ((reg.dev_addr.net_addr & 0xFFFF0000) == 0xA9FE0000 /* 169.254.0.0 */)) {
                    memset(&ipaddr, 0, sizeof(n2n_ip_subnet_t));
                    assign_one_ip_addr(comm, reg.dev_desc, &ipaddr);
                    ack.dev_addr.net_addr = ipaddr.net_addr;
                    ack.dev_addr.net_bitlen = ipaddr.net_bitlen;
                }
            }

            ack.lifetime = reg_lifetime(sss);

            ack.sock.family = AF_INET;
            ack.sock.port = ntohs(sender_sock->sin_port);
            memcpy(ack.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);

            /* Add sender's data to federation (or update it) */
            if(comm->is_federation == IS_FEDERATION) {
                skip_add = SN_ADD;
                p = add_sn_to_list_by_mac_or_sock(&(sss->federation->edges), &(ack.sock), reg.edgeMac, &skip_add);
                p->last_seen = now;
                // communication with other supernodes happens via standard udp port
                p->socket_fd = sss->sock;
            }

            /* Skip random numbers of supernodes before payload assembling, calculating an appropriate random_number.
             * That way, all supernodes have a chance to be propagated with REGISTER_SUPER_ACK. */
            skip = HASH_COUNT(sss->federation->edges) - (int)(REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE / REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE);
            skip = (skip < 0) ? 0 : n2n_rand_sqr(skip);

            /* Assembling supernode list for REGISTER_SUPER_ACK payload */
            payload = (n2n_REGISTER_SUPER_ACK_payload_t*)payload_buf;
            HASH_ITER(hh, sss->federation->edges, peer, tmp_peer) {
                if(skip) {
                    skip--;
                    continue;
                }
                if(peer->sock.family == (uint8_t)AF_INVALID)
                    continue; /* do not add unresolved supernodes to payload */
                if(memcmp(&(peer->sock), &(ack.sock), sizeof(n2n_sock_t)) == 0) continue; /* a supernode doesn't add itself to the payload */
                if((now - peer->last_seen) >= LAST_SEEN_SN_NEW) continue;  /* skip long-time-not-seen supernodes.
                                                                            * We need to allow for a little extra time because supernodes sometimes exceed
                                                                            * their SN_ACTIVE time before they get re-registred to. */
                if(((++num)*REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE) > REG_SUPER_ACK_PAYLOAD_SPACE) break; /* no more space available in REGISTER_SUPER_ACK payload */
                memcpy(&(payload->sock), &(peer->sock), sizeof(n2n_sock_t));
                memcpy(payload->mac, peer->mac_addr, sizeof(n2n_mac_t));
                // shift to next payload entry
                payload++;
            }
            ack.num_sn = num;

            traceEvent(TRACE_DEBUG, "Rx REGISTER_SUPER for %s [%s]",
                       macaddr_str(mac_buf, reg.edgeMac),
                       sock_to_cstr(sockbuf, &(ack.sock)));

            // check authentication
            ret_value = update_edge_no_change;
            if(comm->is_federation != IS_FEDERATION) { /* REVISIT: auth among supernodes is not implemented yet */
                if(cmn.flags & N2N_FLAGS_FROM_SUPERNODE) {
                    ret_value = update_edge(sss, &cmn, &reg, comm, &(ack.sock), socket_fd, &(ack.auth), SN_ADD_SKIP, now);
                } else {
                    // do not add in case of null mac (edge asking for ip address)
                    ret_value = update_edge(sss, &cmn, &reg, comm, &(ack.sock), socket_fd, &(ack.auth), is_null_mac(reg.edgeMac) ? SN_ADD_SKIP : SN_ADD, now);
                }
            }

            if(ret_value == update_edge_auth_fail) {
                // send REGISTER_SUPER_NAK
                cmn2.pc = n2n_register_super_nak;
                nak.cookie = reg.cookie;
                memcpy(nak.srcMac, reg.edgeMac, sizeof(n2n_mac_t));

                encode_REGISTER_SUPER_NAK(ackbuf, &encx, &cmn2, &nak);

                if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                    packet_header_encrypt(ackbuf, encx, encx,
                                          comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
                                          time_stamp());
                    // if user-password-auth
                    if(comm->allowed_users) {
                        encode_buf(ackbuf, &encx, hash_buf /* no matter what content */, N2N_REG_SUP_HASH_CHECK_LEN);
                    }
                }
                sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, ackbuf, encx);

                traceEvent(TRACE_DEBUG, "Tx REGISTER_SUPER_NAK for %s",
                           macaddr_str(mac_buf, reg.edgeMac));
            } else {
                // if this is not already from a supernode ...
                // and not from federation, ...
                if((!(cmn.flags & N2N_FLAGS_FROM_SUPERNODE)) || (!(cmn.flags & N2N_FLAGS_SOCKET))) {
                    // ... forward to all other supernodes (note try_broadcast()'s behavior with
                    //     NULL comm and from_supernode parameter)
                    // exception: do not forward auto ip draw
                    if(!is_null_mac(reg.edgeMac)) {
                        reg.sock.family = AF_INET;
                        reg.sock.port = ntohs(sender_sock->sin_port);
                        memcpy(reg.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);

                        cmn2.pc = n2n_register_super;
                        encode_REGISTER_SUPER(ackbuf, &encx, &cmn2, &reg);

                        if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                            packet_header_encrypt(ackbuf, encx, encx,
                                                  comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
                                                  time_stamp());
                           // if user-password-auth
                           if(comm->allowed_users) {
                               // append an encrypted packet hash
                               pearson_hash_128(hash_buf, ackbuf, encx);
                               // same 'user' as above
                               speck_128_encrypt(hash_buf, (speck_context_t*)user->shared_secret_ctx);
                               encode_buf(ackbuf, &encx, hash_buf, N2N_REG_SUP_HASH_CHECK_LEN);
                            }
                        }

                        try_broadcast(sss, NULL, &cmn, reg.edgeMac, from_supernode, ackbuf, encx, now);
                    }

                    // dynamic key time handling if appropriate
                    ack.key_time = 0;
                    if(comm->is_federation == IS_FEDERATION) {
                        if(reg.key_time > sss->dynamic_key_time) {
                            traceEvent(TRACE_DEBUG, "setting new key time");
                            // have all edges re_register (using old dynamic key)
                            send_re_register_super(sss);
                            // set new key time
                            sss->dynamic_key_time = reg.key_time;
                            // calculate new dynamic keys for all communities
                            calculate_dynamic_keys(sss);
                            // force re-register with all supernodes
                            re_register_and_purge_supernodes(sss, sss->federation, &any_time, now, 1 /* forced */);
                        }
                        ack.key_time = sss->dynamic_key_time;
                    }

                    // send REGISTER_SUPER_ACK
                    encx = 0;
                    cmn2.pc = n2n_register_super_ack;

                    encode_REGISTER_SUPER_ACK(ackbuf, &encx, &cmn2, &ack, payload_buf);

                    if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                        packet_header_encrypt(ackbuf, encx, encx,
                                              comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
                                              time_stamp());
                       // if user-password-auth
                       if(comm->allowed_users) {
                           // append an encrypted packet hash
                           pearson_hash_128(hash_buf, ackbuf, encx);
                           // same 'user' as above
                           speck_128_encrypt(hash_buf, (speck_context_t*)user->shared_secret_ctx);
                           encode_buf(ackbuf, &encx, hash_buf, N2N_REG_SUP_HASH_CHECK_LEN);
                        }
                    }

                    sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, ackbuf, encx);

                    traceEvent(TRACE_DEBUG, "Tx REGISTER_SUPER_ACK for %s [%s]",
                               macaddr_str(mac_buf, reg.edgeMac),
                               sock_to_cstr(sockbuf, &(ack.sock)));
                } else {
                    // this is an edge with valid authentication registering with another supernode, so ...
                    // 1- ... associate it with that other supernode
                    update_node_supernode_association(comm, &(reg.edgeMac), sender_sock, now);
                    // 2- ... we can delete it from regular list if present (can happen)
                    HASH_FIND_PEER(comm->edges, reg.edgeMac, peer);
                    if(peer != NULL) {
                        if((peer->socket_fd != sss->sock) && (peer->socket_fd >= 0)) {
                            n2n_tcp_connection_t *conn;
                            HASH_FIND_INT(sss->tcp_connections, &(peer->socket_fd), conn);
                            close_tcp_connection(sss, conn); /* also deletes the peer */
                        } else {
                            HASH_DEL(comm->edges, peer);
                            free(peer);
                        }
                    }
                }
            }

            break;
        }

        case MSG_TYPE_UNREGISTER_SUPER: {
            n2n_UNREGISTER_SUPER_t unreg;
            struct peer_info       *peer;
            int                    auth;


            memset(&unreg, 0, sizeof(n2n_UNREGISTER_SUPER_t));

            if(!comm) {
                traceEvent(TRACE_DEBUG, "dropped UNREGISTER_SUPER with unknown community %s", cmn.community);
                return -1;
            }

            if((from_supernode == 1) || (comm->is_federation == IS_FEDERATION)) {
                traceEvent(TRACE_DEBUG, "dropped UNREGISTER_SUPER: should not come from a supernode or federation.");
                return -1;
            }

            decode_UNREGISTER_SUPER(&unreg, &cmn, udp_buf, &rem, &idx);

            if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                if(!find_edge_time_stamp_and_verify(comm->edges, sn, unreg.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
                    traceEvent(TRACE_DEBUG, "dropped UNREGISTER_SUPER due to time stamp error");
                    return -1;
                }
            }

            traceEvent(TRACE_DEBUG, "Rx UNREGISTER_SUPER from %s",
                       macaddr_str(mac_buf, unreg.srcMac));

            HASH_FIND_PEER(comm->edges, unreg.srcMac, peer);
            if(peer != NULL) {
                if((auth = auth_edge(&(peer->auth), &unreg.auth, NULL, comm)) == 0) {
                    if((peer->socket_fd != sss->sock) && (peer->socket_fd >= 0)) {
                        n2n_tcp_connection_t *conn;
                        HASH_FIND_INT(sss->tcp_connections, &(peer->socket_fd), conn);
                        close_tcp_connection(sss, conn); /* also deletes the peer */
                    } else {
                        HASH_DEL(comm->edges, peer);
                        free(peer);
                    }
                }
            }
            break;
        }

        case MSG_TYPE_REGISTER_SUPER_ACK: {
            n2n_REGISTER_SUPER_ACK_t         ack;
            struct peer_info                 *scan, *tmp;
            n2n_sock_str_t                   sockbuf1;
            n2n_sock_str_t                   sockbuf2;
            macstr_t                         mac_buf1;
            n2n_sock_t                       sender;
            n2n_sock_t                       *orig_sender;
            int                              i;
            uint8_t                          dec_tmpbuf[REG_SUPER_ACK_PAYLOAD_SPACE];
            n2n_REGISTER_SUPER_ACK_payload_t *payload;

            memset(&sender, 0, sizeof(n2n_sock_t));
            sender.family = AF_INET;
            sender.port = ntohs(sender_sock->sin_port);
            memcpy(&(sender.addr.v4), &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
            orig_sender = &sender;

            memset(&ack, 0, sizeof(n2n_REGISTER_SUPER_ACK_t));

            if(!comm) {
                traceEvent(TRACE_DEBUG, "REGISTER_SUPER_ACK with unknown community %s", cmn.community);
                return -1;
            }

            if((from_supernode == 0) || (comm->is_federation == IS_NO_FEDERATION)) {
                traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER_ACK, should not come from an edge or regular community");
                return -1;
            }

            decode_REGISTER_SUPER_ACK(&ack, &cmn, udp_buf, &rem, &idx, dec_tmpbuf);
            orig_sender = &(ack.sock);

            if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                if(!find_edge_time_stamp_and_verify(comm->edges, sn, ack.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
                    traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER_ACK due to time stamp error");
                    return -1;
                }
            }

            traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_ACK from MAC %s [%s] (external %s)",
                       macaddr_str(mac_buf1, ack.srcMac),
                       sock_to_cstr(sockbuf1, &sender),
                       sock_to_cstr(sockbuf2, orig_sender));

            skip_add = SN_ADD_SKIP;
            scan = add_sn_to_list_by_mac_or_sock(&(sss->federation->edges), &sender, ack.srcMac, &skip_add);
            if(scan != NULL) {
                scan->last_seen = now;
            } else {
                traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER_ACK due to an unknown supernode");
                break;
            }

            if(ack.cookie == scan->last_cookie) {

                payload = (n2n_REGISTER_SUPER_ACK_payload_t *)dec_tmpbuf;
                for(i = 0; i < ack.num_sn; i++) {
                    skip_add = SN_ADD;
                    tmp = add_sn_to_list_by_mac_or_sock(&(sss->federation->edges), &(payload->sock), payload->mac, &skip_add);
                    // other supernodes communicate via standard udp socket
                    tmp->socket_fd = sss->sock;

                    if(skip_add == SN_ADD_ADDED) {
                        tmp->last_seen = now - LAST_SEEN_SN_NEW;
                    }

                    // shift to next payload entry
                    payload++;
                }

                if(ack.key_time > sss->dynamic_key_time) {
                    traceEvent(TRACE_DEBUG, "setting new key time");
                    // have all edges re_register (using old dynamic key)
                    send_re_register_super(sss);
                    // set new key time
                    sss->dynamic_key_time = ack.key_time;
                    // calculate new dynamic keys for all communities
                    calculate_dynamic_keys(sss);
                    // force re-register with all supernodes
                    re_register_and_purge_supernodes(sss, sss->federation, &any_time, now, 1 /* forced */);
               }

            } else {
                traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_ACK with wrong or old cookie");
            }
            break;
        }

        case MSG_TYPE_REGISTER_SUPER_NAK: {
            n2n_REGISTER_SUPER_NAK_t  nak;
            uint8_t                   nakbuf[N2N_SN_PKTBUF_SIZE];
            size_t                    encx = 0;
            struct peer_info          *peer;
            n2n_sock_str_t            sockbuf;
            macstr_t                  mac_buf;
            n2n_sock_t                sender;

            memset(&sender, 0, sizeof(n2n_sock_t));
            sender.family = AF_INET;
            sender.port = ntohs(sender_sock->sin_port);
            memcpy(&(sender.addr.v4), &(sender_sock->sin_addr.s_addr), IPV4_SIZE);

            memset(&nak, 0, sizeof(n2n_REGISTER_SUPER_NAK_t));

            if(!comm) {
                traceEvent(TRACE_DEBUG, "REGISTER_SUPER_NAK with unknown community %s", cmn.community);
                return -1;
            }

            decode_REGISTER_SUPER_NAK(&nak, &cmn, udp_buf, &rem, &idx);

            if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                if(!find_edge_time_stamp_and_verify(comm->edges, sn, nak.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
                    traceEvent(TRACE_DEBUG, "process_udp dropped REGISTER_SUPER_NAK due to time stamp error");
                    return -1;
                }
            }

            traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_NAK from %s [%s]",
                       macaddr_str(mac_buf, nak.srcMac),
                       sock_to_cstr(sockbuf, &sender));

            HASH_FIND_PEER(comm->edges, nak.srcMac, peer);
            if(comm->is_federation == IS_NO_FEDERATION) {
                if(peer != NULL) {
                    // this is a NAK for one of the edges conencted to this supernode, forward,
                    // i.e. re-assemble (memcpy from udpbuf to nakbuf could be sufficient as well)

                    // use incoming cmn (with already decreased TTL)
                    // NAK (cookie, srcMac, auth) remains unchanged

                    encode_REGISTER_SUPER_NAK(nakbuf, &encx, &cmn, &nak);

                    if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                        packet_header_encrypt(nakbuf, encx, encx,
                                              comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
                                              time_stamp());
                        // if user-password-auth
                        if(comm->allowed_users) {
                            encode_buf(nakbuf, &encx, hash_buf /* no matter what content */, N2N_REG_SUP_HASH_CHECK_LEN);
                        }
                    }

                    sendto_peer(sss, peer, nakbuf, encx);

                    if((peer->socket_fd != sss->sock) && (peer->socket_fd >= 0)) {
                        n2n_tcp_connection_t *conn;
                        HASH_FIND_INT(sss->tcp_connections, &(peer->socket_fd), conn);
                        close_tcp_connection(sss, conn); /* also deletes the peer */
                    } else {
                        HASH_DEL(comm->edges, peer);
                        free(peer);
                    }
                }
            }
            break;
        }

        case MSG_TYPE_QUERY_PEER: {
            n2n_QUERY_PEER_t                       query;
            uint8_t                                encbuf[N2N_SN_PKTBUF_SIZE];
            size_t                                 encx = 0;
            n2n_common_t                           cmn2;
            n2n_PEER_INFO_t                        pi;
            struct sn_community_regular_expression *re, *tmp_re;
            int8_t                                 allowed_match = -1;
            uint8_t                                match = 0;
            int                                    match_length = 0;

            if(!comm && sss->lock_communities) {
                HASH_ITER(hh, sss->rules, re, tmp_re) {
                    allowed_match = re_matchp(re->rule, (const char *)cmn.community, &match_length);

                    if((allowed_match != -1)
                       && (match_length == strlen((const char *)cmn.community)) // --- only full matches allowed (remove, if also partial matches wanted)
                       && (allowed_match == 0)) {                               // --- only full matches allowed (remove, if also partial matches wanted)
                        match = 1;
                        break;
                    }
                }
                if(match != 1) {
                    traceEvent(TRACE_DEBUG, "QUERY_PEER from unknown community %s", cmn.community);
                    return -1;
                }
            }

            if(!comm && sss->lock_communities && (match == 0)) {
                traceEvent(TRACE_DEBUG, "QUERY_PEER from not allowed community %s", cmn.community);
                return -1;
            }

            decode_QUERY_PEER( &query, &cmn, udp_buf, &rem, &idx );

            // to answer a PING, it is sufficient if the provided communtiy would be a valid one, there does not
            // neccessarily need to be a comm entry present, e.g. because there locally are no edges of the
            // community connected (several supernodes in a federation setup)
            if(comm) {
                if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                    if(!find_edge_time_stamp_and_verify(comm->edges, sn, query.srcMac, stamp, TIME_STAMP_ALLOW_JITTER)) {
                        traceEvent(TRACE_DEBUG, "dropped QUERY_PEER due to time stamp error");
                        return -1;
                    }
                }
            }

            if(is_null_mac(query.targetMac)) {
                traceEvent(TRACE_DEBUG, "Rx PING from %s",
                           macaddr_str(mac_buf, query.srcMac));

                cmn2.ttl = N2N_DEFAULT_TTL;
                cmn2.pc = n2n_peer_info;
                cmn2.flags = N2N_FLAGS_FROM_SUPERNODE;
                memcpy(cmn2.community, cmn.community, sizeof(n2n_community_t));

                pi.aflags = 0;
                memcpy(pi.mac, query.targetMac, sizeof(n2n_mac_t));
                memcpy(pi.srcMac, sss->mac_addr, sizeof(n2n_mac_t));
                pi.sock.family = AF_INET;
                pi.sock.port = ntohs(sender_sock->sin_port);
                memcpy(pi.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
                pi.load = sn_selection_criterion_gather_data(sss);

                snprintf(pi.version, sizeof(pi.version), "%s", sss->version);
                pi.uptime = now - sss->start_time;

                encode_PEER_INFO(encbuf, &encx, &cmn2, &pi);

                if(comm) {
                    if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                        packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx_dynamic,
                                              comm->header_iv_ctx_dynamic,
                                              time_stamp());
                    }
                }

                sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, encbuf, encx);

                traceEvent(TRACE_DEBUG, "Tx PONG to %s",
                           macaddr_str(mac_buf, query.srcMac));

            } else {
                traceEvent(TRACE_DEBUG, "Rx QUERY_PEER from %s for %s",
                           macaddr_str(mac_buf, query.srcMac),
                           macaddr_str(mac_buf2, query.targetMac));

                struct peer_info *scan;

                // as opposed to the special case 'PING', proper QUERY_PEER processing requires a locally actually present community entry
                if(!comm) {
                    traceEvent(TRACE_DEBUG, "QUERY_PEER with unknown community %s", cmn.community);
                    return -1;
                }

                HASH_FIND_PEER(comm->edges, query.targetMac, scan);
                if(scan) {
                    cmn2.ttl = N2N_DEFAULT_TTL;
                    cmn2.pc = n2n_peer_info;
                    cmn2.flags = N2N_FLAGS_FROM_SUPERNODE;
                    memcpy(cmn2.community, cmn.community, sizeof(n2n_community_t));

                    pi.aflags = 0;
                    memcpy(pi.srcMac, query.srcMac, sizeof(n2n_mac_t));
                    memcpy(pi.mac, query.targetMac, sizeof(n2n_mac_t));
                    pi.sock = scan->sock;
                    if(scan->preferred_sock.family != (uint8_t)AF_INVALID) {
                        cmn2.flags |= N2N_FLAGS_SOCKET;
                        pi.preferred_sock = scan->preferred_sock;
                    }

                    encode_PEER_INFO(encbuf, &encx, &cmn2, &pi);

                    if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                        packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx_dynamic,
                                              comm->header_iv_ctx_dynamic,
                                              time_stamp());
                    }
                    // back to sender, be it edge or supernode (which will forward to edge)
                    sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, encbuf, encx);

                    traceEvent(TRACE_DEBUG, "Tx PEER_INFO to %s",
                               macaddr_str(mac_buf, query.srcMac));

                } else {

                    if(from_supernode) {
                        traceEvent(TRACE_DEBUG, "QUERY_PEER on unknown edge from supernode %s, dropping the packet",
                                   macaddr_str(mac_buf, query.srcMac));
                    } else {
                        traceEvent(TRACE_DEBUG, "QUERY_PEER from unknown edge %s, forwarding to all other supernodes",
                                   macaddr_str(mac_buf, query.srcMac));

                        memcpy(&cmn2, &cmn, sizeof(n2n_common_t));
                        cmn2.flags |= N2N_FLAGS_FROM_SUPERNODE;

                        encode_QUERY_PEER(encbuf, &encx, &cmn2, &query);

                        if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                            packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx_dynamic,
                                                  comm->header_iv_ctx_dynamic,
                                                  time_stamp());
                        }

                        try_broadcast(sss, NULL, &cmn, query.srcMac, from_supernode, encbuf, encx, now);
                    }
                }
            }
            break;
        }

        case MSG_TYPE_PEER_INFO: {
            n2n_PEER_INFO_t                        pi;
            uint8_t                                encbuf[N2N_SN_PKTBUF_SIZE];
            size_t                                 encx = 0;
            struct peer_info                       *peer;

            if(!comm) {
                traceEvent(TRACE_DEBUG, "PEER_INFO with unknown community %s", cmn.community);
                return -1;
            }

            decode_PEER_INFO(&pi, &cmn, udp_buf, &rem, &idx);

            if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                if(!find_edge_time_stamp_and_verify(comm->edges, sn, pi.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
                    traceEvent(TRACE_DEBUG, "dropped PEER_INFO due to time stamp error");
                    return -1;
                }
            }

            traceEvent(TRACE_INFO, "Rx PEER_INFO from %s [%s]",
                       macaddr_str(mac_buf, pi.srcMac),
                       sock_to_cstr(sockbuf, &sender));

            HASH_FIND_PEER(comm->edges, pi.srcMac, peer);
            if(peer != NULL) {
                if((comm->is_federation == IS_NO_FEDERATION) && (!is_null_mac(pi.srcMac))) {
                    // snoop on the information to use for supernode forwarding (do not wait until first remote REGISTER_SUPER)
                    update_node_supernode_association(comm, &(pi.mac), sender_sock, now);

                    // this is a PEER_INFO for one of the edges conencted to this supernode, forward,
                    // i.e. re-assemble (memcpy of udpbuf to encbuf could be sufficient as well)

                    // use incoming cmn (with already decreased TTL)
                    // PEER_INFO remains unchanged

                    encode_PEER_INFO(encbuf, &encx, &cmn, &pi);

                    if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
                        packet_header_encrypt(encbuf, encx, encx,
                                              comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
                                              time_stamp());
                    }

                    sendto_peer(sss, peer, encbuf, encx);
                }
            }
            break;
        }

        default:
            /* Not a known message type */
            traceEvent(TRACE_WARNING, "unable to handle packet type %d: ignored", (signed int)msg_type);
    } /* switch(msg_type) */

    return 0;
}


/** Long lived processing entry point. Split out from main to simply
 *  daemonisation on some platforms. */
int run_sn_loop (n2n_sn_t *sss) {

    uint8_t pktbuf[N2N_SN_PKTBUF_SIZE];
    time_t last_purge_edges = 0;
    time_t last_sort_communities = 0;
    time_t last_re_reg_and_purge = 0;

    sss->start_time = time(NULL);

    while(*sss->keep_running) {
        int rc;
        ssize_t bread;
        int max_sock;
        fd_set socket_mask;
        n2n_tcp_connection_t *conn, *tmp_conn;

#ifdef N2N_HAVE_TCP
        SOCKET tmp_sock;
        n2n_sock_str_t sockbuf;
#endif
        struct timeval wait_time;
        time_t before, now = 0;

        FD_ZERO(&socket_mask);

        FD_SET(sss->sock, &socket_mask);
#ifdef N2N_HAVE_TCP
        FD_SET(sss->tcp_sock, &socket_mask);
#endif
        FD_SET(sss->mgmt_sock, &socket_mask);

        max_sock = MAX(MAX(sss->sock, sss->mgmt_sock), sss->tcp_sock);

#ifdef N2N_HAVE_TCP
        // add the tcp connections' sockets
        HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
            //socket descriptor
            FD_SET(conn->socket_fd, &socket_mask);
            if(conn->socket_fd > max_sock)
                max_sock = conn->socket_fd;
        }
#endif

        wait_time.tv_sec = 10;
        wait_time.tv_usec = 0;

        before = time(NULL);

        rc = select(max_sock + 1, &socket_mask, NULL, NULL, &wait_time);

        now = time(NULL);

        if(rc > 0) {

            // external udp
            if(FD_ISSET(sss->sock, &socket_mask)) {
                struct sockaddr_in sender_sock;
                socklen_t i;

                i = sizeof(sender_sock);
                bread = recvfrom(sss->sock, (void *)pktbuf, N2N_SN_PKTBUF_SIZE, 0 /*flags*/,
                                 (struct sockaddr *)&sender_sock, (socklen_t *)&i);

                if((bread < 0)
#ifdef WIN32
                   && (WSAGetLastError() != WSAECONNRESET)
#endif
                  ) {
                    /* For UDP bread of zero just means no data (unlike TCP). */
                    /* The fd is no good now. Maybe we lost our interface. */
                    traceEvent(TRACE_ERROR, "recvfrom() failed %d errno %d (%s)", bread, errno, strerror(errno));
#ifdef WIN32
                    traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", WSAGetLastError());
#endif
                    *sss->keep_running = 0;
                    break;
                }

                // we have a datagram to process...
                if(bread > 0) {
                    // ...and the datagram has data (not just a header)
                    process_udp(sss, &sender_sock, sss->sock, pktbuf, bread, now);
                }
            }

#ifdef N2N_HAVE_TCP
            // the so far known tcp connections

            // beware: current conn and other items of the connection list may be found
            // due for deletion while processing packets. Even OTHER connections, e.g. if
            // forwarding to another edge node fails. connections due for deletion will
            // not immediately be deleted but marked 'inactive' for later deletion
            HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
                // do not process entries that have been marked inactive, those will be deleted
                // immediately after this loop
                if(conn->inactive)
                    continue;

                if(FD_ISSET(conn->socket_fd, &socket_mask)) {

                    struct sockaddr_in sender_sock;
                    socklen_t i;

                    i = sizeof(sender_sock);
                    bread = recvfrom(conn->socket_fd,
                                     conn->buffer + conn->position, conn->expected - conn->position, 0 /*flags*/,
                                     (struct sockaddr *)&sender_sock, (socklen_t *)&i);

                    if(bread <= 0) {
                        traceEvent(TRACE_INFO, "closing tcp connection to [%s]", sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
                        traceEvent(TRACE_DEBUG, "recvfrom() returns %d and sees errno %d (%s)", bread, errno, strerror(errno));
#ifdef WIN32
                        traceEvent(TRACE_DEBUG, "WSAGetLastError(): %u", WSAGetLastError());
#endif
                        close_tcp_connection(sss, conn);
                        continue;
                    }
                    conn->position += bread;

                    if(conn->position == conn->expected) {
                        if(conn->position == sizeof(uint16_t)) {
                            // the prepended length has been read, preparing for the packet
                            conn->expected += be16toh(*(uint16_t*)(conn->buffer));
                            if(conn->expected > N2N_SN_PKTBUF_SIZE) {
                                traceEvent(TRACE_INFO, "closing tcp connection to [%s]", sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
                                traceEvent(TRACE_DEBUG, "too many bytes in tcp packet expected");
                                close_tcp_connection(sss, conn);
                                continue;
                            }
                        } else {
                            // full packet read, handle it
                            process_udp(sss, (struct sockaddr_in*)&(conn->sock), conn->socket_fd,
                                             conn->buffer + sizeof(uint16_t), conn->position - sizeof(uint16_t), now);

                            // reset, await new prepended length
                            conn->expected = sizeof(uint16_t);
                            conn->position = 0;
                        }
                    }
                }
            }

            // remove inactive / already closed tcp connections from list
            HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
                if(conn->inactive) {
                    HASH_DEL(sss->tcp_connections, conn);
                    free(conn);
                }
            }

            // accept new incoming tcp connection
            if(FD_ISSET(sss->tcp_sock, &socket_mask)) {
                struct sockaddr_in sender_sock;
                socklen_t i;

                i = sizeof(sender_sock);
                if((HASH_COUNT(sss->tcp_connections) + 4) < FD_SETSIZE) {
                    tmp_sock = accept(sss->tcp_sock, (struct sockaddr *)&sender_sock, (socklen_t *)&i);
                    if(tmp_sock >= 0) {
                        conn = (n2n_tcp_connection_t*)malloc(sizeof(n2n_tcp_connection_t));
                        if(conn) {
                            conn->socket_fd = tmp_sock;
                            memcpy(&(conn->sock), &sender_sock, sizeof(struct sockaddr_in));
                            conn->inactive = 0;
                            conn->expected = sizeof(uint16_t);
                            conn->position = 0;
                            HASH_ADD_INT(sss->tcp_connections, socket_fd, conn);
                            traceEvent(TRACE_INFO, "accepted incoming TCP connection from [%s]",
                                                   sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
                        }
                    }
                } else {
                        // no space to store the socket for a new connection, close immediately
                        traceEvent(TRACE_DEBUG, "denied incoming TCP connection from [%s] due to max connections limit hit",
                                                sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
                }
            }
#endif /* N2N_HAVE_TCP */

            // handle management port input
            if(FD_ISSET(sss->mgmt_sock, &socket_mask)) {
                struct sockaddr_in sender_sock;
                size_t i;

                i = sizeof(sender_sock);
                bread = recvfrom(sss->mgmt_sock, (void *)pktbuf, N2N_SN_PKTBUF_SIZE, 0 /*flags*/,
                                 (struct sockaddr *)&sender_sock, (socklen_t *)&i);

                if(bread <= 0) {
                    traceEvent(TRACE_ERROR, "recvfrom() failed %d errno %d (%s)", bread, errno, strerror(errno));
                    *sss->keep_running = 0;
                    break;
                }

                // we have a datagram to process
                process_mgmt(sss, &sender_sock, (char *)pktbuf, bread, now);
            }

        } else {
            if(((now - before) < wait_time.tv_sec) && (*sss->keep_running)){
                // this is no real timeout, something went wrong with one of the tcp connections (probably)
                // close them all, edges will re-open if they detect closure
                traceEvent(TRACE_DEBUG, "falsly claimed timeout, assuming issue with tcp connection, closing them all");
                HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn)
                    close_tcp_connection(sss, conn);
            } else
                traceEvent(TRACE_DEBUG, "timeout");
        }

        re_register_and_purge_supernodes(sss, sss->federation, &last_re_reg_and_purge, now, 0 /* not forced */);
        purge_expired_communities(sss, &last_purge_edges, now);
        sort_communities(sss, &last_sort_communities, now);
        resolve_check(sss->resolve_parameter, 0 /* presumably, no special resolution requirement */, now);
    } /* while */

    sn_term(sss);

    return 0;
}
