/*
 * ndpi_main.c
 *
 * Copyright (C) 2011-19 - ntop.org
 *
 * This file is part of nDPI, an open source deep packet inspection
 * library based on the OpenDPI and PACE technology by ipoque GmbH
 *
 * nDPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * nDPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifdef HAVE_CONFIG_H
#include "ndpi_config.h"
#endif

#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include "ahocorasick.h"
#include "libcache.h"

#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_UNKNOWN

#include "ndpi_api.h"
#include "ndpi_config.h"

#include <time.h>
#ifndef WIN32
#include <unistd.h>
#endif

#if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
#include <sys/endian.h>
#endif

#include "third_party/include/ndpi_patricia.h"
#include "third_party/include/ht_hash.h"

#ifdef HAVE_HYPERSCAN
#include <hs/hs.h>

struct hs_list {
    char *expression;
    unsigned int id;
    struct hs_list *next;
};

struct hs {
    hs_database_t *database;
    hs_scratch_t  *scratch;
};
#endif

#define NDPI_CONST_GENERIC_PROTOCOL_NAME  "GenericProtocol"

static int _ndpi_debug_callbacks = 0;

// #define MATCH_DEBUG 1

/* implementation of the punycode check function */
int check_punycode_string(char * buffer, int len)
{
    int i = 0;

    while(i++ < len)
    {
        if( buffer[i] == 'x' &&
                buffer[i+1] == 'n' &&
                buffer[i+2] == '-' &&
                buffer[i+3] == '-' )
            // is a punycode string
            return 1;
    }
    // not a punycode string
    return 0;
}

/* ftp://ftp.cc.uoc.gr/mirrors/OpenBSD/src/lib/libc/stdlib/tsearch.c */
/* find or insert datum into search tree */
void * ndpi_tsearch(const void *vkey, void **vrootp,
        int (*compar)(const void *, const void *))
{
    ndpi_node *q;
    char *key = (char *)vkey;
    ndpi_node **rootp = (ndpi_node **)vrootp;

    if(rootp == (ndpi_node **)0)
        return ((void *)0);
    while (*rootp != (ndpi_node *)0) {  /* Knuth's T1: */
        int r;

        if((r = (*compar)(key, (*rootp)->key)) == 0)    /* T2: */
            return ((void *)*rootp);    /* we found it! */
        rootp = (r < 0) ?
            &(*rootp)->left :       /* T3: follow left branch */
            &(*rootp)->right;       /* T4: follow right branch */
    }
    q = (ndpi_node *) ndpi_malloc(sizeof(ndpi_node));   /* T5: key not found */
    if(q != (ndpi_node *)0) {   /* make new node */
        *rootp = q;         /* link new node to old */
        q->key = key;       /* initialize new node */
        q->left = q->right = (ndpi_node *)0;
    }
    return ((void *)q);
}

/* delete node with given key */
void * ndpi_tdelete(const void *vkey, void **vrootp,
        int (*compar)(const void *, const void *))
{
    ndpi_node **rootp = (ndpi_node **)vrootp;
    char *key = (char *)vkey;
    ndpi_node *p = (ndpi_node *)1;
    ndpi_node *q;
    ndpi_node *r;
    int cmp;

    if(rootp == (ndpi_node **)0 || *rootp == (ndpi_node *)0)
        return ((ndpi_node *)0);
    while ((cmp = (*compar)(key, (*rootp)->key)) != 0) {
        p = *rootp;
        rootp = (cmp < 0) ?
            &(*rootp)->left :       /* follow left branch */
            &(*rootp)->right;       /* follow right branch */
        if(*rootp == (ndpi_node *)0)
            return ((void *)0);     /* key not found */
    }
    r = (*rootp)->right;            /* D1: */
    if((q = (*rootp)->left) == (ndpi_node *)0)  /* Left (ndpi_node *)0? */
        q = r;
    else if(r != (ndpi_node *)0) {      /* Right link is null? */
        if(r->left == (ndpi_node *)0) { /* D2: Find successor */
            r->left = q;
            q = r;
        } else {            /* D3: Find (ndpi_node *)0 link */
            for(q = r->left; q->left != (ndpi_node *)0; q = r->left)
                r = q;
            r->left = q->right;
            q->left = (*rootp)->left;
            q->right = (*rootp)->right;
        }
    }
    ndpi_free((ndpi_node *) *rootp);    /* D4: Free node */
    *rootp = q;             /* link parent to new node */
    return(p);
}

/* Walk the nodes of a tree */
static void ndpi_trecurse(ndpi_node *root, void (*action)(const void *, ndpi_VISIT, int, void*), int level, void *user_data)
{
    if(root->left == (ndpi_node *)0 && root->right == (ndpi_node *)0)
        (*action)(root, ndpi_leaf, level, user_data);
    else {
        (*action)(root, ndpi_preorder, level, user_data);
        if(root->left != (ndpi_node *)0)
            ndpi_trecurse(root->left, action, level + 1, user_data);
        (*action)(root, ndpi_postorder, level, user_data);
        if(root->right != (ndpi_node *)0)
            ndpi_trecurse(root->right, action, level + 1, user_data);
        (*action)(root, ndpi_endorder, level, user_data);
    }
}

/* Walk the nodes of a tree */
void ndpi_twalk(const void *vroot, void (*action)(const void *, ndpi_VISIT, int, void *), void *user_data)
{
    ndpi_node *root = (ndpi_node *)vroot;

    if(root != (ndpi_node *)0 && action != (void (*)(const void *, ndpi_VISIT, int, void*))0)
        ndpi_trecurse(root, action, 0, user_data);
}

/* find a node, or return 0 */
void * ndpi_tfind(const void *vkey, void *vrootp,
        int (*compar)(const void *, const void *))
{
    char *key = (char *)vkey;
    ndpi_node **rootp = (ndpi_node **)vrootp;

    if(rootp == (ndpi_node **)0)
        return ((ndpi_node *)0);
    while (*rootp != (ndpi_node *)0) {  /* T1: */
        int r;
        if((r = (*compar)(key, (*rootp)->key)) == 0)    /* T2: */
            return (*rootp);        /* key found */
        rootp = (r < 0) ?
            &(*rootp)->left :       /* T3: follow left branch */
            &(*rootp)->right;       /* T4: follow right branch */
    }
    return (ndpi_node *)0;
}

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

/* Walk the nodes of a tree */
static void ndpi_tdestroy_recurse(ndpi_node* root, void (*free_action)(void *))
{
    if(root->left != NULL)
        ndpi_tdestroy_recurse(root->left, free_action);
    if(root->right != NULL)
        ndpi_tdestroy_recurse(root->right, free_action);

    (*free_action) ((void *) root->key);
    ndpi_free(root);
}

void ndpi_tdestroy(void *vrootp, void (*freefct)(void *))
{
    ndpi_node *root = (ndpi_node *) vrootp;

    if(root != NULL)
        ndpi_tdestroy_recurse(root, freefct);
}

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

u_int8_t ndpi_net_match(u_int32_t ip_to_check,
        u_int32_t net,
        u_int32_t num_bits)
{
    u_int32_t mask = 0;

    mask = ~(~mask >> num_bits);

    return(((ip_to_check & mask) == (net & mask)) ? 1 : 0);
}

u_int8_t ndpi_ips_match(u_int32_t src, u_int32_t dst,
        u_int32_t net, u_int32_t num_bits)
{
    return(ndpi_net_match(src, net, num_bits) || ndpi_net_match(dst, net, num_bits));
}

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

static void *(*_ndpi_flow_malloc)(size_t size);
static void  (*_ndpi_flow_free)(void *ptr);

static void *(*_ndpi_malloc)(size_t size);
static void  (*_ndpi_free)(void *ptr);

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

/* Keep it in order and in sync with ndpi_protocol_category_t in ndpi_typedefs.h */
static const char* categories[] = {
    "Unspecified",
};

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

#ifdef WIN32
/* http://opensource.apple.com/source/Libc/Libc-186/string.subproj/strcasecmp.c */

/*
 * This array is designed for mapping upper and lower case letter
 * together for a case independent comparison.  The mappings are
 * based upon ascii character sequences.
 */
static const u_char charmap[] = {
    '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
    '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
    '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
    '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
    '\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
    '\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
    '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
    '\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
    '\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
    '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
    '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
    '\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
    '\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
    '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
    '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
    '\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
    '\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
    '\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
    '\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
    '\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
    '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
    '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
    '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
    '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
    '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
    '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
    '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
    '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
    '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
    '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
    '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
    '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
};

int strcasecmp(s1, s2)
    const char *s1, *s2;
{
    register const u_char *cm = charmap,
             *us1 = (const u_char *)s1,
             *us2 = (const u_char *)s2;

    while (cm[*us1] == cm[*us2++])
        if(*us1++ == '\0')
            return (0);
    return (cm[*us1] - cm[*--us2]);
}

int strncasecmp(s1, s2, n)
    const char *s1, *s2;
    register size_t n;
{
    if(n != 0) {
        register const u_char *cm = charmap,
                 *us1 = (const u_char *)s1,
                 *us2 = (const u_char *)s2;

        do {
            if(cm[*us1] != cm[*us2++])
                return (cm[*us1] - cm[*--us2]);
            if(*us1++ == '\0')
                break;
        } while (--n != 0);
    }
    return (0);
}

#endif

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

void * ndpi_malloc(size_t size) {
    return(_ndpi_malloc ? _ndpi_malloc(size) : malloc(size));
}

void * ndpi_flow_malloc(size_t size) {
    return(_ndpi_flow_malloc ? _ndpi_flow_malloc(size) : ndpi_malloc(size));
}

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

void * ndpi_calloc(unsigned long count, size_t size)
{
    size_t len = count*size;
    void *p = ndpi_malloc(len);

    if(p)
        memset(p, 0, len);

    return(p);
}

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

void ndpi_free(void *ptr) {
    if(_ndpi_free)
        _ndpi_free(ptr);
    else
        free(ptr);
}

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

void ndpi_flow_free(void *ptr) {
    if(_ndpi_flow_free)
        _ndpi_flow_free(ptr);
    else
        ndpi_free_flow((struct ndpi_flow_struct *) ptr);
}

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

void * ndpi_realloc(void *ptr, size_t old_size, size_t new_size)
{
    void *ret = ndpi_malloc(new_size);

    if(!ret)
        return(ret);
    else {
        memcpy(ret, ptr, old_size);
        ndpi_free(ptr);
        return(ret);
    }
}
/* ****************************************** */

char * ndpi_strdup(const char *s)
{
    int len = strlen(s);
    char *m = ndpi_malloc(len+1);

    if(m) {
        memcpy(m, s, len);
        m[len] = '\0';
    }

    return(m);
}

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

u_int32_t ndpi_detection_get_sizeof_ndpi_flow_struct(void) {
    return sizeof(struct ndpi_flow_struct);
}

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

u_int32_t ndpi_detection_get_sizeof_ndpi_id_struct(void) {
    return sizeof(struct ndpi_id_struct);
}

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

char * ndpi_get_proto_by_id(struct ndpi_detection_module_struct *ndpi_mod, u_int id)
{
    return((id >= ndpi_mod->ndpi_num_supported_protocols) ?
            NULL : ndpi_mod->proto_defaults[id].proto_name);
}

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

u_int16_t ndpi_get_proto_by_name(
        struct ndpi_detection_module_struct *ndpi_mod, const char *name) {

    u_int16_t i, num = ndpi_get_num_supported_protocols(ndpi_mod);

    for(i = 0; i < num; i++)
        if(strcasecmp(ndpi_get_proto_by_id(ndpi_mod, i), name) == 0)
            return(i);

    return(NDPI_PROTOCOL_UNKNOWN);
}

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

ndpi_port_range * ndpi_build_default_ports_range(ndpi_port_range *ports,
        u_int16_t portA_low, u_int16_t portA_high,
        u_int16_t portB_low, u_int16_t portB_high,
        u_int16_t portC_low, u_int16_t portC_high,
        u_int16_t portD_low, u_int16_t portD_high,
        u_int16_t portE_low, u_int16_t portE_high)
{
    int i = 0;

    ports[i].port_low = portA_low, ports[i].port_high = portA_high; i++;
    ports[i].port_low = portB_low, ports[i].port_high = portB_high; i++;
    ports[i].port_low = portC_low, ports[i].port_high = portC_high; i++;
    ports[i].port_low = portD_low, ports[i].port_high = portD_high; i++;
    ports[i].port_low = portE_low, ports[i].port_high = portE_high;

    return(ports);
}

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

ndpi_port_range * ndpi_build_default_ports(ndpi_port_range *ports,
        u_int16_t portA,
        u_int16_t portB,
        u_int16_t portC,
        u_int16_t portD,
        u_int16_t portE)
{
    int i = 0;

    ports[i].port_low = portA, ports[i].port_high = portA; i++;
    ports[i].port_low = portB, ports[i].port_high = portB; i++;
    ports[i].port_low = portC, ports[i].port_high = portC; i++;
    ports[i].port_low = portD, ports[i].port_high = portD; i++;
    ports[i].port_low = portE, ports[i].port_high = portE;

    return(ports);
}

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

void ndpi_exclude_protocol(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        u_int16_t proto_id,
        const char *_file, const char *_func,int _line) {

    if(proto_id < NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS) {
#ifdef NDPI_ENABLE_DEBUG_MESSAGES
        if ( ndpi_struct &&
                ndpi_struct->ndpi_log_level >= NDPI_LOG_DEBUG &&
                ndpi_struct->ndpi_debug_printf != NULL) {

            (*(ndpi_struct->ndpi_debug_printf))(proto_id, ndpi_struct, NDPI_LOG_DEBUG,
                    _file, _func, _line, "exclude %s\n",ndpi_get_proto_name(ndpi_struct, proto_id));
        }
#endif
        NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, proto_id);
    }
}

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

static int ndpi_default_ports_tree_node_t_cmp(const void *a, const void *b)
{
    ndpi_default_ports_tree_node_t *fa = (ndpi_default_ports_tree_node_t*)a;
    ndpi_default_ports_tree_node_t *fb = (ndpi_default_ports_tree_node_t*)b;

    //printf("[NDPI] %s(%d, %d)\n", __FUNCTION__, fa->default_port, fb->default_port);

    return((fa->default_port == fb->default_port) ? 0 : ((fa->default_port < fb->default_port) ? -1 : 1));
}

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

void ndpi_default_ports_tree_node_t_walker(const void *node, const ndpi_VISIT which, const int depth)
{
    ndpi_default_ports_tree_node_t *f = *(ndpi_default_ports_tree_node_t **)node;

    printf("<%d>Walk on node %s (%u)\n",
            depth,
            which == ndpi_preorder?"ndpi_preorder":
            which == ndpi_postorder?"ndpi_postorder":
            which == ndpi_endorder?"ndpi_endorder":
            which == ndpi_leaf?"ndpi_leaf": "unknown",
            f->default_port);
}

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

static void ndpi_add_default_port(struct ndpi_detection_module_struct *ndpi_mod,
        ndpi_port_range *range,
        ndpi_proto_defaults_t *def,
        u_int8_t custom_user_proto,
        ndpi_default_ports_tree_node_t **root)
{
    ndpi_default_ports_tree_node_t *ret;
    u_int16_t port;

    for(port = range->port_low; port <= range->port_high; port++) {
        ndpi_default_ports_tree_node_t *node =
            (ndpi_default_ports_tree_node_t*)ndpi_malloc(
                    sizeof(ndpi_default_ports_tree_node_t));

        if(!node) {
            NDPI_LOG_ERR(ndpi_mod, "%s:%d not enough memory\n",
                    __FUNCTION__, __LINE__);
            break;
        }

        node->proto = def, node->default_port = port;
        node->custom_user_proto = custom_user_proto;

        ret = *(ndpi_default_ports_tree_node_t **)ndpi_tsearch(
                node, (void*)root, ndpi_default_ports_tree_node_t_cmp); /* Add it to the tree */

        if(ret != node) {
            NDPI_LOG_DBG(ndpi_mod,
                    "[NDPI] %s:%d found duplicate for port %u: overwriting it with new value\n",
                    __FUNCTION__, __LINE__, port);

            ret->proto = def;
            ndpi_free(node);
        }
    }
}

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

/*
   NOTE

   This function must be called with a semaphore set, this in order to avoid
   changing the datastructures while using them
   */
static int ndpi_remove_default_port(ndpi_port_range *range,
        ndpi_proto_defaults_t *def,
        ndpi_default_ports_tree_node_t **root)
{
    ndpi_default_ports_tree_node_t node;
    ndpi_default_ports_tree_node_t *ret;
    u_int16_t port;

    for(port=range->port_low; port<=range->port_high; port++) {
        node.proto = def, node.default_port = port;
        ret = *(ndpi_default_ports_tree_node_t**)ndpi_tdelete(
                &node, (void*)root, ndpi_default_ports_tree_node_t_cmp); /* Add it to the tree */

        if(ret != NULL) {
            ndpi_free((ndpi_default_ports_tree_node_t*)ret);
            return(0);
        }
    }

    return(-1);
}

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

void ndpi_set_proto_defaults(
        struct ndpi_detection_module_struct *ndpi_mod, u_int16_t proto_id,
        u_int16_t tcp_master_proto_id[2], u_int16_t udp_master_proto_id[2], char *proto_name,
        ndpi_port_range *tcp_default_ports, ndpi_port_range *udp_default_ports) {

    int j;
    char *name;

    if(proto_id >= NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS) {
#ifdef DEBUG
        NDPI_LOG_ERR(ndpi_mod, "[NDPI] %s/proto_id=%d: INTERNAL ERROR\n", proto_name, proto_id);
#endif
        return;
    }

    if(ndpi_mod->proto_defaults[proto_id].proto_name != NULL) {
#ifdef DEBUG
        NDPI_LOG_ERR(ndpi_mod, "[NDPI] %s/proto_id=%d: already initialized. Ignoring it\n", proto_name, proto_id);
#endif
        return;
    }

    name = ndpi_strdup(proto_name);

    ndpi_mod->proto_defaults[proto_id].proto_name = name,
        ndpi_mod->proto_defaults[proto_id].proto_id = proto_id;

    memcpy(
            &ndpi_mod->proto_defaults[proto_id].master_tcp_proto_id,
            tcp_master_proto_id, 2 * sizeof(u_int16_t));
    memcpy(
            &ndpi_mod->proto_defaults[proto_id].master_udp_proto_id,
            udp_master_proto_id, 2 * sizeof(u_int16_t));

    for(j=0; j < MAX_DEFAULT_PORTS; j++) {
        if(tcp_default_ports[j].port_low != 0) {
            ndpi_add_default_port(ndpi_mod, &tcp_default_ports[j],
                    &ndpi_mod->proto_defaults[proto_id], 0, &ndpi_mod->tcp_root_node);
        }
        if(udp_default_ports[j].port_low != 0) {
            ndpi_add_default_port(ndpi_mod, &udp_default_ports[j],
                    &ndpi_mod->proto_defaults[proto_id], 0, &ndpi_mod->udp_root_node);
        }
    }
}

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

static int ndpi_string_to_automa(struct ndpi_detection_module_struct *ndpi_struct,
        ndpi_automa *automa, char *value, int proto_id) {

    AC_PATTERN_t ac_pattern;

    if(proto_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) {
        NDPI_LOG_ERR(ndpi_struct, "[NDPI] proto_id=%d: INTERNAL ERROR\n", proto_id);
        return(-1);
    }

    if(automa->ac_automa == NULL) return(-2);
    ac_pattern.astring = value;
    ac_pattern.rep.number = proto_id;
    ac_pattern.rep.category = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED;
    ac_pattern.rep.breed = NDPI_PROTOCOL_UNRATED;
    if(value == NULL)
        ac_pattern.length = 0;
    else
        ac_pattern.length = strlen(ac_pattern.astring);

    ac_automata_add(((AC_AUTOMATA_t*)automa->ac_automa), &ac_pattern);

    return(0);
}

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

static int ndpi_add_host_app_proto(
        struct ndpi_detection_module_struct *ndpi_struct, char *value, int proto_id) {
#ifdef DEBUG
    NDPI_LOG_DEBUG2(ndpi_struct, "[NDPI] Adding host automa [%s][%d]\n", value, proto_id);
#endif
    return(ndpi_string_to_automa(ndpi_struct, &ndpi_struct->host_automa, value, proto_id));
}

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

int ndpi_add_content_app_proto(
        struct ndpi_detection_module_struct *ndpi_struct, char *value, int proto_id) {
#ifdef DEBUG
    NDPI_LOG_DEBUG2(ndpi_struct, "[NDPI] Adding content automa [%s][%d]\n", value, proto_id);
#endif
    return(ndpi_string_to_automa(ndpi_struct, &ndpi_struct->content_automa, value, proto_id));
}

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

/*
   NOTE

   This function must be called with a semaphore set, this in order to avoid
   changing the datastructures while using them
   */
static int ndpi_remove_host_app_proto(struct ndpi_detection_module_struct *ndpi_struct,
        char *value, int proto_id)
{
    NDPI_LOG_ERR(ndpi_struct, "[NDPI] Missing implementation for proto %s/%d\n",value,proto_id);
    return(-1);
}

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

void ndpi_init_protocol_match(
        struct ndpi_detection_module_struct *ndpi_mod, ndpi_protocol_match *match)
{
    u_int16_t no_master[2] = { NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO };
    ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS];

    if(ndpi_mod->proto_defaults[match->proto_id].proto_name == NULL) {
        if(match->proto_id == NDPI_PROTOCOL_GENERIC)
            ndpi_mod->proto_defaults[match->proto_id].proto_name = ndpi_strdup(NDPI_CONST_GENERIC_PROTOCOL_NAME);
        else
            ndpi_mod->proto_defaults[match->proto_id].proto_name = ndpi_strdup(match->proto_name);

        ndpi_mod->proto_defaults[match->proto_id].proto_id = match->proto_id;

        ndpi_set_proto_defaults(ndpi_mod,
                ndpi_mod->proto_defaults[match->proto_id].proto_id,
                no_master, no_master,
                ndpi_mod->proto_defaults[match->proto_id].proto_name,
                ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
                ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
    }

    ndpi_add_host_app_proto(ndpi_mod, match->string_to_match, match->proto_id);
}

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

#ifdef HAVE_HYPERSCAN

static int hyperscan_load_patterns(struct hs *hs, u_int num_patterns,
        const char **expressions, unsigned int *ids) {
    hs_compile_error_t *compile_err;

    if(hs_compile_multi(expressions, NULL, ids,
                num_patterns, HS_MODE_BLOCK, NULL,
                &hs->database, &compile_err) != HS_SUCCESS) {
        NDPI_LOG_ERR(ndpi_mod, "Unable to initialize hyperscan database\n");
        hs_free_compile_error(compile_err);
        return -1;
    }

    hs->scratch = NULL;
    if(hs_alloc_scratch(hs->database, &hs->scratch) != HS_SUCCESS) {
        NDPI_LOG_ERR(ndpi_mod, "Unable to allocate hyperscan scratch space\n");
        hs_free_database(hs->database);
        return -1;
    }

    return 0;
}

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

static char* string2hex(const char *pat) {
    u_int patlen, i;
    char *hexbuf, *buf;

    patlen = strlen(pat);
    hexbuf = (char*)calloc(sizeof(char), patlen * 4 + 1);
    if(!hexbuf) return(NULL);

    for (i = 0, buf = hexbuf; i < patlen; i++, buf += 4) {
        snprintf(buf, 5, "\\x%02x", (unsigned char)pat[i]);
    }
    *buf = '\0';

    return hexbuf;
}

static int init_hyperscan(struct ndpi_detection_module_struct *ndpi_mod) {
    u_int num_patterns = 0, i, j;
    char **expressions;
    unsigned int *ids;
    unsigned char *need_to_be_free;
    struct hs *hs;
    int rc;

    ndpi_mod->hyperscan = (void*)malloc(sizeof(struct hs));
    if(!ndpi_mod->hyperscan) return(-1);
    hs = (struct hs*)ndpi_mod->hyperscan;

    for(i = 0; host_match[i].string_to_match != NULL || host_match[i].pattern_to_match != NULL; i++) {
        num_patterns++;
    }

    expressions = (char**)calloc(sizeof(char*), num_patterns + 1);
    if(!expressions) return(-1);

    ids = (unsigned int*)calloc(sizeof(unsigned int), num_patterns + 1);
    if(!ids) {
        free(expressions);
        return(-1);
    }

    need_to_be_free = (unsigned char*)calloc(sizeof(unsigned char), num_patterns + 1);
    if(!need_to_be_free) {
        free(expressions);
        free(ids);
        return(-1);
    }

    for(i = 0, j = 0; host_match[i].string_to_match != NULL || host_match[i].pattern_to_match != NULL; i++) {
        if(host_match[i].pattern_to_match) {
            expressions[j] = host_match[i].pattern_to_match;
            ids[j] = host_match[i].protocol_id;
            need_to_be_free[j] = 0;
            ++j;
        } else {
            expressions[j] = string2hex(host_match[i].string_to_match);
            if(expressions[j] != NULL) {
                ids[j] = host_match[i].protocol_id;
                need_to_be_free[j] = 1;
                ++j;
            } else {
#ifdef DEBUG
                printf("Fail to calloc memory for %s\n", host_match[i].string_to_match);
#endif
            }
        }
        /*printf("[DEBUG] %s\n", j ? expressions[j - 1] : "No Expression");*/
    }

    rc = hyperscan_load_patterns(hs, j, (const char**)expressions, ids);

    for(i = 0; i < j; ++i)
        if(need_to_be_free[i])
            free(expressions[i]);
    free(expressions), free(ids);

    free(need_to_be_free);
    return(rc);
}

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

static void free_hyperscan_memory(struct hs *h) {
    if(h) {
        hs_free_scratch(h->scratch);
        hs_free_database(h->database);
        free(h);
    }
}

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

static void destroy_hyperscan(struct ndpi_detection_module_struct *ndpi_mod) {
    if(ndpi_mod->hyperscan)
        free_hyperscan_memory((struct hs*)ndpi_mod->hyperscan);
}

#endif

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

void ndpi_init_string_based_protocols(struct ndpi_detection_module_struct *ndpi_mod)
{
    int i;

#ifdef HAVE_HYPERSCAN
    // TODO check return value
    init_hyperscan(ndpi_mod);
#endif
#if 0
    for(i=0; host_match[i].string_to_match != NULL; i++)
        ndpi_init_protocol_match(ndpi_mod, &host_match[i]);
#endif
#ifdef MATCH_DEBUG
    // ac_automata_display(ndpi_mod->host_automa.ac_automa, 'n');
#endif

    for(i=0; i<(int)ndpi_mod->ndpi_num_supported_protocols; i++) {
        if(ndpi_mod->proto_defaults[i].proto_name == NULL) {
#if 0
            printf("[NDPI] %s(missing proto_id=%d) WARNING: protocol/service has not been initialized\n", __FUNCTION__, i);
#endif
            ndpi_mod->proto_defaults[i].proto_name = ndpi_strdup("Uninitialized");
        }
    }
}

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

int ndpi_set_detection_preferences(struct ndpi_detection_module_struct *ndpi_mod,
        ndpi_detection_preference pref,
        int value) {
    switch(pref) {
        case ndpi_pref_http_dont_dissect_response:
            ndpi_mod->http_dont_dissect_response = (u_int8_t)value;
            break;

        case ndpi_pref_dns_dont_dissect_response:
            ndpi_mod->dns_dont_dissect_response = (u_int8_t)value;
            break;

        case ndpi_pref_direction_detect_disable:
            ndpi_mod->direction_detect_disable = (u_int8_t)value;
            break;

        case ndpi_pref_disable_metadata_export:
            ndpi_mod->disable_metadata_export = (u_int8_t)value;
            break;

        case ndpi_pref_enable_category_substring_match:
            ndpi_mod->enable_category_substring_match = (u_int8_t)value;
            break;

        default:
            return(-1);
    }

    return(0);
}

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

/* This function is used to map protocol name and default ports and it MUST
   be updated whenever a new protocol is added to NDPI.

   Do NOT add web services (NDPI_SERVICE_xxx) here.
   */
static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndpi_mod) {
    ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS];
    u_int16_t no_master[2] = { NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO },
              custom_master[2];

    /* Reset all settings */
    memset(ndpi_mod->proto_defaults, 0, sizeof(ndpi_mod->proto_defaults));

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_UNKNOWN,
            no_master, no_master, "Unknown",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FTP_CONTROL,
            no_master, no_master, "FTP_CONTROL",
            ndpi_build_default_ports(ports_a, 21, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    /* Missing dissector: port based only,
     * essentially impossible to dissect -- guess only */
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FTP_DATA,
            no_master, no_master, "FTP_DATA",
            ndpi_build_default_ports(ports_a, 20, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MAIL_POP,
            no_master, no_master, "POP3",
            ndpi_build_default_ports(ports_a, 110, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MAIL_POPS,
            no_master, no_master, "POPS",
            ndpi_build_default_ports(ports_a, 995, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MAIL_SMTP,
            no_master, no_master, "SMTP",
            ndpi_build_default_ports(ports_a, 25, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MAIL_SMTPS,
            no_master, no_master, "SMTPS",
            ndpi_build_default_ports(ports_a, 465, 587, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MAIL_IMAP,
            no_master, no_master, "IMAP",
            ndpi_build_default_ports(ports_a, 143, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MAIL_IMAPS,
            no_master, no_master, "IMAPS",
            ndpi_build_default_ports(ports_a, 993, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DNS,
            no_master, no_master, "DNS",
            ndpi_build_default_ports(ports_a, 53, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 53, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IPP,
            no_master, no_master, "IPP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HEP,
            no_master, no_master, "HEP",
            ndpi_build_default_ports(ports_a, 9064, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 9063, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HTTP,
            no_master, no_master, "HTTP",
            ndpi_build_default_ports(ports_a, 80, 0 /* ntop */, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HTTPS,
            no_master, no_master, "HTTPS",
            ndpi_build_default_ports(ports_a, 443, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MDNS,
            no_master, no_master, "MDNS",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 5353, 5354, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NTP,
            no_master, no_master, "NTP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 123, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NETBIOS,
            no_master, no_master, "NetBIOS",
            ndpi_build_default_ports(ports_a, 139, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 137, 138, 139, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NFS,
            no_master, no_master, "NFS",
            ndpi_build_default_ports(ports_a, 2049, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 2049, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SSDP,
            no_master, no_master, "SSDP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_BGP,
            no_master, no_master, "BGP",
            ndpi_build_default_ports(ports_a, 179, 2605, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SNMP,
            no_master, no_master, "SNMP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 161, 162, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_XDMCP,
            no_master, no_master, "XDMCP",
            ndpi_build_default_ports(ports_a, 177, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 177, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SMBV1,
            no_master, no_master, "SMBv1",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SYSLOG,
            no_master, no_master, "Syslog",
            ndpi_build_default_ports(ports_a, 514, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 514, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DHCP,
            no_master, no_master, "DHCP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 67, 68, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_POSTGRES,
            no_master, no_master, "PostgreSQL",
            ndpi_build_default_ports(ports_a, 5432, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MYSQL,
            no_master, no_master, "MySQL",
            ndpi_build_default_ports(ports_a, 3306, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DIRECT_DOWNLOAD_LINK,
            no_master, no_master, "Direct_Download_Link",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_APPLEJUICE,
            no_master, no_master, "AppleJuice",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DIRECTCONNECT,
            no_master, no_master, "DirectConnect",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NTOP,
            no_master, no_master, "ntop",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_VMWARE,
            no_master, no_master, "VMware",
            ndpi_build_default_ports(ports_a, 903, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 902, 903, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FBZERO,
            no_master, no_master, "FacebookZero",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_KONTIKI,
            no_master, no_master, "Kontiki",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_OPENFT,
            no_master, no_master, "OpenFT",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FASTTRACK,
            no_master, no_master, "FastTrack",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_GNUTELLA,
            no_master, no_master, "Gnutella",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_EDONKEY,
            no_master, no_master, "eDonkey",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_BITTORRENT,
            no_master, no_master, "BitTorrent",
            ndpi_build_default_ports(ports_a, 51413, 53646, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 6771, 51413, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RSYNC,
            no_master, no_master, "RSYNC",
            ndpi_build_default_ports(ports_a, 873, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SKYPE,
            no_master, no_master, "Skype",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SKYPE_CALL,
            no_master, no_master, "SkypeCall",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TEREDO,
            no_master, no_master, "Teredo",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 3544, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WECHAT,
            no_master, no_master, "WeChat",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MEMCACHED,
            no_master, no_master, "Memcached",
            ndpi_build_default_ports(ports_a, 11211, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 11211, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SMBV23,
            no_master, no_master, "SMBv23",
            ndpi_build_default_ports(ports_a, 445, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MINING,
            no_master, no_master, "Mining",
            ndpi_build_default_ports(ports_a, 8333, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NEST_LOG_SINK,
            no_master, no_master, "NestLogSink",
            ndpi_build_default_ports(ports_a, 11095, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MODBUS,
            no_master, no_master, "Modbus",
            ndpi_build_default_ports(ports_a, 502, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FREE_45,
            no_master, no_master, "Free",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FREE_46,
            no_master, no_master, "Free",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TIKTOK,
            no_master, no_master, "TikTok",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SIGNAL,
            no_master, no_master, "Signal", /* https://signal.org */
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FREE_206,
            no_master, no_master, "Free",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_PPSTREAM,
            no_master, no_master, "PPStream",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_XBOX,
            no_master, no_master, "Xbox",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_QQ,
            no_master, no_master, "QQ",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RTSP,
            no_master, no_master, "RTSP",
            ndpi_build_default_ports(ports_a, 554, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 554, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_ICECAST,
            no_master, no_master, "IceCast",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_PPLIVE,
            no_master, no_master, "PPLive",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_PPSTREAM,
            no_master, no_master, "PPStream",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_ZATTOO,
            no_master, no_master, "Zattoo",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SHOUTCAST,
            no_master, no_master, "ShoutCast",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SOPCAST,
            no_master, no_master, "Sopcast",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TVANTS,
            no_master, no_master, "Tvants",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TVUPLAYER,
            no_master, no_master, "TVUplayer",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HTTP_DOWNLOAD,
            no_master, no_master, "HTTP_Download",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_QQLIVE,
            no_master, no_master, "QQLive",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_THUNDER,
            no_master, no_master, "Thunder",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SOULSEEK,
            no_master, no_master, "Soulseek",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    custom_master[0] = NDPI_PROTOCOL_SSL, custom_master[1] = NDPI_PROTOCOL_UNKNOWN;
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SSL_NO_CERT,
            custom_master, no_master, "SSL_No_Cert",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IRC,
            no_master, no_master, "IRC",
            ndpi_build_default_ports(ports_a, 194, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 194, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_AYIYA,
            no_master, no_master, "Ayiya",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 5072, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_UNENCRYPTED_JABBER,
            no_master, no_master, "Unencrypted_Jabber",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_OSCAR,
            no_master, no_master, "Oscar",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_BATTLEFIELD,
            no_master, no_master, "BattleField",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_VRRP,
            no_master, no_master, "VRRP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_STEAM,
            no_master, no_master, "Steam",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HALFLIFE2,
            no_master, no_master, "HalfLife2",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WORLDOFWARCRAFT,
            no_master, no_master, "WorldOfWarcraft",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HOTSPOT_SHIELD,
            no_master, no_master, "HotspotShield",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TELNET,
            no_master, no_master, "Telnet",
            ndpi_build_default_ports(ports_a, 23, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    custom_master[0] = NDPI_PROTOCOL_SIP, custom_master[1] = NDPI_PROTOCOL_H323;
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_STUN,
            no_master, custom_master, "STUN",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 3478, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_IPSEC,
            no_master, no_master, "IPsec",
            ndpi_build_default_ports(ports_a, 500, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 500, 4500, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_GRE,
            no_master, no_master, "GRE",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_ICMP,
            no_master, no_master, "ICMP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_IGMP,
            no_master, no_master, "IGMP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_EGP,
            no_master, no_master, "EGP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_SCTP,
            no_master, no_master, "SCTP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_OSPF,
            no_master, no_master, "OSPF",
            ndpi_build_default_ports(ports_a, 2604, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_IP_IN_IP,
            no_master, no_master, "IP_in_IP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RTP,
            no_master, no_master, "RTP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RDP,
            no_master, no_master, "RDP",
            ndpi_build_default_ports(ports_a, 3389, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_VNC,
            no_master, no_master, "VNC",
            ndpi_build_default_ports(ports_a, 5900, 5901, 5800, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_PCANYWHERE,
            no_master, no_master, "PcAnywhere",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WHATSAPP_VOICE,
            no_master, no_master, "WhatsAppVoice",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WHATSAPP_FILES,
            no_master, no_master, "WhatsAppFiles",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WHATSAPP,
            no_master, no_master, "WhatsApp",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    custom_master[0] = NDPI_PROTOCOL_SSL_NO_CERT, custom_master[1] = NDPI_PROTOCOL_UNKNOWN;
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SSL,
            no_master, custom_master, "SSL",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SSH,
            no_master, no_master, "SSH",
            ndpi_build_default_ports(ports_a, 22, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_USENET,
            no_master, no_master, "Usenet",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MGCP,
            no_master, no_master, "MGCP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IAX,
            no_master, no_master, "IAX",
            ndpi_build_default_ports(ports_a, 4569, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 4569, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_AFP,
            no_master, no_master, "AFP",
            ndpi_build_default_ports(ports_a, 548, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 548, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_GENERIC,
            no_master, no_master, NDPI_CONST_GENERIC_PROTOCOL_NAME,
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_CHECKMK,
            no_master, no_master, "CHECKMK",
            ndpi_build_default_ports(ports_a, 6556, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_STEALTHNET,
            no_master, no_master, "Stealthnet",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_AIMINI,
            no_master, no_master, "Aimini",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SIP,
            no_master, no_master, "SIP",
            ndpi_build_default_ports(ports_a, 5060, 5061, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 5060, 5061, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TRUPHONE,
            no_master, no_master, "TruPhone",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_IP_ICMPV6,
            no_master, no_master, "ICMPV6",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DHCPV6,
            no_master, no_master, "DHCPV6",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_ARMAGETRON,
            no_master, no_master, "Armagetron",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_CROSSFIRE,
            no_master, no_master, "Crossfire",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DOFUS,
            no_master, no_master, "Dofus",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FIESTA,
            no_master, no_master, "Fiesta",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FLORENSIA,
            no_master, no_master, "Florensia",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_GUILDWARS,
            no_master, no_master, "Guildwars",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HTTP_ACTIVESYNC,
            no_master, no_master, "HTTP_ActiveSync",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_KERBEROS,
            no_master, no_master, "Kerberos",
            ndpi_build_default_ports(ports_a, 88, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 88, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_LDAP,
            no_master, no_master, "LDAP",
            ndpi_build_default_ports(ports_a, 389, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 389, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MAPLESTORY,
            no_master, no_master, "MapleStory",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MSSQL_TDS,
            no_master, no_master, "MsSQL-TDS",
            ndpi_build_default_ports(ports_a, 1433, 1434, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_PPTP,
            no_master, no_master, "PPTP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WARCRAFT3,
            no_master, no_master, "Warcraft3",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WORLD_OF_KUNG_FU,
            no_master, no_master, "WorldOfKungFu",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DCERPC,
            no_master, no_master, "DCE_RPC",
            ndpi_build_default_ports(ports_a, 135, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NETFLOW,
            no_master, no_master, "NetFlow",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 2055, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SFLOW,
            no_master, no_master, "sFlow",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 6343, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HTTP_CONNECT,
            no_master, no_master, "HTTP_Connect",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HTTP_PROXY,
            no_master, no_master, "HTTP_Proxy",
            ndpi_build_default_ports(ports_a, 8080, 3128, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_CITRIX,
            no_master, no_master, "Citrix",
            ndpi_build_default_ports(ports_a, 1494, 2598, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WEBEX,
            no_master, no_master, "Webex",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RADIUS,
            no_master, no_master, "Radius",
            ndpi_build_default_ports(ports_a, 1812, 1813, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 1812, 1813, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TEAMVIEWER,
            no_master, no_master, "TeamViewer",
            ndpi_build_default_ports(ports_a, 5938, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 5938, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_LOTUS_NOTES,
            no_master, no_master, "LotusNotes",
            ndpi_build_default_ports(ports_a, 1352, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    /* Missing dissector: port based only */
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SAP,
            no_master, no_master, "SAP",
            ndpi_build_default_ports(ports_a, 3201, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_GTP,
            no_master, no_master, "GTP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 2152, 2123, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_UPNP,
            no_master, no_master, "UPnP",
            ndpi_build_default_ports(ports_a, 1780, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 1900, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TELEGRAM,
            no_master, no_master, "Telegram",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_QUIC,
            no_master, no_master, "QUIC",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 443, 80, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DIAMETER,
            no_master, no_master, "Diameter",
            ndpi_build_default_ports(ports_a, 3868, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_APPLE_PUSH,
            no_master, no_master, "ApplePush",
            ndpi_build_default_ports(ports_a, 1, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DROPBOX,
            no_master, no_master, "Dropbox",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 17500, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SPOTIFY,
            no_master, no_master, "Spotify",
            ndpi_build_default_ports(ports_a, 4070, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_LISP,
            no_master, no_master, "LISP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 4342, 4341, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_EAQ,
            no_master, no_master, "EAQ",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 6000, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_KAKAOTALK_VOICE,
            no_master, no_master, "KakaoTalk_Voice",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MPEGTS,
            no_master, no_master, "MPEG_TS",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    /* Missing dissector: port based only */
    /* http://en.wikipedia.org/wiki/Link-local_Multicast_Name_Resolution */
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_LLMNR,
            no_master, no_master, "LLMNR",
            ndpi_build_default_ports(ports_a, 5355, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 5355, 0, 0, 0, 0) /* UDP */);

    /* Missing dissector: port based only */
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_REMOTE_SCAN,
            no_master, no_master, "RemoteScan",
            ndpi_build_default_ports(ports_a, 6077, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 6078, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_H323,
            no_master, no_master,"H323",
            ndpi_build_default_ports(ports_a, 1719, 1720, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 1719, 1720, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_OPENVPN,
            no_master, no_master, "OpenVPN",
            ndpi_build_default_ports(ports_a, 1194, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 1194, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NOE,
            no_master, no_master, "NOE",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_CISCOVPN,
            no_master, no_master, "CiscoVPN",
            ndpi_build_default_ports(ports_a, 10000, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 10000, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TEAMSPEAK,
            no_master, no_master, "TeamSpeak",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SKINNY,
            no_master, no_master, "CiscoSkinny",
            ndpi_build_default_ports(ports_a, 2000, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RTCP,
            no_master, no_master, "RTCP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_ORACLE,
            no_master, no_master, "Oracle",
            ndpi_build_default_ports(ports_a, 1521, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_CORBA,
            no_master, no_master, "Corba",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_UBUNTUONE,
            no_master, no_master, "UbuntuONE",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_WHOIS_DAS,
            no_master, no_master, "Whois-DAS",
            ndpi_build_default_ports(ports_a, 43, 4343, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_COLLECTD,
            no_master, no_master, "Collectd",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 25826, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SOCKS,
            no_master, no_master, "SOCKS",
            ndpi_build_default_ports(ports_a, 1080, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 1080, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TFTP,
            no_master, no_master, "TFTP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 69, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RTMP,
            no_master, no_master, "RTMP",
            ndpi_build_default_ports(ports_a, 1935, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_PANDO,
            no_master, no_master, "Pando_Media_Booster",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MEGACO,
            no_master, no_master, "Megaco",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 2944, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_REDIS,
            no_master, no_master, "Redis",
            ndpi_build_default_ports(ports_a, 6379, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_ZMQ,
            no_master, no_master, "ZeroMQ",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_VHUA,
            no_master, no_master, "VHUA",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 58267, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_STARCRAFT,
            no_master, no_master, "Starcraft",
            ndpi_build_default_ports(ports_a, 1119, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 1119, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_UBNTAC2,
            no_master, no_master, "UBNTAC2",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 10001, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: Too many false positives
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_VIBER,
            no_master, no_master, "Viber",
            ndpi_build_default_ports(ports_a, 7985, 5242, 5243, 4244, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 7985, 7987, 5242, 5243, 4244) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_COAP,
            no_master, no_master, "COAP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 5683, 5684, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_MQTT,
            no_master, no_master, "MQTT",
            ndpi_build_default_ports(ports_a, 1883, 8883, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SOMEIP,
            no_master, no_master, "SOMEIP",
            ndpi_build_default_ports(ports_a, 30491, 30501, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 30491, 30501, 30490, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_RX,
            no_master, no_master, "RX",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_GIT,
            no_master, no_master, "Git",
            ndpi_build_default_ports(ports_a, 9418, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DRDA,
            no_master, no_master, "DRDA",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_HANGOUT,
            no_master, no_master, "GoogleHangout",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_BJNP,
            no_master, no_master, "BJNP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 8612, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_SMPP,
            no_master, no_master, "SMPP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_OOKLA,
            no_master, no_master, "Ookla",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_AMQP,
            no_master, no_master, "AMQP",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#if 0 // XXX: No dissector
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_DNSCRYPT,
            no_master, no_master, "DNScrypt",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
#endif
    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_TINC,
            no_master, no_master, "TINC",
            ndpi_build_default_ports(ports_a, 655, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 655, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_FIX,
            no_master, no_master, "FIX",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_NINTENDO,
            no_master, no_master, "Nintendo",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_CSGO,
            no_master, no_master, "CSGO",
            ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

    ndpi_set_proto_defaults(ndpi_mod, NDPI_PROTOCOL_AJP,
            no_master, no_master, "AJP",
            ndpi_build_default_ports(ports_a, 8009, 0, 0, 0, 0) /* TCP */,
            ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
}

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

static int ac_match_handler(AC_MATCH_t *m, AC_TEXT_t *txt, AC_REP_t *match) {
    int min_len = (txt->length < m->patterns->length) ? txt->length : m->patterns->length;
    char buf[64] = { '\0' };
    int min_buf_len = (txt->length > 63 /* sizeof(buf)-1 */) ? 63 : txt->length;
    u_int buf_len = strlen(buf);

    strncpy(buf, txt->astring, min_buf_len);
    buf[min_buf_len] = '\0';

#ifdef MATCH_DEBUG
    printf("Searching [to search: %s/%u][pattern: %s/%u] [len: %u][match_num: %u][%s]\n",
            buf, txt->length, m->patterns->astring, m->patterns->length, min_len,
            m->match_num, m->patterns->astring);
#endif

    {
        char *whatfound = strstr(buf, m->patterns->astring);

#ifdef MATCH_DEBUG
        printf("[NDPI] %s() [searching=%s][pattern=%s][%s][%c]\n",
                __FUNCTION__, buf,  m->patterns->astring,
                whatfound ? whatfound : "<NULL>",
                whatfound[-1]);
#endif

        /*
           The patch below allows in case of pattern ws.amazon.com
           to avoid matching aws.amazon.com whereas a.ws.amazon.com
           has to match
           */
        if(whatfound && (whatfound != buf)
                && (m->patterns->astring[0] != '.')  /* The searched patter does not start with . */
                && strchr(m->patterns->astring, '.') /* The matched pattern has a . (e.g. numeric or sym IPs) */
                && (whatfound[-1] != '.')
          )
            return(0);
    }

    /*
       Return 1 for stopping to the first match.
       We might consider searching for the more
       specific match, paying more cpu cycles.
       */
    memcpy(match, &m->patterns[0].rep, sizeof(AC_REP_t));

    if(((buf_len >= min_len) && (strncmp(&buf[buf_len-min_len], m->patterns->astring, min_len) == 0))
            || (strncmp(buf, m->patterns->astring, min_len) == 0) /* begins with */
      ) {
#ifdef MATCH_DEBUG
        printf("Found match [%s][%s] [len: %u][proto_id: %u]\n",
                buf, m->patterns->astring, min_len , *matching_protocol_id);
#endif
        return(1); /* If the pattern found matches the string at the beginning we stop here */
    } else
        return 0; /* 0 to continue searching, !0 to stop */
}

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

static int fill_prefix_v4(prefix_t *p, struct in_addr *a, int b, int mb) {
    do {
        if(b < 0 || b > mb)
            return(-1);

        memset(p, 0, sizeof(prefix_t));
        memcpy(&p->add.sin, a, (mb+7)/8);
        p->family = AF_INET;
        p->bitlen = b;
        p->ref_count = 0;
    } while (0);

    return(0);
}

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

u_int16_t ndpi_network_ptree_match(struct ndpi_detection_module_struct *ndpi_struct,
        struct in_addr *pin /* network byte order */) {
    prefix_t prefix;
    patricia_node_t *node;

    /* Make sure all in network byte order otherwise compares wont work */
    fill_prefix_v4(&prefix, pin, 32, ((patricia_tree_t*)ndpi_struct->protocols_ptree)->maxbits);
    node = ndpi_patricia_search_best(ndpi_struct->protocols_ptree, &prefix);

    return(node ? node->value.user_value : NDPI_PROTOCOL_UNKNOWN);
}

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

#if 0
static u_int8_t tor_ptree_match(struct ndpi_detection_module_struct *ndpi_struct, struct in_addr *pin) {
    return((ndpi_network_ptree_match(ndpi_struct, pin) == NDPI_PROTOCOL_TOR) ? 1 : 0);
}
#endif

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

u_int8_t ndpi_is_tor_flow(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow) {
    struct ndpi_packet_struct *packet = &flow->packet;

    if(packet->tcp != NULL) {
        if(packet->iph) {
            if(flow->guessed_host_proto_id == NDPI_PROTOCOL_TOR)
                return(1);
        }
    }

    return(0);
}

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

static patricia_node_t* add_to_ptree(patricia_tree_t *tree, int family,
        void *addr, int bits) {
    prefix_t prefix;
    patricia_node_t *node;

    fill_prefix_v4(&prefix, (struct in_addr*)addr, bits, tree->maxbits);

    node = ndpi_patricia_lookup(tree, &prefix);

    return(node);
}

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

static void ndpi_init_ptree_ipv4(struct ndpi_detection_module_struct *ndpi_str,
        void *ptree, ndpi_network host_list[]) {
    int i;

    for(i=0; host_list[i].network != 0x0; i++) {
        struct in_addr pin;
        patricia_node_t *node;

        pin.s_addr = htonl(host_list[i].network);
        if((node = add_to_ptree(ptree, AF_INET,
                        &pin, host_list[i].cidr /* bits */)) != NULL)
            node->value.user_value = host_list[i].value;
    }
}

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

void ndpi_add_to_ptree_ipv4(struct ndpi_detection_module_struct *ndpi_str,
        void *ptree, ndpi_network *host_entry) {
    struct in_addr pin;
    patricia_node_t *node;

    pin.s_addr = htonl(host_entry->network);
    if((node = add_to_ptree(ptree, AF_INET, &pin, host_entry->cidr /* bits */)) != NULL)
        node->value.user_value = host_entry->value;
}

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

static int ndpi_add_ip_app_proto(struct ndpi_detection_module_struct *ndpi_struct,
        char *value, int proto_id) {

    patricia_node_t *node;
    struct in_addr pin;
    int bits = 32;
    char *ptr = strrchr(value, '/');

    if(ptr) {
        ptr[0] = '\0';
        ptr++;
        if(atoi(ptr)>=0 && atoi(ptr)<=32)
            bits = atoi(ptr);
    }

    inet_pton(AF_INET, value, &pin);

    if((node = add_to_ptree(ndpi_struct->protocols_ptree, AF_INET, &pin, bits)) != NULL)
        node->value.user_value = proto_id;

    return 0;
}

void set_ndpi_malloc(void* (*__ndpi_malloc)(size_t size)) { _ndpi_malloc = __ndpi_malloc; }
void set_ndpi_flow_malloc(void* (*__ndpi_flow_malloc)(size_t size)) { _ndpi_flow_malloc = __ndpi_flow_malloc; }

void set_ndpi_free(void  (*__ndpi_free)(void *ptr))       { _ndpi_free = __ndpi_free; }
void set_ndpi_flow_free(void  (*__ndpi_flow_free)(void *ptr))       { _ndpi_flow_free = __ndpi_flow_free; }

void ndpi_debug_printf(unsigned int proto, struct ndpi_detection_module_struct *ndpi_str,
        ndpi_log_level_t log_level, const char *file_name, const char *func_name, int line_number,
        const char * format, ...)
{
#ifdef NDPI_ENABLE_DEBUG_MESSAGES
    va_list args;
#define MAX_STR_LEN 250
    char str[MAX_STR_LEN];
    if(ndpi_str != NULL && log_level > NDPI_LOG_ERROR &&
            proto > 0 && proto < NDPI_MAX_SUPPORTED_PROTOCOLS &&
            !NDPI_ISSET(&ndpi_str->debug_bitmask,proto)) return;
    va_start(args, format);
    vsnprintf(str,sizeof(str)-1, format, args);
    va_end(args);

    if (ndpi_str != NULL) {
        printf("%s:%s:%-3u - [%s]: %s",
                file_name, func_name, line_number, ndpi_get_proto_name(ndpi_str, proto), str);
    } else {
        printf("Proto: %u, %s", proto, str);
    }
#endif
}

void set_ndpi_debug_function(struct ndpi_detection_module_struct *ndpi_str, ndpi_debug_function_ptr ndpi_debug_printf) {
#ifdef NDPI_ENABLE_DEBUG_MESSAGES
    ndpi_str->ndpi_debug_printf = ndpi_debug_printf;
#endif
}

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

struct ndpi_detection_module_struct *ndpi_init_detection_module(void) {
    struct ndpi_detection_module_struct *ndpi_str = ndpi_malloc(sizeof(struct ndpi_detection_module_struct));
    int i;

    if(ndpi_str == NULL) {
#ifdef NDPI_ENABLE_DEBUG_MESSAGES
        NDPI_LOG_ERR(ndpi_str, "ndpi_init_detection_module initial malloc failed for ndpi_str\n");
#endif /* NDPI_ENABLE_DEBUG_MESSAGES */
        return NULL;
    }
    memset(ndpi_str, 0, sizeof(struct ndpi_detection_module_struct));

#ifdef NDPI_ENABLE_DEBUG_MESSAGES
    set_ndpi_debug_function(ndpi_str, (ndpi_debug_function_ptr)ndpi_debug_printf);
#endif /* NDPI_ENABLE_DEBUG_MESSAGES */
#if 0
    if((ndpi_str->protocols_ptree = ndpi_New_Patricia(32 /* IPv4 */)) != NULL)
        ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, host_protocol_list);
#endif
    NDPI_BITMASK_RESET(ndpi_str->detection_bitmask);
#ifdef NDPI_ENABLE_DEBUG_MESSAGES
    ndpi_str->user_data = NULL;
#endif

    ndpi_str->ticks_per_second = 1000; /* ndpi_str->ticks_per_second */
    ndpi_str->tcp_max_retransmission_window_size = NDPI_DEFAULT_MAX_TCP_RETRANSMISSION_WINDOW_SIZE;
    ndpi_str->directconnect_connection_ip_tick_timeout =
        NDPI_DIRECTCONNECT_CONNECTION_IP_TICK_TIMEOUT * ndpi_str->ticks_per_second;

    ndpi_str->rtsp_connection_timeout = NDPI_RTSP_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->tvants_connection_timeout = NDPI_TVANTS_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->irc_timeout = NDPI_IRC_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->gnutella_timeout = NDPI_GNUTELLA_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;

    ndpi_str->battlefield_timeout = NDPI_BATTLEFIELD_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;

    ndpi_str->thunder_timeout = NDPI_THUNDER_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->yahoo_detect_http_connections = NDPI_YAHOO_DETECT_HTTP_CONNECTIONS;

    ndpi_str->yahoo_lan_video_timeout = NDPI_YAHOO_LAN_VIDEO_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->zattoo_connection_timeout = NDPI_ZATTOO_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->jabber_stun_timeout = NDPI_JABBER_STUN_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->jabber_file_transfer_timeout = NDPI_JABBER_FT_TIMEOUT * ndpi_str->ticks_per_second;
    ndpi_str->soulseek_connection_ip_tick_timeout = NDPI_SOULSEEK_CONNECTION_IP_TICK_TIMEOUT * ndpi_str->ticks_per_second;

    ndpi_str->ndpi_num_supported_protocols = NDPI_MAX_SUPPORTED_PROTOCOLS;
    ndpi_str->ndpi_num_custom_protocols = 0;

    ndpi_str->host_automa.ac_automa               = ac_automata_init(ac_match_handler);
    ndpi_str->content_automa.ac_automa            = ac_automata_init(ac_match_handler);

    ndpi_init_protocol_defaults(ndpi_str);

    return ndpi_str;
}

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

static void free_ptree_data(void *data) { ; }

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

/* Wrappers */
void* ndpi_init_ptree(int bits) {
    return(ndpi_New_Patricia(bits));
}

void ndpi_free_ptree(void *_ptree) {
    ndpi_Destroy_Patricia((patricia_tree_t*)_ptree, free_ptree_data);
}

void* ndpi_init_automa(void) {
    return(ac_automata_init(ac_match_handler));
}

int ndpi_add_string_value_to_automa(void *_automa, char *str, unsigned long num) {
    AC_PATTERN_t ac_pattern;
    AC_AUTOMATA_t *automa = (AC_AUTOMATA_t*)_automa;

    if(automa == NULL) return(-1);

    memset(&ac_pattern, 0, sizeof(ac_pattern));
    ac_pattern.astring = str;
    ac_pattern.rep.number = num;
    ac_pattern.length = strlen(ac_pattern.astring);
    return(ac_automata_add(automa, &ac_pattern) == ACERR_SUCCESS ? 0 : -1);
}

int ndpi_add_string_to_automa(void *_automa, char *str) {
    return(ndpi_add_string_value_to_automa(_automa, str, 1));
}

void ndpi_free_automa(void *_automa)     { ac_automata_release((AC_AUTOMATA_t*)_automa);  }
void ndpi_finalize_automa(void *_automa) { ac_automata_finalize((AC_AUTOMATA_t*)_automa); }

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

int ndpi_match_string(void *_automa, char *string_to_match) {
    AC_REP_t match = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED };
    AC_TEXT_t ac_input_text;
    AC_AUTOMATA_t *automa = (AC_AUTOMATA_t*)_automa;

    if((automa == NULL)
            || (string_to_match == NULL)
            || (string_to_match[0] == '\0'))
        return(-2);

    ac_input_text.astring = string_to_match, ac_input_text.length = strlen(string_to_match);
    ac_automata_search(automa, &ac_input_text, &match);
    ac_automata_reset(automa);

    return(match.number > 0 ? 0 : -1);
}

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

int ndpi_match_string_id(void *_automa, char *string_to_match, unsigned long *id) {
    AC_TEXT_t ac_input_text;
    AC_AUTOMATA_t *automa = (AC_AUTOMATA_t*)_automa;
    AC_REP_t match = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED };

    *id = -1;
    if((automa == NULL)
            || (string_to_match == NULL)
            || (string_to_match[0] == '\0'))
        return(-2);

    ac_input_text.astring = string_to_match, ac_input_text.length = strlen(string_to_match);
    ac_automata_search(automa, &ac_input_text, &match);
    ac_automata_reset(automa);

    *id = match.number;

    return(*id != NDPI_PROTOCOL_UNKNOWN ? 0 : -1);
}

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

void ndpi_exit_detection_module(struct ndpi_detection_module_struct *ndpi_struct) {
    if(ndpi_struct != NULL) {
        int i;

        for(i=0; i<(int)ndpi_struct->ndpi_num_supported_protocols; i++) {
            if(ndpi_struct->proto_defaults[i].proto_name)
                ndpi_free(ndpi_struct->proto_defaults[i].proto_name);
        }

        /* NDPI_PROTOCOL_TINC */
        if(ndpi_struct->tinc_cache)
            cache_free((cache_t)(ndpi_struct->tinc_cache));

        if(ndpi_struct->protocols_ptree)
            ndpi_Destroy_Patricia((patricia_tree_t*)ndpi_struct->protocols_ptree,
                    free_ptree_data);

        if (ndpi_struct->udp_root_node != NULL)
            ndpi_tdestroy(ndpi_struct->udp_root_node, ndpi_free);
        if (ndpi_struct->tcp_root_node != NULL)
            ndpi_tdestroy(ndpi_struct->tcp_root_node, ndpi_free);

        if(ndpi_struct->host_automa.ac_automa != NULL)
            ac_automata_release((AC_AUTOMATA_t*)ndpi_struct->host_automa.ac_automa);

        if(ndpi_struct->content_automa.ac_automa != NULL)
            ac_automata_release((AC_AUTOMATA_t*)ndpi_struct->content_automa.ac_automa);

#ifdef HAVE_HYPERSCAN
        destroy_hyperscan(ndpi_struct);

        //free_hyperscan_memory(ndpi_struct->custom_categories.hostnames);
#endif

        ndpi_free(ndpi_struct);
    }
}

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

#define NDPI_MAX_PROTO_NAME_LEN 64

int ndpi_get_protocol_id(struct ndpi_detection_module_struct *ndpi_mod, char *proto) {
    int i;
    const char *p;

    for(i=0; i<(int)ndpi_mod->ndpi_num_supported_protocols; i++) {
        if(ndpi_mod->proto_defaults[i].proto_name[0] >= '0' &&
                ndpi_mod->proto_defaults[i].proto_name[0] <= '9' &&
                (p = (const char *)memchr(ndpi_mod->proto_defaults[i].proto_name, '.', NDPI_MAX_PROTO_NAME_LEN)))
            p++;
        else
            p = ndpi_mod->proto_defaults[i].proto_name;

        if(strcasecmp(proto, p) == 0)
            return(i);
    }

    return(NDPI_PROTOCOL_UNKNOWN);
}

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

int ndpi_get_proto_id_master_proto(struct ndpi_detection_module_struct *ndpi_struct,
        u_int16_t proto_id,
        u_int16_t** tcp_master_proto,
        u_int16_t** udp_master_proto) {
    if(proto_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) {
        *tcp_master_proto = ndpi_struct->proto_defaults[NDPI_PROTOCOL_UNKNOWN].master_tcp_proto_id,
        *udp_master_proto = ndpi_struct->proto_defaults[NDPI_PROTOCOL_UNKNOWN].master_udp_proto_id;
        return(-1);
    }

    *tcp_master_proto = ndpi_struct->proto_defaults[proto_id].master_tcp_proto_id,
        *udp_master_proto = ndpi_struct->proto_defaults[proto_id].master_udp_proto_id;

    return(0);
}

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

static ndpi_default_ports_tree_node_t *ndpi_guess_protocol_node(
        struct ndpi_detection_module_struct *ndpi_struct,
        u_int8_t proto, u_int16_t sport, u_int16_t dport) {

    const void *ret;
    ndpi_default_ports_tree_node_t node;

    if(sport && dport) {
        int low  = ndpi_min(sport, dport);
        int high = ndpi_max(sport, dport);

        node.default_port = low; /* Check server port first */
        ret = ndpi_tfind(&node,
                (proto == IPPROTO_TCP) ?
                (void*)&ndpi_struct->tcp_root_node : (void*)&ndpi_struct->udp_root_node,
                ndpi_default_ports_tree_node_t_cmp);

        if(ret == NULL) {
            node.default_port = high;
            ret = ndpi_tfind(&node,
                    (proto == IPPROTO_TCP) ?
                    (void*)&ndpi_struct->tcp_root_node : (void*)&ndpi_struct->udp_root_node,
                    ndpi_default_ports_tree_node_t_cmp);
        }

        if(ret) return(*(ndpi_default_ports_tree_node_t **)ret);
    }

    return(NULL);
}

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

/*
   These are UDP protocols that must fit a single packet
   and thus that if have NOT been detected they cannot be guessed
   as they have been excluded
   */
u_int8_t is_udp_guessable_proto(u_int16_t l7_guessed_proto) {
    switch(l7_guessed_proto) {
        case NDPI_PROTOCOL_QUIC:
        case NDPI_PROTOCOL_SNMP:
        case NDPI_PROTOCOL_NETFLOW:
            /* TODO: add more protocols (if any missing) */
            return(1);
    }

    return(0);
}

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

u_int16_t ndpi_(
        struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        u_int8_t proto, u_int16_t sport, u_int16_t dport,
        u_int8_t *user_defined_proto) {
    *user_defined_proto = 0; /* Default */

    if(sport && dport) {
        ndpi_default_ports_tree_node_t *found = ndpi_guess_protocol_node(ndpi_struct, proto, sport, dport);

        if(found != NULL) {
            u_int16_t guessed_proto = found->proto->proto_id;

            /* We need to check if the guessed protocol isn't excluded by nDPI */
            if(flow
                    && (proto == IPPROTO_UDP)
                    && NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, guessed_proto)
                    && is_udp_guessable_proto(guessed_proto)
              )
                return(NDPI_PROTOCOL_UNKNOWN);
            else {
                *user_defined_proto = found->custom_user_proto;
                return(guessed_proto);
            }
        }
    } else {
        /* No TCP/UDP */

        switch(proto) {
            case NDPI_IPSEC_PROTOCOL_ESP:
            case NDPI_IPSEC_PROTOCOL_AH:
                return(NDPI_PROTOCOL_IP_IPSEC);
                break;
            case NDPI_GRE_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_GRE);
                break;
            case NDPI_ICMP_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_ICMP);
                break;
            case NDPI_IGMP_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_IGMP);
                break;
            case NDPI_EGP_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_EGP);
                break;
            case NDPI_SCTP_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_SCTP);
                break;
            case NDPI_OSPF_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_OSPF);
                break;
            case NDPI_IPIP_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_IP_IN_IP);
                break;
            case NDPI_ICMPV6_PROTOCOL_TYPE:
                return(NDPI_PROTOCOL_IP_ICMPV6);
                break;
            case 112:
                return(NDPI_PROTOCOL_IP_VRRP);
                break;
        }
    }

    return(NDPI_PROTOCOL_UNKNOWN);
}

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

u_int ndpi_get_num_supported_protocols(struct ndpi_detection_module_struct *ndpi_mod) {
    return(ndpi_mod->ndpi_num_supported_protocols);
}

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

#ifdef WIN32
char * strsep(char **sp, char *sep)
{
    char *p, *s;
    if (sp == NULL || *sp == NULL || **sp == '\0') return(NULL);
    s = *sp;
    p = s + strcspn(s, sep);
    if (*p != '\0') *p++ = '\0';
    *sp = p;
    return(s);
}
#endif

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

int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_mod,
        char* rule, u_int8_t do_add) {

    char *at, *proto, *elem;
    ndpi_proto_defaults_t *def;
    int app_proto_id, i;

    at = strrchr(rule, '@');
    if(at == NULL) {
        NDPI_LOG_ERR(ndpi_mod, "Invalid rule '%s'\n", rule);
        return(-1);
    } else
        at[0] = 0, proto = &at[1];

    for(i=0; proto[i] != '\0'; i++) {
        switch(proto[i]) {
            case '/':
            case '&':
            case '^':
            case ':':
            case ';':
            case '\'':
            case '"':
            case ' ':
                proto[i] = '_';
                break;
        }
    }

    for(i = 0, def = NULL; i < (int)ndpi_mod->ndpi_num_supported_protocols; i++) {
        if(strcasecmp(ndpi_mod->proto_defaults[i].proto_name, proto) == 0) {
            def = &ndpi_mod->proto_defaults[i];
            app_proto_id = i;
            break;
        }
    }

    if(def == NULL) {
        if(!do_add) {
            /* We need to remove a rule */
            NDPI_LOG_ERR(ndpi_mod, "Unable to find protocol '%s': skipping rule '%s'\n", proto, rule);
            return(-3);
        } else {
            ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS];
            u_int16_t no_master[2] = { NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO };

            if(ndpi_mod->ndpi_num_custom_protocols >= (NDPI_MAX_NUM_CUSTOM_PROTOCOLS-1)) {
                NDPI_LOG_ERR(ndpi_mod, "Too many protocols defined (%u): skipping protocol %s\n",
                        ndpi_mod->ndpi_num_custom_protocols, proto);
                return(-2);
            }

            ndpi_set_proto_defaults(ndpi_mod, ndpi_mod->ndpi_num_supported_protocols,
                    no_master, no_master, proto,
                    ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
                    ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

            def = &ndpi_mod->proto_defaults[ndpi_mod->ndpi_num_supported_protocols];
            app_proto_id = ndpi_mod->ndpi_num_supported_protocols;
            ndpi_mod->ndpi_num_supported_protocols++, ndpi_mod->ndpi_num_custom_protocols++;
        }
    }

    while((elem = strsep(&rule, ",")) != NULL) {
        ndpi_port_range range;
        char *attr = elem, *value = NULL;
        int is_tcp = 0, is_udp = 0, is_ip = 0;

        if(strncmp(attr, "tcp:", 4) == 0) {
            /* tcp:"<value>",tcp:"<value>",.....@<app_proto> */
            is_tcp = 1, value = &attr[4];
        }
        else if(strncmp(attr, "udp:", 4) == 0) {
            /* udp:"<value>",udp:"<value>",.....@<app_proto> */
            is_udp = 1, value = &attr[4];
        }
        else if(strncmp(attr, "ip:", 3) == 0) {
            /* ip:"<value>",ip:"<value>",.....@<app_proto> */
            is_ip = 1, value = &attr[3];
        }
        else if(strncmp(attr, "host:", 5) == 0) {
            /* host:"<value>",host:"<value>",.....@<app_proto> */
            value = &attr[5];
            if(value[0] == '"') value++; /* remove leading " */
            if(value[strlen(value)-1] == '"') value[strlen(value)-1] = '\0'; /* remove trailing " */
        }
        else {
            NDPI_LOG_ERR(ndpi_mod, "Unrecognized attribute: %s\n", attr);
            return(-4);
        }

        if(is_tcp || is_udp) {
            if(sscanf(value, "%u-%u", /* port range? */
                        (u_int32_t *)&range.port_low, (u_int32_t *)&range.port_high) != 2)
                range.port_low = range.port_high = atoi(&elem[4]); /* single port */

            if(do_add) {
                ndpi_add_default_port(ndpi_mod, &range, def, 1 /* Custom user proto */,
                        is_tcp ? &ndpi_mod->tcp_root_node : &ndpi_mod->udp_root_node);
            }
            else {
                ndpi_remove_default_port(&range, def,
                        is_tcp ? &ndpi_mod->tcp_root_node : &ndpi_mod->udp_root_node);
            }
        }
        else if(is_ip) {
            ndpi_add_ip_app_proto(ndpi_mod, value, app_proto_id);
        }
        else {
            if(do_add)
                ndpi_add_host_app_proto(ndpi_mod, ndpi_strdup(value), app_proto_id);
            else
                ndpi_remove_host_app_proto(ndpi_mod, value, app_proto_id);
        }
    }

    return(0);
}

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

/*
Format:
<tcp|udp>:<port>,<tcp|udp>:<port>,.....@<proto>

Subprotocols Format:
host:"<value>",host:"<value>",.....@<subproto>

IP based Subprotocols Format (<value> is IP or CIDR):
ip:<value>,ip:<value>,.....@<subproto>

Example:
tcp:80,tcp:3128@HTTP
udp:139@NETBIOS

*/
int ndpi_load_protocols_file(struct ndpi_detection_module_struct *ndpi_mod, const char* path) {
    FILE *fd;
    char *buffer, *old_buffer;
    int chunk_len = 512, buffer_len = chunk_len, old_buffer_len;
    int i, rc = -1;

    fd = fopen(path, "r");

    if(fd == NULL) {
        NDPI_LOG_ERR(ndpi_mod, "Unable to open file %s [%s]\n", path, strerror(errno));
        goto error;
    }

    buffer = ndpi_malloc(buffer_len);

    if(buffer == NULL) {
        NDPI_LOG_ERR(ndpi_mod, "Memory allocation failure\n");
        goto close_fd;
    }

    while(fd) {
        char *line = buffer;
        int line_len = buffer_len;

        while((line = fgets(line, line_len, fd)) != NULL && line[strlen(line)-1] != '\n') {
            i = strlen(line);
            old_buffer = buffer;
            old_buffer_len = buffer_len;
            buffer_len += chunk_len;

            buffer = ndpi_realloc(old_buffer, old_buffer_len, buffer_len);

            if(buffer == NULL) {
                NDPI_LOG_ERR(ndpi_mod, "Memory allocation failure\n");
                free(old_buffer);
                goto close_fd;
            }

            line = &buffer[i];
            line_len = chunk_len;
        }

        if(!line) /* safety check */
            break;

        i = strlen(buffer);
        if((i <= 1) || (buffer[0] == '#'))
            continue;
        else
            buffer[i-1] = '\0';

        ndpi_handle_rule(ndpi_mod, buffer, 1);
    }

    rc = 0;

    free(buffer);

close_fd:
    fclose(fd);

error:
    return(rc);
}

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

/* ntop */
void ndpi_set_bitmask_protocol_detection(char * label,
        struct ndpi_detection_module_struct *ndpi_struct,
        const NDPI_PROTOCOL_BITMASK * detection_bitmask,
        const u_int32_t idx,
        u_int16_t ndpi_proto_id,
        void (*func) (struct ndpi_detection_module_struct *, struct ndpi_flow_struct *flow),
        const NDPI_SELECTION_BITMASK_PROTOCOL_SIZE ndpi_selection_bitmask,
        u_int8_t b_save_bitmask_unknow,
        u_int8_t b_add_detection_bitmask) {
    /*
       Compare specify protocol bitmask with main detection bitmask
       */
    if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(*detection_bitmask, ndpi_proto_id) != 0) {
#ifdef DEBUG
        NDPI_LOG_DBG2(ndpi_struct
                "[NDPI] ndpi_set_bitmask_protocol_detection: %s : [callback_buffer] idx= %u, [proto_defaults] proto_id=%u\n",
                label, idx, ndpi_proto_id);
#endif

        if(ndpi_struct->proto_defaults[ndpi_proto_id].proto_idx != 0) {
            NDPI_LOG_DBG2(ndpi_struct,
                    "[NDPI] Internal error: protocol %s/%u has been already registered\n", label, ndpi_proto_id);
#ifdef DEBUG
        } else {
            NDPI_LOG_DBG2(ndpi_struct,
                    "[NDPI] Adding %s with protocol id %d\n", label, ndpi_proto_id);
#endif
        }

        /*
           Set function and index protocol within proto_default structure for port protocol detection
           and callback_buffer function for DPI protocol detection
           */
        ndpi_struct->proto_defaults[ndpi_proto_id].proto_idx = idx;
        ndpi_struct->proto_defaults[ndpi_proto_id].func = ndpi_struct->callback_buffer[idx].func = func;

        /*
           Set ndpi_selection_bitmask for protocol
           */
        ndpi_struct->callback_buffer[idx].ndpi_selection_bitmask = ndpi_selection_bitmask;

        /*
           Reset protocol detection bitmask via NDPI_PROTOCOL_UNKNOWN and than add specify protocol bitmast to callback
           buffer.
           */
        if(b_save_bitmask_unknow) NDPI_SAVE_AS_BITMASK(ndpi_struct->callback_buffer[idx].detection_bitmask, NDPI_PROTOCOL_UNKNOWN);
        if(b_add_detection_bitmask) NDPI_ADD_PROTOCOL_TO_BITMASK(ndpi_struct->callback_buffer[idx].detection_bitmask, ndpi_proto_id);

        NDPI_SAVE_AS_BITMASK(ndpi_struct->callback_buffer[idx].excluded_protocol_bitmask, ndpi_proto_id);
    }
}

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

void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *ndpi_struct,
        const NDPI_PROTOCOL_BITMASK * dbm) {
    NDPI_PROTOCOL_BITMASK detection_bitmask_local;
    NDPI_PROTOCOL_BITMASK *detection_bitmask = &detection_bitmask_local;
    u_int32_t a = 0;

    NDPI_BITMASK_SET(detection_bitmask_local, *dbm);
    NDPI_BITMASK_SET(ndpi_struct->detection_bitmask, *dbm);

    /* set this here to zero to be interrupt safe */
    ndpi_struct->callback_buffer_size = 0;

    /* HTTP */
    init_http_dissector(ndpi_struct, &a, detection_bitmask);

    /* STARCRAFT */
    init_starcraft_dissector(ndpi_struct, &a, detection_bitmask);

    /* SSL */
    init_ssl_dissector(ndpi_struct, &a, detection_bitmask);

    /* STUN */
    init_stun_dissector(ndpi_struct, &a, detection_bitmask);

    /* RTP */
    init_rtp_dissector(ndpi_struct, &a, detection_bitmask);

    /* RTSP */
    init_rtsp_dissector(ndpi_struct, &a, detection_bitmask);

    /* RDP */
    init_rdp_dissector(ndpi_struct, &a, detection_bitmask);

    /* SIP */
    init_sip_dissector(ndpi_struct, &a, detection_bitmask);

    /* HEP */
    init_hep_dissector(ndpi_struct, &a, detection_bitmask);

    /* Teredo */
    init_teredo_dissector(ndpi_struct, &a, detection_bitmask);

    /* EDONKEY */
    init_edonkey_dissector(ndpi_struct, &a, detection_bitmask);

    /* FASTTRACK */
    init_fasttrack_dissector(ndpi_struct, &a, detection_bitmask);

    /* GNUTELLA */
    init_gnutella_dissector(ndpi_struct, &a, detection_bitmask);

    /* DIRECTCONNECT */
    init_directconnect_dissector(ndpi_struct, &a, detection_bitmask);

    /* MSN */
    init_msn_dissector(ndpi_struct, &a, detection_bitmask);

    /* YAHOO */
    init_yahoo_dissector(ndpi_struct, &a, detection_bitmask);

    /* OSCAR */
    init_oscar_dissector(ndpi_struct, &a, detection_bitmask);

    /* APPLEJUICE */
    init_applejuice_dissector(ndpi_struct, &a, detection_bitmask);

    /* SOULSEEK */
    init_soulseek_dissector(ndpi_struct, &a, detection_bitmask);

    /* SOCKS */
    init_socks_dissector(ndpi_struct, &a, detection_bitmask);

    /* IRC */
    init_irc_dissector(ndpi_struct, &a, detection_bitmask);

    /* JABBER */
    init_jabber_dissector(ndpi_struct, &a, detection_bitmask);

    /* MAIL_POP */
    init_mail_pop_dissector(ndpi_struct, &a, detection_bitmask);

    /* MAIL_IMAP */
    init_mail_imap_dissector(ndpi_struct, &a, detection_bitmask);

    /* MAIL_SMTP */
    init_mail_smtp_dissector(ndpi_struct, &a, detection_bitmask);

    /* USENET */
    init_usenet_dissector(ndpi_struct, &a, detection_bitmask);

    /* DNS */
    init_dns_dissector(ndpi_struct, &a, detection_bitmask);

    /* FILETOPIA */
    init_fbzero_dissector(ndpi_struct, &a, detection_bitmask);

    /* VMWARE */
    init_vmware_dissector(ndpi_struct, &a, detection_bitmask);

    /* NON_TCP_UDP */
    init_non_tcp_udp_dissector(ndpi_struct, &a, detection_bitmask);

    /* TVANTS */
    init_tvants_dissector(ndpi_struct, &a, detection_bitmask);

    /* SOPCAST */
    init_sopcast_dissector(ndpi_struct, &a, detection_bitmask);

    /* TVUPLAYER */
    init_tvuplayer_dissector(ndpi_struct, &a, detection_bitmask);

    /* PPSTREAM */
    init_ppstream_dissector(ndpi_struct, &a, detection_bitmask);

    /* PPLIVE */
    init_pplive_dissector(ndpi_struct, &a, detection_bitmask);

    /* IAX */
    init_iax_dissector(ndpi_struct, &a, detection_bitmask);

    /* MGPC */
    init_mgpc_dissector(ndpi_struct, &a, detection_bitmask);

    /* ZATTOO */
    init_zattoo_dissector(ndpi_struct, &a, detection_bitmask);

    /* QQ */
    init_qq_dissector(ndpi_struct, &a, detection_bitmask);

    /* SSH */
    init_ssh_dissector(ndpi_struct, &a, detection_bitmask);

    /* AYIYA */
    init_ayiya_dissector(ndpi_struct, &a, detection_bitmask);

    /* THUNDER */
    init_thunder_dissector(ndpi_struct, &a, detection_bitmask);

    /* VNC */
    init_vnc_dissector(ndpi_struct, &a, detection_bitmask);

    /* TEAMVIEWER */
    init_teamviewer_dissector(ndpi_struct, &a, detection_bitmask);

    /* DHCP */
    init_dhcp_dissector(ndpi_struct, &a, detection_bitmask);

    /* STEAM */
    init_steam_dissector(ndpi_struct, &a, detection_bitmask);

    /* HALFLIFE2 */
    init_halflife2_dissector(ndpi_struct, &a, detection_bitmask);

    /* XBOX */
    init_xbox_dissector(ndpi_struct, &a, detection_bitmask);

    /* HTTP_APPLICATION_ACTIVESYNC */
    init_http_activesync_dissector(ndpi_struct, &a, detection_bitmask);

    /* SMB */
    init_smb_dissector(ndpi_struct, &a, detection_bitmask);

    /* MINING */
    init_mining_dissector(ndpi_struct, &a, detection_bitmask);

    /* TELNET */
    init_telnet_dissector(ndpi_struct, &a, detection_bitmask);

    /* NTP */
    init_ntp_dissector(ndpi_struct, &a, detection_bitmask);

    /* NFS */
    init_nfs_dissector(ndpi_struct, &a, detection_bitmask);

    /* SSDP */
    init_ssdp_dissector(ndpi_struct, &a, detection_bitmask);

    /* WORLD_OF_WARCRAFT */
    init_world_of_warcraft_dissector(ndpi_struct, &a, detection_bitmask);

    /* POSTGRES */
    init_postgres_dissector(ndpi_struct, &a, detection_bitmask);

    /* MYSQL */
    init_mysql_dissector(ndpi_struct, &a, detection_bitmask);

    /* BGP */
    init_bgp_dissector(ndpi_struct, &a, detection_bitmask);

    /* BATTLEFIELD */
    init_battlefield_dissector(ndpi_struct, &a, detection_bitmask);

    /* PCANYWHERE */
    init_pcanywhere_dissector(ndpi_struct, &a, detection_bitmask);

    /* SNMP */
    init_snmp_dissector(ndpi_struct, &a, detection_bitmask);

    /* KONTIKI */
    init_kontiki_dissector(ndpi_struct, &a, detection_bitmask);

    /* ICECAST */
    init_icecast_dissector(ndpi_struct, &a, detection_bitmask);

    /* SHOUTCAST */
    init_shoutcast_dissector(ndpi_struct, &a, detection_bitmask);

    /* KERBEROS */
    init_kerberos_dissector(ndpi_struct, &a, detection_bitmask);

    /* OPENFT */
    init_openft_dissector(ndpi_struct, &a, detection_bitmask);

    /* SYSLOG */
    init_syslog_dissector(ndpi_struct, &a, detection_bitmask);

    /* DIRECT_DOWNLOAD_LINK */
    init_directdownloadlink_dissector(ndpi_struct, &a, detection_bitmask);

    /* NETBIOS */
    init_netbios_dissector(ndpi_struct, &a, detection_bitmask);

    /* MDNS */
    init_mdns_dissector(ndpi_struct, &a, detection_bitmask);

    /* IPP */
    init_ipp_dissector(ndpi_struct, &a, detection_bitmask);

    /* LDAP */
    init_ldap_dissector(ndpi_struct, &a, detection_bitmask);

    /* WARCRAFT3 */
    init_warcraft3_dissector(ndpi_struct, &a, detection_bitmask);

    /* XDMCP */
    init_xdmcp_dissector(ndpi_struct, &a, detection_bitmask);

    /* TFTP */
    init_tftp_dissector(ndpi_struct, &a, detection_bitmask);

    /* MSSQL_TDS */
    init_mssql_tds_dissector(ndpi_struct, &a, detection_bitmask);

    /* PPTP */
    init_pptp_dissector(ndpi_struct, &a, detection_bitmask);

    /* STEALTHNET */
    init_stealthnet_dissector(ndpi_struct, &a, detection_bitmask);

    /* DHCPV6 */
    init_dhcpv6_dissector(ndpi_struct, &a, detection_bitmask);

    /* AFP */
    init_afp_dissector(ndpi_struct, &a, detection_bitmask);

    /* check_mk */
    init_checkmk_dissector(ndpi_struct, &a, detection_bitmask);

    /* AIMINI */
    init_aimini_dissector(ndpi_struct, &a, detection_bitmask);

    /* FLORENSIA */
    init_florensia_dissector(ndpi_struct, &a, detection_bitmask);

    /* MAPLESTORY */
    init_maplestory_dissector(ndpi_struct, &a, detection_bitmask);

    /* DOFUS */
    init_dofus_dissector(ndpi_struct, &a, detection_bitmask);

    /* WORLD_OF_KUNG_FU */
    init_world_of_kung_fu_dissector(ndpi_struct, &a, detection_bitmask);

    /* FIESTA */
    init_fiesta_dissector(ndpi_struct, &a, detection_bitmask);

    /* CROSSIFIRE */
    init_crossfire_dissector(ndpi_struct, &a, detection_bitmask);

    /* GUILDWARS */
    init_guildwars_dissector(ndpi_struct, &a, detection_bitmask);

    /* ARMAGETRON */
    init_armagetron_dissector(ndpi_struct, &a, detection_bitmask);

    /* DROPBOX */
    init_dropbox_dissector(ndpi_struct, &a, detection_bitmask);

    /* SPOTIFY */
    init_spotify_dissector(ndpi_struct, &a, detection_bitmask);

    /* RADIUS */
    init_radius_dissector(ndpi_struct, &a, detection_bitmask);

    /* CITRIX */
    init_citrix_dissector(ndpi_struct, &a, detection_bitmask);

    /* LOTUS_NOTES */
    init_lotus_notes_dissector(ndpi_struct, &a, detection_bitmask);

    /* GTP */
    init_gtp_dissector(ndpi_struct, &a, detection_bitmask);

    /* DCERPC */
    init_dcerpc_dissector(ndpi_struct, &a, detection_bitmask);

    /* NETFLOW */
    init_netflow_dissector(ndpi_struct, &a, detection_bitmask);

    /* SFLOW */
    init_sflow_dissector(ndpi_struct, &a, detection_bitmask);

    /* H323 */
    init_h323_dissector(ndpi_struct, &a, detection_bitmask);

    /* OPENVPN */
    init_openvpn_dissector(ndpi_struct, &a, detection_bitmask);

    /* NOE */
    init_noe_dissector(ndpi_struct, &a, detection_bitmask);

    /* CISCOVPN */
    init_ciscovpn_dissector(ndpi_struct, &a, detection_bitmask);

    /* TEAMSPEAK */
    init_teamspeak_dissector(ndpi_struct, &a, detection_bitmask);

    /* VIBER
     * Disabled; too many false-positives.
     init_viber_dissector(ndpi_struct, &a, detection_bitmask); */

    /* TOR */
    init_tor_dissector(ndpi_struct, &a, detection_bitmask);

    /* SKINNY */
    init_skinny_dissector(ndpi_struct, &a, detection_bitmask);

    /* RTCP */
    init_rtcp_dissector(ndpi_struct, &a, detection_bitmask);

    /* RSYNC */
    init_rsync_dissector(ndpi_struct, &a, detection_bitmask);

    /* WHOIS_DAS */
    init_whois_das_dissector(ndpi_struct, &a, detection_bitmask);

    /* ORACLE */
    init_oracle_dissector(ndpi_struct, &a, detection_bitmask);

    /* CORBA */
    init_corba_dissector(ndpi_struct, &a, detection_bitmask);

    /* RTMP */
    init_rtmp_dissector(ndpi_struct, &a, detection_bitmask);

    /* FTP_CONTROL */
    init_ftp_control_dissector(ndpi_struct, &a, detection_bitmask);

    /* FTP_DATA
     * Impossible to dissect without many false-positives,
     * or under-matches.
     init_ftp_data_dissector(ndpi_struct, &a, detection_bitmask); */

    /* PANDO */
    init_pando_dissector(ndpi_struct, &a, detection_bitmask);

    /* MEGACO */
    init_megaco_dissector(ndpi_struct, &a, detection_bitmask);

    /* REDIS */
    init_redis_dissector(ndpi_struct, &a, detection_bitmask);

    /* UPnP */
    init_upnp_dissector(ndpi_struct, &a, detection_bitmask);

    /* VHUA */
    init_vhua_dissector(ndpi_struct, &a, detection_bitmask);

    /* ZMQ */
    init_zmq_dissector(ndpi_struct, &a, detection_bitmask);

    /* TELEGRAM */
    init_telegram_dissector(ndpi_struct, &a, detection_bitmask);

    /* QUIC */
    init_quic_dissector(ndpi_struct, &a, detection_bitmask);

    /* DIAMETER */
    init_diameter_dissector(ndpi_struct, &a, detection_bitmask);

    /* APPLE_PUSH */
    init_apple_push_dissector(ndpi_struct, &a, detection_bitmask);

    /* EAQ */
    init_eaq_dissector(ndpi_struct, &a, detection_bitmask);

    /* KAKAOTALK_VOICE */
    init_kakaotalk_voice_dissector(ndpi_struct, &a, detection_bitmask);

    /* MPEGTS */
    init_mpegts_dissector(ndpi_struct, &a, detection_bitmask);

    /* UBNTAC2 */
    init_ubntac2_dissector(ndpi_struct, &a, detection_bitmask);

    /* COAP */
    init_coap_dissector(ndpi_struct, &a, detection_bitmask);

    /* MQTT */
    init_mqtt_dissector(ndpi_struct, &a, detection_bitmask);

    /* SOME/IP */
    init_someip_dissector(ndpi_struct, &a, detection_bitmask);

    /* RX */
    init_rx_dissector(ndpi_struct, &a, detection_bitmask);

    /* GIT */
    init_git_dissector(ndpi_struct, &a, detection_bitmask);

    /* HANGOUT */
    init_hangout_dissector(ndpi_struct, &a, detection_bitmask);

    /* DRDA */
    init_drda_dissector(ndpi_struct, &a, detection_bitmask);

    /* BJNP */
    init_bjnp_dissector(ndpi_struct, &a, detection_bitmask);

    /* SMPP */
    init_smpp_dissector(ndpi_struct, &a, detection_bitmask);

    /* TINC */
    init_tinc_dissector(ndpi_struct, &a, detection_bitmask);

    /* FIX */
    init_fix_dissector(ndpi_struct, &a, detection_bitmask);

    /* NINTENDO */
    init_nintendo_dissector(ndpi_struct, &a, detection_bitmask);

    /* MODBUS */
    init_modbus_dissector(ndpi_struct, &a, detection_bitmask);

    /*** Put false-positive sensitive protocols at the end ***/

    /* SKYPE */
    init_skype_dissector(ndpi_struct, &a, detection_bitmask);

    /* BITTORRENT */
    init_bittorrent_dissector(ndpi_struct, &a, detection_bitmask);

    /* WHATSAPP */
    init_whatsapp_dissector(ndpi_struct, &a, detection_bitmask);

    /* OOKLA
     * XXX: Unnecessary.
     init_ookla_dissector(ndpi_struct, &a, detection_bitmask); */

    /* AMQP */
    init_amqp_dissector(ndpi_struct, &a, detection_bitmask);

    /* CSGO */
    init_csgo_dissector(ndpi_struct, &a, detection_bitmask);

    /* LISP */
    init_lisp_dissector(ndpi_struct, &a, detection_bitmask);

    /* AJP */
    init_ajp_dissector(ndpi_struct, &a, detection_bitmask);

    /* Memcached */
    init_memcached_dissector(ndpi_struct, &a, detection_bitmask);

    /* Nest Log Sink */
    init_nest_log_sink_dissector(ndpi_struct, &a, detection_bitmask);

    /* ----------------------------------------------------------------- */

    ndpi_struct->callback_buffer_size = a;

    NDPI_LOG_DBG2(ndpi_struct,
            "callback_buffer_size is %u\n", ndpi_struct->callback_buffer_size);

    /* now build the specific buffer for tcp, udp and non_tcp_udp */
    ndpi_struct->callback_buffer_size_tcp_payload = 0;
    ndpi_struct->callback_buffer_size_tcp_no_payload = 0;
    for(a = 0; a < ndpi_struct->callback_buffer_size; a++) {
        if((ndpi_struct->callback_buffer[a].ndpi_selection_bitmask
                    & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP |
                        NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP |
                        NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC)) != 0) {
            if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_struct,
                    "callback_buffer_tcp_payload, adding buffer %u as entry %u\n", a,
                    ndpi_struct->callback_buffer_size_tcp_payload);

            memcpy(&ndpi_struct->callback_buffer_tcp_payload[ndpi_struct->callback_buffer_size_tcp_payload],
                    &ndpi_struct->callback_buffer[a], sizeof(struct ndpi_call_function_struct));
            ndpi_struct->callback_buffer_size_tcp_payload++;

            if((ndpi_struct->
                        callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == 0) {
                if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_struct,
                        "\tcallback_buffer_tcp_no_payload, additional adding buffer %u to no_payload process\n", a);

                memcpy(&ndpi_struct->callback_buffer_tcp_no_payload
                        [ndpi_struct->callback_buffer_size_tcp_no_payload], &ndpi_struct->callback_buffer[a],
                        sizeof(struct ndpi_call_function_struct));
                ndpi_struct->callback_buffer_size_tcp_no_payload++;
            }
        }
    }

    ndpi_struct->callback_buffer_size_udp = 0;
    for(a = 0; a < ndpi_struct->callback_buffer_size; a++) {
        if((ndpi_struct->callback_buffer[a].ndpi_selection_bitmask & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP |
                        NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP |
                        NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC))
                != 0) {
            if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_struct,
                    "callback_buffer_size_udp: adding buffer : %u as entry %u\n", a, ndpi_struct->callback_buffer_size_udp);

            memcpy(&ndpi_struct->callback_buffer_udp[ndpi_struct->callback_buffer_size_udp],
                    &ndpi_struct->callback_buffer[a], sizeof(struct ndpi_call_function_struct));
            ndpi_struct->callback_buffer_size_udp++;
        }
    }

    ndpi_struct->callback_buffer_size_non_tcp_udp = 0;
    for(a = 0; a < ndpi_struct->callback_buffer_size; a++) {
        if((ndpi_struct->callback_buffer[a].ndpi_selection_bitmask & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP |
                        NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP |
                        NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP))
                == 0
                || (ndpi_struct->
                    callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC) != 0) {
            if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_struct,
                    "callback_buffer_non_tcp_udp: adding buffer : %u as entry %u\n", a, ndpi_struct->callback_buffer_size_non_tcp_udp);

            memcpy(&ndpi_struct->callback_buffer_non_tcp_udp[ndpi_struct->callback_buffer_size_non_tcp_udp],
                    &ndpi_struct->callback_buffer[a], sizeof(struct ndpi_call_function_struct));
            ndpi_struct->callback_buffer_size_non_tcp_udp++;
        }
    }
}

#ifdef NDPI_DETECTION_SUPPORT_IPV6
/* handle extension headers in IPv6 packets
 * arguments:
 *  l4ptr: pointer to the byte following the initial IPv6 header
 *  l4len: the length of the IPv6 packet excluding the IPv6 header
 *  nxt_hdr: next header value from the IPv6 header
 * result:
 *  l4ptr: pointer to the start of the actual packet payload
 *  l4len: length of the actual payload
 *  nxt_hdr: protocol of the actual payload
 * returns 0 upon success and 1 upon failure
 */
static int ndpi_handle_ipv6_extension_headers(struct ndpi_detection_module_struct *ndpi_struct, const u_int8_t ** l4ptr, u_int16_t * l4len, u_int8_t * nxt_hdr)
{
    while ((*nxt_hdr == 0 || *nxt_hdr == 43 || *nxt_hdr == 44 || *nxt_hdr == 60 || *nxt_hdr == 135 || *nxt_hdr == 59)) {
        u_int16_t ehdr_len;

        // no next header
        if(*nxt_hdr == 59) {
            return 1;
        }
        // fragment extension header has fixed size of 8 bytes and the first byte is the next header type
        if(*nxt_hdr == 44) {
            if(*l4len < 8) {
                return 1;
            }
            *nxt_hdr = (*l4ptr)[0];
            *l4len -= 8;
            (*l4ptr) += 8;
            continue;
        }
        // the other extension headers have one byte for the next header type
        // and one byte for the extension header length in 8 byte steps minus the first 8 bytes
        ehdr_len = (*l4ptr)[1];
        ehdr_len *= 8;
        ehdr_len += 8;

        if(*l4len < ehdr_len) {
            return 1;
        }
        *nxt_hdr = (*l4ptr)[0];
        *l4len -= ehdr_len;
        (*l4ptr) += ehdr_len;
    }
    return 0;
}
#endif /* NDPI_DETECTION_SUPPORT_IPV6 */


static u_int8_t ndpi_iph_is_valid_and_not_fragmented(const struct ndpi_iphdr *iph, const u_int16_t ipsize)
{
    //#ifdef REQUIRE_FULL_PACKETS
    if(ipsize < iph->ihl * 4 ||
            ipsize < ntohs(iph->tot_len) || ntohs(iph->tot_len) < iph->ihl * 4 || (iph->frag_off & htons(0x1FFF)) != 0) {
        return 0;
    }
    //#endif

    return 1;
}

static u_int8_t ndpi_detection_get_l4_internal(struct ndpi_detection_module_struct *ndpi_struct,
        const u_int8_t * l3, u_int16_t l3_len,
        const u_int8_t ** l4_return, u_int16_t * l4_len_return,
        u_int8_t * l4_protocol_return, u_int32_t flags)
{
    const struct ndpi_iphdr *iph = NULL;
#ifdef NDPI_DETECTION_SUPPORT_IPV6
    const struct ndpi_ipv6hdr *iph_v6 = NULL;
#endif
    u_int16_t l4len = 0;
    const u_int8_t *l4ptr = NULL;
    u_int8_t l4protocol = 0;

    if(l3 == NULL || l3_len < sizeof(struct ndpi_iphdr))
        return 1;

    iph = (const struct ndpi_iphdr *) l3;

    if(iph->version == IPVERSION && iph->ihl >= 5) {
        NDPI_LOG_DBG2(ndpi_struct, "ipv4 header\n");
    }
#ifdef NDPI_DETECTION_SUPPORT_IPV6
    else if(iph->version == 6 && l3_len >= sizeof(struct ndpi_ipv6hdr)) {
        NDPI_LOG_DBG2(ndpi_struct, "ipv6 header\n");
        iph_v6 = (const struct ndpi_ipv6hdr *) iph;
        iph = NULL;
    }
#endif
    else {
        return 1;
    }

    if((flags & NDPI_DETECTION_ONLY_IPV6) && iph != NULL) {
        NDPI_LOG_DBG2(ndpi_struct, "ipv4 header found but excluded by flag\n");
        return 1;
    }
#ifdef NDPI_DETECTION_SUPPORT_IPV6
    else if((flags & NDPI_DETECTION_ONLY_IPV4) && iph_v6 != NULL) {
        NDPI_LOG_DBG2(ndpi_struct, "ipv6 header found but excluded by flag\n");
        return 1;
    }
#endif

    if(iph != NULL && ndpi_iph_is_valid_and_not_fragmented(iph, l3_len)) {
        u_int16_t len  = ntohs(iph->tot_len);
        u_int16_t hlen = (iph->ihl * 4);

        l4ptr = (((const u_int8_t *) iph) + iph->ihl * 4);

        if(len == 0) len = l3_len;

        l4len = (len > hlen) ? (len - hlen) : 0;
        l4protocol = iph->protocol;
    }
#ifdef NDPI_DETECTION_SUPPORT_IPV6
    else if(iph_v6 != NULL && (l3_len - sizeof(struct ndpi_ipv6hdr)) >= ntohs(iph_v6->ip6_hdr.ip6_un1_plen)) {
        l4ptr = (((const u_int8_t *) iph_v6) + sizeof(struct ndpi_ipv6hdr));
        l4len = ntohs(iph_v6->ip6_hdr.ip6_un1_plen);
        l4protocol = iph_v6->ip6_hdr.ip6_un1_nxt;

        // we need to handle IPv6 extension headers if present
        if(ndpi_handle_ipv6_extension_headers(ndpi_struct, &l4ptr, &l4len, &l4protocol) != 0) {
            return 1;
        }

    }
#endif
    else {
        return 1;
    }

    if(l4_return != NULL) {
        *l4_return = l4ptr;
    }

    if(l4_len_return != NULL) {
        *l4_len_return = l4len;
    }

    if(l4_protocol_return != NULL) {
        *l4_protocol_return = l4protocol;
    }

    return 0;
}

void ndpi_apply_flow_protocol_to_packet(struct ndpi_flow_struct *flow,
        struct ndpi_packet_struct *packet)
{
    packet->detected_protocol = flow->detected_protocol;
}

static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        unsigned short packetlen)
{
    const struct ndpi_iphdr *decaps_iph = NULL;
    u_int16_t l3len;
    u_int16_t l4len;
    const u_int8_t *l4ptr;
    u_int8_t l4protocol;
    u_int8_t l4_result;

    if (flow) {
        /* reset payload_packet_len, will be set if ipv4 tcp or udp */
        flow->packet.payload_packet_len = 0;
        flow->packet.l4_packet_len = 0;
        flow->packet.l3_packet_len = packetlen;

        flow->packet.tcp = NULL;
        flow->packet.udp = NULL;
        flow->packet.generic_l4_ptr = NULL;
#ifdef NDPI_DETECTION_SUPPORT_IPV6
        flow->packet.iphv6 = NULL;
#endif                          /* NDPI_DETECTION_SUPPORT_IPV6 */
    }

    if(flow)
        ndpi_apply_flow_protocol_to_packet(flow, &flow->packet);
    else
        ndpi_int_reset_packet_protocol(&flow->packet);

    l3len = flow->packet.l3_packet_len;

#ifdef NDPI_DETECTION_SUPPORT_IPV6
    if(flow->packet.iph != NULL) {
#endif                          /* NDPI_DETECTION_SUPPORT_IPV6 */

        decaps_iph = flow->packet.iph;

#ifdef NDPI_DETECTION_SUPPORT_IPV6
    }
#endif                          /* NDPI_DETECTION_SUPPORT_IPV6 */

    if(decaps_iph && decaps_iph->version == IPVERSION && decaps_iph->ihl >= 5) {
        NDPI_LOG_DBG2(ndpi_struct, "ipv4 header\n");
    }
#ifdef NDPI_DETECTION_SUPPORT_IPV6
    else if(decaps_iph && decaps_iph->version == 6 && l3len >= sizeof(struct ndpi_ipv6hdr) &&
            (ndpi_struct->ip_version_limit & NDPI_DETECTION_ONLY_IPV4) == 0) {
        NDPI_LOG_DBG2(ndpi_struct, "ipv6 header\n");
        flow->packet.iphv6 = (struct ndpi_ipv6hdr *)flow->packet.iph;
        flow->packet.iph = NULL;
    }
#endif
    else {
        flow->packet.iph = NULL;
        return 1;
    }


    /* needed:
     *  - unfragmented packets
     *  - ip header <= packet len
     *  - ip total length >= packet len
     */


    l4ptr = NULL;
    l4len = 0;
    l4protocol = 0;

    l4_result =
        ndpi_detection_get_l4_internal(ndpi_struct, (const u_int8_t *) decaps_iph, l3len, &l4ptr, &l4len, &l4protocol, 0);

    if(l4_result != 0) {
        return 1;
    }

    flow->packet.l4_protocol = l4protocol;
    flow->packet.l4_packet_len = l4len;

    /* tcp / udp detection */
    if(l4protocol == IPPROTO_TCP && flow->packet.l4_packet_len >= 20 /* min size of tcp */ ) {
        /* tcp */
        flow->packet.tcp = (struct ndpi_tcphdr *) l4ptr;

        if(flow->packet.l4_packet_len >=flow->packet.tcp->doff * 4) {
            flow->packet.payload_packet_len =
                flow->packet.l4_packet_len -flow->packet.tcp->doff * 4;
            flow->packet.actual_payload_len = flow->packet.payload_packet_len;
            flow->packet.payload = ((u_int8_t *)flow->packet.tcp) + (flow->packet.tcp->doff * 4);

            /* check for new tcp syn packets, here
             * idea: reset detection state if a connection is unknown
             */
            if(flow && flow->packet.tcp->syn != 0
                    && flow->packet.tcp->ack == 0
                    && flow->init_finished != 0
                    && flow->detected_protocol == NDPI_PROTOCOL_UNKNOWN) {

                u_int8_t backup;

                if(flow->http.url)
                    ndpi_free(flow->http.url);
                if(flow->http.content_type)
                    ndpi_free(flow->http.content_type);

                backup  = flow->num_processed_pkts;

                memset(flow, 0, sizeof(*(flow)));

                flow->num_processed_pkts = backup;

                NDPI_LOG_DBG(ndpi_struct,
                        "tcp syn packet for unknown protocol, reset detection state\n");
            }
        } else {
            /* tcp header not complete */
            flow->packet.tcp = NULL;
        }
    } else if(l4protocol == IPPROTO_UDP && flow->packet.l4_packet_len >= 8 /* size of udp */ ) {
        flow->packet.udp = (struct ndpi_udphdr *) l4ptr;
        flow->packet.payload_packet_len =flow->packet.l4_packet_len - 8;
        flow->packet.payload = ((u_int8_t *)flow->packet.udp) + 8;
    } else {
        flow->packet.generic_l4_ptr = l4ptr;
    }

    return 0;
}

void ndpi_connection_tracking(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow)
{
    /* const for gcc code optimization and cleaner code */
    struct ndpi_packet_struct *packet = &flow->packet;
    const struct ndpi_iphdr *iph = packet->iph;
#ifdef NDPI_DETECTION_SUPPORT_IPV6
    const struct ndpi_ipv6hdr *iphv6 = packet->iphv6;
#endif
    const struct ndpi_tcphdr *tcph = packet->tcp;
    const struct ndpi_udphdr *udph = flow->packet.udp;

    packet->tcp_retransmission = 0, packet->packet_direction = 0;

    if(ndpi_struct->direction_detect_disable) {
        packet->packet_direction = flow->packet_direction;
    } else {
        if(iph != NULL && le32toh(iph->saddr) < le32toh(iph->daddr))
            packet->packet_direction = 1;

#ifdef NDPI_DETECTION_SUPPORT_IPV6
        if(iphv6 != NULL && NDPI_COMPARE_IPV6_ADDRESS_STRUCTS(&iphv6->ip6_src,
                    &iphv6->ip6_dst) != 0)
            packet->packet_direction = 1;
#endif
    }

    packet->packet_lines_parsed_complete = 0;
    if(flow == NULL)
        return;

    if(flow->init_finished == 0) {
        flow->init_finished = 1;
        flow->setup_packet_direction = packet->packet_direction;
    }

    if(tcph != NULL) {
        /* reset retried bytes here before setting it */
        packet->num_retried_bytes = 0;

        if(!ndpi_struct->direction_detect_disable)
            packet->packet_direction = (le16toh(tcph->source) < le16toh(tcph->dest)) ? 1 : 0;

        if(tcph->syn != 0 && tcph->ack == 0 && flow->l4.tcp.seen_syn == 0 && flow->l4.tcp.seen_syn_ack == 0
                && flow->l4.tcp.seen_ack == 0) {
            flow->l4.tcp.seen_syn = 1;
        }
        if(tcph->syn != 0 && tcph->ack != 0 && flow->l4.tcp.seen_syn == 1 && flow->l4.tcp.seen_syn_ack == 0
                && flow->l4.tcp.seen_ack == 0) {
            flow->l4.tcp.seen_syn_ack = 1;
        }
        if(tcph->syn == 0 && tcph->ack == 1 && flow->l4.tcp.seen_syn == 1 && flow->l4.tcp.seen_syn_ack == 1
                && flow->l4.tcp.seen_ack == 0) {
            flow->l4.tcp.seen_ack = 1;
        }
        if((flow->next_tcp_seq_nr[0] == 0 && flow->next_tcp_seq_nr[1] == 0)
                || (flow->next_tcp_seq_nr[0] == 0 || flow->next_tcp_seq_nr[1] == 0)) {
            /* initialize tcp sequence counters */
            /* the ack flag needs to be set to get valid sequence numbers from the other
             * direction. Usually it will catch the second packet syn+ack but it works
             * also for asymmetric traffic where it will use the first data packet
             *
             * if the syn flag is set add one to the sequence number,
             * otherwise use the payload length.
             */
            if(tcph->ack != 0) {
                flow->next_tcp_seq_nr[flow->packet.packet_direction] =
                    ntohl(tcph->seq) + (tcph->syn ? 1 : packet->payload_packet_len);

                flow->next_tcp_seq_nr[1 -flow->packet.packet_direction] = ntohl(tcph->ack_seq);
            }
        } else if(packet->payload_packet_len > 0) {
            /* check tcp sequence counters */
            if(((u_int32_t)(ntohl(tcph->seq) - flow->next_tcp_seq_nr[packet->packet_direction])) >
                    ndpi_struct->tcp_max_retransmission_window_size) {

                packet->tcp_retransmission = 1;

                /* CHECK IF PARTIAL RETRY IS HAPPENING */
                if((flow->next_tcp_seq_nr[packet->packet_direction] - ntohl(tcph->seq) < packet->payload_packet_len)) {
                    /* num_retried_bytes actual_payload_len hold info about the partial retry
                       analyzer which require this info can make use of this info
                       Other analyzer can use packet->payload_packet_len */
                    packet->num_retried_bytes = (u_int16_t)(flow->next_tcp_seq_nr[packet->packet_direction] - ntohl(tcph->seq));
                    packet->actual_payload_len = packet->payload_packet_len - packet->num_retried_bytes;
                    flow->next_tcp_seq_nr[packet->packet_direction] = ntohl(tcph->seq) + packet->payload_packet_len;
                }
            }

            /* normal path
               actual_payload_len is initialized to payload_packet_len during tcp header parsing itself.
               It will be changed only in case of retransmission */
            else {
                packet->num_retried_bytes = 0;
                flow->next_tcp_seq_nr[packet->packet_direction] = ntohl(tcph->seq) + packet->payload_packet_len;
            }
        }

        if(tcph->rst) {
            flow->next_tcp_seq_nr[0] = 0;
            flow->next_tcp_seq_nr[1] = 0;
        }
    } else if(udph != NULL) {
        if(!ndpi_struct->direction_detect_disable)
            packet->packet_direction = (le16toh(udph->source) < le16toh(udph->dest)) ? 1 : 0;
    }

    if(flow->packet_counter < MAX_PACKET_COUNTER && packet->payload_packet_len) {
        flow->packet_counter++;
    }

    if(flow->packet_direction_counter[packet->packet_direction] < MAX_PACKET_COUNTER && packet->payload_packet_len) {
        flow->packet_direction_counter[packet->packet_direction]++;
    }

    if(flow->byte_counter[packet->packet_direction] + packet->payload_packet_len >
            flow->byte_counter[packet->packet_direction]) {
        flow->byte_counter[packet->packet_direction] += packet->payload_packet_len;
    }
}

void ndpi_check_other_flow_func(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) {

    void *func = NULL;
    u_int32_t a;
    u_int16_t proto_index = ndpi_struct->proto_defaults[flow->guessed_proto_id].proto_idx;
    int16_t proto_id = ndpi_struct->proto_defaults[flow->guessed_proto_id].proto_id;
    NDPI_PROTOCOL_BITMASK detection_bitmask;

    NDPI_SAVE_AS_BITMASK(detection_bitmask, flow->packet.detected_protocol);

    if((proto_id != NDPI_PROTOCOL_UNKNOWN)
            && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                ndpi_struct->callback_buffer[proto_index].excluded_protocol_bitmask) == 0
            && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer[proto_index].detection_bitmask,
                detection_bitmask) != 0
            && (ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask
                & *ndpi_selection_packet) == ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask) {
        if((flow->guessed_proto_id != NDPI_PROTOCOL_UNKNOWN)
                && (ndpi_struct->proto_defaults[flow->guessed_proto_id].func != NULL))
            ndpi_struct->proto_defaults[flow->guessed_proto_id].func(ndpi_struct, flow),
                func = ndpi_struct->proto_defaults[flow->guessed_proto_id].func;
    }

    for(a = 0; a < ndpi_struct->callback_buffer_size_non_tcp_udp; a++) {
        if((func != ndpi_struct->callback_buffer_non_tcp_udp[a].func)
                && (ndpi_struct->callback_buffer_non_tcp_udp[a].ndpi_selection_bitmask & *ndpi_selection_packet) ==
                ndpi_struct->callback_buffer_non_tcp_udp[a].ndpi_selection_bitmask
                && (flow == NULL
                    ||
                    NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                        ndpi_struct->callback_buffer_non_tcp_udp[a].excluded_protocol_bitmask) == 0)
                && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer_non_tcp_udp[a].detection_bitmask,
                    detection_bitmask) != 0) {

            if(ndpi_struct->callback_buffer_non_tcp_udp[a].func != NULL)
                ndpi_struct->callback_buffer_non_tcp_udp[a].func(ndpi_struct, flow);

            if(flow->detected_protocol != NDPI_PROTOCOL_UNKNOWN)
                break; /* Stop after detecting the first protocol */
        }
    }
}

void check_ndpi_udp_flow_func(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) {
    void *func = NULL;
    u_int32_t a;
    u_int16_t proto_index = ndpi_struct->proto_defaults[flow->guessed_proto_id].proto_idx;
    int16_t proto_id = ndpi_struct->proto_defaults[flow->guessed_proto_id].proto_id;
    NDPI_PROTOCOL_BITMASK detection_bitmask;

    NDPI_SAVE_AS_BITMASK(detection_bitmask, flow->packet.detected_protocol);

    if((proto_id != NDPI_PROTOCOL_UNKNOWN)
            && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                ndpi_struct->callback_buffer[proto_index].excluded_protocol_bitmask) == 0
            && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer[proto_index].detection_bitmask,
                detection_bitmask) != 0
            && (ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask
                & *ndpi_selection_packet) == ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask) {
        if((flow->guessed_proto_id != NDPI_PROTOCOL_UNKNOWN)
                && (ndpi_struct->proto_defaults[flow->guessed_proto_id].func != NULL))
            ndpi_struct->proto_defaults[flow->guessed_proto_id].func(ndpi_struct, flow),
                func = ndpi_struct->proto_defaults[flow->guessed_proto_id].func;
    }

    for(a = 0; a < ndpi_struct->callback_buffer_size_udp; a++) {
        if((func != ndpi_struct->callback_buffer_udp[a].func)
                && (ndpi_struct->callback_buffer_udp[a].ndpi_selection_bitmask & *ndpi_selection_packet) ==
                ndpi_struct->callback_buffer_udp[a].ndpi_selection_bitmask
                && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                    ndpi_struct->callback_buffer_udp[a].excluded_protocol_bitmask) == 0
                && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer_udp[a].detection_bitmask,
                    detection_bitmask) != 0) {
            ndpi_struct->callback_buffer_udp[a].func(ndpi_struct, flow);
            // NDPI_LOG_DBG(ndpi_struct, "[UDP,CALL] dissector of protocol as callback_buffer idx =  %d\n",a);
            if(flow->detected_protocol != NDPI_PROTOCOL_UNKNOWN)
                break; /* Stop after detecting the first protocol */
        } else
            if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_struct,
                    "[UDP,SKIP] dissector of protocol as callback_buffer idx =  %d\n",a);
    }
}


void check_ndpi_tcp_flow_func(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) {

    void *func = NULL;
    u_int32_t a;
    u_int16_t proto_index = ndpi_struct->proto_defaults[flow->guessed_proto_id].proto_idx;
    int16_t proto_id = ndpi_struct->proto_defaults[flow->guessed_proto_id].proto_id;
    NDPI_PROTOCOL_BITMASK detection_bitmask;

    NDPI_SAVE_AS_BITMASK(detection_bitmask, flow->packet.detected_protocol);

    if(flow->packet.payload_packet_len != 0) {
        if((proto_id != NDPI_PROTOCOL_UNKNOWN)
                && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                    ndpi_struct->callback_buffer[proto_index].excluded_protocol_bitmask) == 0
                && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer[proto_index].detection_bitmask, detection_bitmask) != 0
                && (ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask & *ndpi_selection_packet) == ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask) {
            if((flow->guessed_proto_id != NDPI_PROTOCOL_UNKNOWN)
                    && (ndpi_struct->proto_defaults[flow->guessed_proto_id].func != NULL))
                ndpi_struct->proto_defaults[flow->guessed_proto_id].func(ndpi_struct, flow),
                    func = ndpi_struct->proto_defaults[flow->guessed_proto_id].func;
        }

        if(flow->detected_protocol == NDPI_PROTOCOL_UNKNOWN) {
            for(a = 0; a < ndpi_struct->callback_buffer_size_tcp_payload; a++) {
                if((func != ndpi_struct->callback_buffer_tcp_payload[a].func)
                        && (ndpi_struct->callback_buffer_tcp_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) == ndpi_struct->callback_buffer_tcp_payload[a].ndpi_selection_bitmask
                        && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                            ndpi_struct->callback_buffer_tcp_payload[a].excluded_protocol_bitmask) == 0
                        && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer_tcp_payload[a].detection_bitmask,
                            detection_bitmask) != 0) {
                    ndpi_struct->callback_buffer_tcp_payload[a].func(ndpi_struct, flow);


                    if(flow->detected_protocol != NDPI_PROTOCOL_UNKNOWN)
                        break; /* Stop after detecting the first protocol */
                }
            }
        }
    } else {
        /* no payload */
        if((proto_id != NDPI_PROTOCOL_UNKNOWN)
                && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                    ndpi_struct->callback_buffer[proto_index].excluded_protocol_bitmask) == 0
                && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer[proto_index].detection_bitmask,
                    detection_bitmask) != 0
                && (ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask
                    & *ndpi_selection_packet) == ndpi_struct->callback_buffer[proto_index].ndpi_selection_bitmask) {
            if((flow->guessed_proto_id != NDPI_PROTOCOL_UNKNOWN)
                    && (ndpi_struct->proto_defaults[flow->guessed_proto_id].func != NULL)
                    && ((ndpi_struct->callback_buffer[flow->guessed_proto_id].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == 0))
                ndpi_struct->proto_defaults[flow->guessed_proto_id].func(ndpi_struct, flow),
                    func = ndpi_struct->proto_defaults[flow->guessed_proto_id].func;
        }

        for(a = 0; a < ndpi_struct->callback_buffer_size_tcp_no_payload; a++) {
            if((func != ndpi_struct->callback_buffer_tcp_payload[a].func)
                    && (ndpi_struct->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) ==
                    ndpi_struct->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask
                    && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask,
                        ndpi_struct->callback_buffer_tcp_no_payload[a].excluded_protocol_bitmask) == 0
                    && NDPI_BITMASK_COMPARE(ndpi_struct->callback_buffer_tcp_no_payload[a].detection_bitmask,
                        detection_bitmask) != 0) {
                ndpi_struct->callback_buffer_tcp_no_payload[a].func(ndpi_struct, flow);

                if(flow->detected_protocol != NDPI_PROTOCOL_UNKNOWN)
                    break; /* Stop after detecting the first protocol */
            }
        }
    }
}

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

void ndpi_check_flow_func(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) {
    if(flow->packet.tcp != NULL)
        check_ndpi_tcp_flow_func(ndpi_struct, flow, ndpi_selection_packet);
    else if(flow->packet.udp != NULL)
        check_ndpi_udp_flow_func(ndpi_struct, flow, ndpi_selection_packet);
    else
        ndpi_check_other_flow_func(ndpi_struct, flow, ndpi_selection_packet);
}

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

static u_int16_t ndpi_match_host_proto_id(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow) {
    u_int16_t ret = NDPI_PROTOCOL_UNKNOWN;

    if(flow->packet.iph) {
        /* guess host protocol */
        ret = ndpi_network_ptree_match(ndpi_struct, (struct in_addr *)&flow->packet.iph->saddr);

        if(ret == NDPI_PROTOCOL_UNKNOWN)
            ret = ndpi_network_ptree_match(ndpi_struct, (struct in_addr *)&flow->packet.iph->daddr);
    }

    return(ret);
}

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

ndpi_protocol ndpi_detection_giveup(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow, u_int8_t enable_guess) {
    ndpi_protocol ret = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN };

    if(flow == NULL) return(ret);

    /* TODO: add the remaining stage_XXXX protocols */
    if(flow->detected_protocol == NDPI_PROTOCOL_UNKNOWN) {
        u_int16_t guessed_proto_id, guessed_host_proto_id;

        if(flow->guessed_proto_id == NDPI_PROTOCOL_STUN)
            goto check_stun_export;
        else if((flow->l4.tcp.ssl_seen_client_cert == 1)
                && (flow->protos.stun_ssl.ssl.client_certificate[0] != '\0')) {
            ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SSL);
        } else {
            if(!enable_guess)
                return(ret);

            if((flow->guessed_proto_id == NDPI_PROTOCOL_UNKNOWN)
                    && (flow->packet.l4_protocol == IPPROTO_TCP)
                    && (flow->l4.tcp.ssl_stage > 1))
                flow->guessed_proto_id = NDPI_PROTOCOL_SSL_NO_CERT;

            guessed_proto_id = flow->guessed_proto_id;
            guessed_host_proto_id = flow->guessed_host_proto_id;
#if 0
            if((guessed_host_proto_id != NDPI_PROTOCOL_UNKNOWN)
                    && ((flow->packet.l4_protocol == IPPROTO_UDP)
                        && NDPI_ISSET(&flow->excluded_protocol_bitmask, guessed_host_proto_id)
                        && is_udp_guessable_proto(guessed_host_proto_id)
                       ))
                flow->guessed_host_proto_id = guessed_host_proto_id = NDPI_PROTOCOL_UNKNOWN;

            /* Ignore guessed protocol if they have been discarded */
            if((guessed_proto_id != NDPI_PROTOCOL_UNKNOWN)
                    // && (guessed_host_proto_id == NDPI_PROTOCOL_UNKNOWN)
                    && (flow->packet.l4_protocol == IPPROTO_UDP)
                    && NDPI_ISSET(&flow->excluded_protocol_bitmask, guessed_proto_id)
                    && is_udp_guessable_proto(guessed_proto_id))
                flow->guessed_proto_id = guessed_proto_id = NDPI_PROTOCOL_UNKNOWN;

            if((guessed_proto_id != NDPI_PROTOCOL_UNKNOWN)
                    || (guessed_host_proto_id != NDPI_PROTOCOL_UNKNOWN)) {
                if((guessed_protocol_id == 0)
                        && (flow->protos.stun_ssl.stun.num_binding_requests > 0)
                        && (flow->protos.stun_ssl.stun.num_processed_pkts > 0))
                    guessed_protocol_id = NDPI_PROTOCOL_STUN;

                if(flow->host_server_name[0] != '\0') {
                    ndpi_protocol_match_result ret_match;

                    ndpi_match_host_subprotocol(ndpi_struct, flow,
                            (char *)flow->host_server_name,
                            strlen((const char*)flow->host_server_name),
                            &ret_match,
                            NDPI_PROTOCOL_DNS);

                    if(ret_match.protocol_id != NDPI_PROTOCOL_UNKNOWN)
                        guessed_host_protocol_id = ret_match.protocol_id;
                }

                ndpi_int_change_protocol(ndpi_struct, flow,
                        guessed_host_proto_id,
                        guessed_proto_id);
            }
#endif
        }
    } else {
#if 0
        flow->detected_protocol_stack[1] = flow->guessed_proto_id,
            flow->detected_protocol_stack[0] = flow->guessed_host_proto_id;

        if(flow->detected_protocol_stack[1] == flow->detected_protocol_stack[0])
            flow->detected_protocol_stack[1] = flow->guessed_host_proto_id;
#endif
    }

    if((flow->detected_protocol == NDPI_PROTOCOL_UNKNOWN)
            && (flow->guessed_proto_id == NDPI_PROTOCOL_STUN)) {
check_stun_export:
        if(flow->protos.stun_ssl.stun.num_processed_pkts > 0) {
            if(/* (flow->protos.stun_ssl.stun.num_processed_pkts >= NDPI_MIN_NUM_STUN_DETECTION) */
                    flow->protos.stun_ssl.stun.is_skype) {
                ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SKYPE);
            } else
                ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_STUN);
        }
    }

    ret.master_protocol = flow->detected_protocol;

    return(ret);
}

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

void ndpi_process_extra_packet(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        const unsigned char *packet,
        const unsigned short packetlen,
        const u_int64_t current_tick_l,
        struct ndpi_id_struct *src,
        struct ndpi_id_struct *dst) {
    if(flow == NULL)
        return;

    if(flow->server_id == NULL) flow->server_id = dst; /* Default */

    /* need at least 20 bytes for ip header */
    if(packetlen < 20) {
        return;
    }

    flow->packet.tick_timestamp_l = current_tick_l;
    flow->packet.tick_timestamp = (u_int32_t)(current_tick_l/ndpi_struct->ticks_per_second);

    /* parse packet */
    flow->packet.iph = (struct ndpi_iphdr *)packet;
    /* we are interested in ipv4 packet */

    /* set up the packet headers for the extra packet function to use if it wants */
    if(ndpi_init_packet_header(ndpi_struct, flow, packetlen) != 0)
        return;

    /* detect traffic for tcp or udp only */
    flow->src = src, flow->dst = dst;
    ndpi_connection_tracking(ndpi_struct, flow);

    /* call the extra packet function (which may add more data/info to flow) */
    if (flow->extra_packets_func) {
        if ((flow->extra_packets_func(ndpi_struct, flow)) == 0)
            flow->check_extra_packets = 0;
    }

    flow->num_extra_packets_checked++;
}

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

ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        const unsigned char *packet,
        const unsigned short packetlen,
        const u_int64_t current_tick_l,
        struct ndpi_id_struct *src,
        struct ndpi_id_struct *dst) {

    u_int32_t id;
    NDPI_SELECTION_BITMASK_PROTOCOL_SIZE ndpi_selection_packet;
    ndpi_protocol ret = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN };

    if(ndpi_struct->ndpi_log_level >= NDPI_LOG_TRACE)
        NDPI_LOG(flow ? flow->detected_protocol : NDPI_PROTOCOL_UNKNOWN,
                ndpi_struct, NDPI_LOG_TRACE, "START packet processing\n");

    if(flow == NULL) return(ret);

    if(flow->server_id == NULL) flow->server_id = dst; /* Default */

    if(flow->detected_protocol == NDPI_PROTOCOL_UNKNOWN) {

        /* need at least 20 bytes for ip header */
        if(packetlen < 20) {
            /* reset protocol which is normally done in init_packet_header */
            ndpi_int_reset_packet_protocol(&flow->packet);
            flow->packet.iph = NULL, flow->packet.tcp = NULL, flow->packet.udp = NULL;
            return(ret);
        }

        flow->packet.tick_timestamp_l = current_tick_l;
        flow->packet.tick_timestamp = (u_int32_t)(current_tick_l / ndpi_struct->ticks_per_second);

        /* parse packet */
        flow->packet.iph = (struct ndpi_iphdr *)packet;

        /* we are interested in ipv4 packet */
        if(ndpi_init_packet_header(ndpi_struct, flow, packetlen) != 0) {
            flow->packet.iph = NULL, flow->packet.tcp = NULL, flow->packet.udp = NULL;
            return(ret);
        }

        /* detect traffic for tcp or udp only */
        flow->src = src, flow->dst = dst;
        ndpi_connection_tracking(ndpi_struct, flow);

        /* build ndpi_selection packet bitmask */
        ndpi_selection_packet = NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC;
        if(flow->packet.iph != NULL)
            ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_IP | NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6;

        if(flow->packet.tcp != NULL)
            ndpi_selection_packet |=
                (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP);

        if(flow->packet.udp != NULL)
            ndpi_selection_packet |=
                (NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP);

        if(flow->packet.payload_packet_len != 0)
            ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD;

        if(flow->packet.tcp_retransmission == 0)
            ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_NO_TCP_RETRANSMISSION;

#ifdef NDPI_DETECTION_SUPPORT_IPV6
        if(flow->packet.iphv6 != NULL)
            ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6;
#endif

        ndpi_check_flow_func(ndpi_struct, flow, &ndpi_selection_packet);

        id = flow->packet.detected_protocol;
        if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(ndpi_struct->detection_bitmask, id) == 0)
            id = NDPI_PROTOCOL_UNKNOWN;
    }
    else {
        if(flow->check_extra_packets) {
            ndpi_process_extra_packet(ndpi_struct, flow,
                    packet, packetlen, current_tick_l, src, dst);
        }
        id = flow->packet.detected_protocol;
    }

    if(id != NDPI_PROTOCOL_UNKNOWN) {
        int i;
        ndpi_protocol_match_result ret_match;

        for(i = 0; i < sizeof(flow->host_server_name)
                && flow->host_server_name[i] != '\0'; i++) {
            if (flow->host_server_name[i] != '\0')
                flow->host_server_name[i] = tolower(flow->host_server_name[i]);
            else {
                flow->host_server_name[i] = '\0';
                break;
            }
        }

        switch (id) {
            case NDPI_PROTOCOL_HTTPS:
            case NDPI_PROTOCOL_SSL:
            case NDPI_PROTOCOL_MAIL_IMAPS:
            case NDPI_PROTOCOL_MAIL_SMTPS:
            case NDPI_PROTOCOL_MAIL_POPS:
            case NDPI_PROTOCOL_SSL_NO_CERT:
            case NDPI_PROTOCOL_OSCAR:
                if (flow->protos.stun_ssl.ssl.server_certificate[0] != '\0') {
                    ret.app_protocol = (uint16_t)ndpi_match_host_app_proto(
                            ndpi_struct, flow,
                            (char *)flow->protos.stun_ssl.ssl.server_certificate,
                            strlen((const char*)flow->protos.stun_ssl.ssl.server_certificate),
                            &ret_match
                            );
                }
                if (ret.app_protocol == NDPI_PROTOCOL_UNKNOWN &&
                        flow->protos.stun_ssl.ssl.client_certificate[0] != '\0') {
                    ret.app_protocol = (uint16_t)ndpi_match_host_app_proto(
                            ndpi_struct, flow,
                            (char *)flow->protos.stun_ssl.ssl.client_certificate,
                            strlen((const char*)flow->protos.stun_ssl.ssl.client_certificate),
                            &ret_match
                            );
                }
                break;

            case NDPI_PROTOCOL_SPOTIFY:
                ret.app_protocol = ndpi_get_protocol_id(ndpi_struct, "netify.spotify");
                break;

            case NDPI_PROTOCOL_MDNS:
                if (ret.app_protocol == NDPI_PROTOCOL_UNKNOWN &&
                        flow->protos.mdns.answer[0] != '\0') {
                    ret.app_protocol = (uint16_t)ndpi_match_host_app_proto(
                            ndpi_struct, flow,
                            (char *)flow->protos.mdns.answer,
                            strlen((const char*)flow->protos.mdns.answer),
                            &ret_match
                            );
                }
                break;
        }

        if (ret.app_protocol == NDPI_PROTOCOL_UNKNOWN &&
                flow->host_server_name[0] != '\0') {
            ret.app_protocol = (uint16_t)ndpi_match_host_app_proto(
                    ndpi_struct, flow,
                    (char *)flow->host_server_name,
                    strlen((const char*)flow->host_server_name),
                    &ret_match
                    );
        }

        if (ret.app_protocol == NDPI_PROTOCOL_UNKNOWN)
            ret.app_protocol = ndpi_match_host_proto_id(ndpi_struct, flow);

        if(id == NDPI_PROTOCOL_STUN) {
            if(ret.app_protocol == NDPI_PROTOCOL_FACEBOOK)
                ret.app_protocol = NDPI_PROTOCOL_MESSENGER;
            else if(ret.app_protocol == NDPI_PROTOCOL_GOOGLE)
                ret.app_protocol = NDPI_PROTOCOL_HANGOUT;
        }

        ret.master_protocol = (uint16_t)id;
    }
    else if (flow->num_processed_pkts == 1
            && ret.master_protocol == NDPI_PROTOCOL_UNKNOWN
            && ret.app_protocol == NDPI_PROTOCOL_UNKNOWN
            && flow->packet.tcp
            && flow->packet.tcp->syn == 0
            ) {
        /*
           This is a TCP flow
           - whose first packet is NOT a SYN
           - no protocol has been detected

           We don't see how future packets can match anything
           hence we giveup here
           */
        ret = ndpi_detection_giveup(ndpi_struct, flow, 1);
    }

    flow->packet.iph = NULL, flow->packet.tcp = NULL, flow->packet.udp = NULL;

    return(ret);
}

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

u_int32_t ndpi_bytestream_to_number(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read)
{
    u_int32_t val;
    val = 0;
    // cancel if eof, ' ' or line end chars are reached
    while (*str >= '0' && *str <= '9' && max_chars_to_read > 0) {
        val *= 10;
        val += *str - '0';
        str++;
        max_chars_to_read = max_chars_to_read - 1;
        *bytes_read = *bytes_read + 1;
    }
    return (val);
}

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

u_int32_t ndpi_bytestream_dec_or_hex_to_number(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read)
{
    u_int32_t val;
    val = 0;
    if(max_chars_to_read <= 2 || str[0] != '0' || str[1] != 'x') {
        return ndpi_bytestream_to_number(str, max_chars_to_read, bytes_read);
    } else {
        /*use base 16 system */
        str += 2;
        max_chars_to_read -= 2;
        *bytes_read = *bytes_read + 2;
        while (max_chars_to_read > 0) {

            if(*str >= '0' && *str <= '9') {
                val *= 16;
                val += *str - '0';
            } else if(*str >= 'a' && *str <= 'f') {
                val *= 16;
                val += *str + 10 - 'a';
            } else if(*str >= 'A' && *str <= 'F') {
                val *= 16;
                val += *str + 10 - 'A';
            } else {
                break;
            }
            str++;
            max_chars_to_read = max_chars_to_read - 1;
            *bytes_read = *bytes_read + 1;
        }
    }
    return (val);
}

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

u_int64_t ndpi_bytestream_to_number64(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read)
{
    u_int64_t val;
    val = 0;
    // cancel if eof, ' ' or line end chars are reached
    while (max_chars_to_read > 0 && *str >= '0' && *str <= '9') {
        val *= 10;
        val += *str - '0';
        str++;
        max_chars_to_read = max_chars_to_read - 1;
        *bytes_read = *bytes_read + 1;
    }
    return (val);
}

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

u_int64_t ndpi_bytestream_dec_or_hex_to_number64(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read)
{
    u_int64_t val;
    val = 0;
    if(max_chars_to_read <= 2 || str[0] != '0' || str[1] != 'x') {
        return ndpi_bytestream_to_number64(str, max_chars_to_read, bytes_read);
    } else {
        /*use base 16 system */
        str += 2;
        max_chars_to_read -= 2;
        *bytes_read = *bytes_read + 2;
        while (max_chars_to_read > 0) {

            if(*str >= '0' && *str <= '9') {
                val *= 16;
                val += *str - '0';
            } else if(*str >= 'a' && *str <= 'f') {
                val *= 16;
                val += *str + 10 - 'a';
            } else if(*str >= 'A' && *str <= 'F') {
                val *= 16;
                val += *str + 10 - 'A';
            } else {
                break;
            }
            str++;
            max_chars_to_read = max_chars_to_read - 1;
            *bytes_read = *bytes_read + 1;
        }
    }
    return (val);
}

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

u_int32_t ndpi_bytestream_to_ipv4(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read)
{
    u_int32_t val;
    u_int16_t read = 0;
    u_int16_t oldread;
    u_int32_t c;
    /* ip address must be X.X.X.X with each X between 0 and 255 */
    oldread = read;
    c = ndpi_bytestream_to_number(str, max_chars_to_read, &read);
    if(c > 255 || oldread == read || max_chars_to_read == read || str[read] != '.')
        return 0;
    read++;
    val = c << 24;
    oldread = read;
    c = ndpi_bytestream_to_number(&str[read], max_chars_to_read - read, &read);
    if(c > 255 || oldread == read || max_chars_to_read == read || str[read] != '.')
        return 0;
    read++;
    val = val + (c << 16);
    oldread = read;
    c = ndpi_bytestream_to_number(&str[read], max_chars_to_read - read, &read);
    if(c > 255 || oldread == read || max_chars_to_read == read || str[read] != '.')
        return 0;
    read++;
    val = val + (c << 8);
    oldread = read;
    c = ndpi_bytestream_to_number(&str[read], max_chars_to_read - read, &read);
    if(c > 255 || oldread == read || max_chars_to_read == read)
        return 0;
    val = val + c;

    *bytes_read = *bytes_read + read;

    return htonl(val);
}

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

/* internal function for every detection to parse one packet and to increase the info buffer */
void ndpi_parse_packet_line_info(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow)
{
    u_int32_t a;
    struct ndpi_packet_struct *packet = &flow->packet;

    if(packet->packet_lines_parsed_complete != 0)
        return;

    packet->packet_lines_parsed_complete = 1;
    packet->parsed_lines = 0;
    packet->empty_line_position_set = 0;
    packet->host_line.ptr = NULL;
    packet->host_line.len = 0;
    packet->referer_line.ptr = NULL;
    packet->referer_line.len = 0;
    packet->content_line.ptr = NULL;
    packet->content_line.len = 0;
    packet->accept_line.ptr = NULL;
    packet->accept_line.len = 0;
    packet->user_agent_line.ptr = NULL;
    packet->user_agent_line.len = 0;
    packet->http_url_name.ptr = NULL;
    packet->http_url_name.len = 0;
    packet->http_encoding.ptr = NULL;
    packet->http_encoding.len = 0;
    packet->http_transfer_encoding.ptr = NULL;
    packet->http_transfer_encoding.len = 0;
    packet->http_contentlen.ptr = NULL;
    packet->http_contentlen.len = 0;
    packet->http_cookie.ptr = NULL;
    packet->http_cookie.len = 0;
    packet->http_origin.len = 0;
    packet->http_origin.ptr = NULL;
    packet->http_x_session_type.ptr = NULL;
    packet->http_x_session_type.len = 0;
    packet->server_line.ptr = NULL;
    packet->server_line.len = 0;
    packet->http_method.ptr = NULL;
    packet->http_method.len = 0;
    packet->http_response.ptr = NULL;
    packet->http_response.len = 0;
    packet->http_num_headers=0;

    if((packet->payload_packet_len < 3)
            || (packet->payload == NULL))
        return;

    packet->line[packet->parsed_lines].ptr = packet->payload;
    packet->line[packet->parsed_lines].len = 0;

    for(a = 0; (a < packet->payload_packet_len)
            && (packet->parsed_lines < NDPI_MAX_PARSE_LINES_PER_PACKET); a++) {
        if((a + 1) == packet->payload_packet_len)
            return;

        if(get_u_int16_t(packet->payload, a) == ntohs(0x0d0a)) { /* If end of line char sequence CR+NL "\r\n", process line */
            packet->line[packet->parsed_lines].len = (u_int16_t)(((unsigned long) &packet->payload[a]) - ((unsigned long) packet->line[packet->parsed_lines].ptr));

            /* First line of a HTTP response parsing. Expected a "HTTP/1.? ???" */
            if(packet->parsed_lines == 0 && packet->line[0].len >= NDPI_STATICSTRING_LEN("HTTP/1.X 200 ") &&
                    strncasecmp((const char *)packet->line[0].ptr, "HTTP/1.", NDPI_STATICSTRING_LEN("HTTP/1.")) == 0 &&
                    packet->line[0].ptr[NDPI_STATICSTRING_LEN("HTTP/1.X ")] > '0' && /* response code between 000 and 699 */
                    packet->line[0].ptr[NDPI_STATICSTRING_LEN("HTTP/1.X ")] < '6') {
                packet->http_response.ptr = &packet->line[0].ptr[NDPI_STATICSTRING_LEN("HTTP/1.1 ")];
                packet->http_response.len = packet->line[0].len - NDPI_STATICSTRING_LEN("HTTP/1.1 ");
                packet->http_num_headers++;

                /* Set server HTTP response code */
                if(packet->payload_packet_len >= 12) {
                    char buf[4];

                    /* Set server HTTP response code */
                    strncpy(buf, (char*)&packet->payload[9], 3);
                    buf[3] = '\0';

                    flow->http.response_status_code = atoi(buf);
                    /* https://en.wikipedia.org/wiki/List_of_HTTP_status_codes */
                    if((flow->http.response_status_code < 100) || (flow->http.response_status_code > 509))
                        flow->http.response_status_code = 0; /* Out of range */
                }
            }

            /* "Server:" header line in HTTP response */
            if(packet->line[packet->parsed_lines].len > NDPI_STATICSTRING_LEN("Server:") + 1
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Server:", NDPI_STATICSTRING_LEN("Server:")) == 0) {
                // some stupid clients omit a space and place the servername directly after the colon
                if(packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:")] == ' ') {
                    packet->server_line.ptr =
                        &packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:") + 1];
                    packet->server_line.len =
                        packet->line[packet->parsed_lines].len - (NDPI_STATICSTRING_LEN("Server:") + 1);
                } else {
                    packet->server_line.ptr = &packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:")];
                    packet->server_line.len = packet->line[packet->parsed_lines].len - NDPI_STATICSTRING_LEN("Server:");
                }
                packet->http_num_headers++;
            }
            /* "Host:" header line in HTTP request */
            if(packet->line[packet->parsed_lines].len > 6
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr,
                        "Host:", 5) == 0) {
                // some stupid clients omit a space and place the hostname directly after the colon
                if(packet->line[packet->parsed_lines].ptr[5] == ' ') {
                    packet->host_line.ptr = &packet->line[packet->parsed_lines].ptr[6];
                    packet->host_line.len = packet->line[packet->parsed_lines].len - 6;
                } else {
                    packet->host_line.ptr = &packet->line[packet->parsed_lines].ptr[5];
                    packet->host_line.len = packet->line[packet->parsed_lines].len - 5;
                }
                packet->http_num_headers++;
            }
            /* "X-Forwarded-For:" header line in HTTP request. Commonly used for HTTP proxies. */
            if(packet->line[packet->parsed_lines].len > 17
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "X-Forwarded-For:", 16) == 0) {
                // some stupid clients omit a space and place the hostname directly after the colon
                if(packet->line[packet->parsed_lines].ptr[16] == ' ') {
                    packet->forwarded_line.ptr = &packet->line[packet->parsed_lines].ptr[17];
                    packet->forwarded_line.len = packet->line[packet->parsed_lines].len - 17;
                } else {
                    packet->forwarded_line.ptr = &packet->line[packet->parsed_lines].ptr[16];
                    packet->forwarded_line.len = packet->line[packet->parsed_lines].len - 16;
                }
                packet->http_num_headers++;
            }
            /* "Content-Type:" header line in HTTP. */
            if(packet->line[packet->parsed_lines].len > 14
                    && (strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-Type: ", 14) == 0
                        || strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-type: ", 14) == 0)) {
                packet->content_line.ptr = &packet->line[packet->parsed_lines].ptr[14];
                packet->content_line.len = packet->line[packet->parsed_lines].len - 14;
                packet->http_num_headers++;
            }
            /* "Content-Type:" header line in HTTP AGAIN. Probably a bogus response without space after ":" */
            if(packet->line[packet->parsed_lines].len > 13
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-type:", 13) == 0) {
                packet->content_line.ptr = &packet->line[packet->parsed_lines].ptr[13];
                packet->content_line.len = packet->line[packet->parsed_lines].len - 13;
                packet->http_num_headers++;
            }
            /* "Accept:" header line in HTTP request. */
            if(packet->line[packet->parsed_lines].len > 8
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept: ", 8) == 0) {
                packet->accept_line.ptr = &packet->line[packet->parsed_lines].ptr[8];
                packet->accept_line.len = packet->line[packet->parsed_lines].len - 8;
                packet->http_num_headers++;
            }
            /* "Referer:" header line in HTTP request. */
            if(packet->line[packet->parsed_lines].len > 9
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Referer: ", 9) == 0) {
                packet->referer_line.ptr = &packet->line[packet->parsed_lines].ptr[9];
                packet->referer_line.len = packet->line[packet->parsed_lines].len - 9;
                packet->http_num_headers++;
            }
            /* "User-Agent:" header line in HTTP request. */
            if(packet->line[packet->parsed_lines].len > 12
                    && (strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "User-Agent: ", 12) == 0
                        || strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "User-agent: ", 12) == 0)) {
                packet->user_agent_line.ptr = &packet->line[packet->parsed_lines].ptr[12];
                packet->user_agent_line.len = packet->line[packet->parsed_lines].len - 12;
                packet->http_num_headers++;
            }
            /* "Content-Encoding:" header line in HTTP response (and request?). */
            if(packet->line[packet->parsed_lines].len > 18
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-Encoding: ", 18) == 0) {
                packet->http_encoding.ptr = &packet->line[packet->parsed_lines].ptr[18];
                packet->http_encoding.len = packet->line[packet->parsed_lines].len - 18;
                packet->http_num_headers++;
            }
            /* "Transfer-Encoding:" header line in HTTP. */
            if(packet->line[packet->parsed_lines].len > 19
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Transfer-Encoding: ", 19) == 0) {
                packet->http_transfer_encoding.ptr = &packet->line[packet->parsed_lines].ptr[19];
                packet->http_transfer_encoding.len = packet->line[packet->parsed_lines].len - 19;
                packet->http_num_headers++;
            }
            /* "Content-Length:" header line in HTTP. */
            if(packet->line[packet->parsed_lines].len > 16
                    && ((strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-Length: ", 16) == 0)
                        || (strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "content-length: ", 16) == 0))) {
                packet->http_contentlen.ptr = &packet->line[packet->parsed_lines].ptr[16];
                packet->http_contentlen.len = packet->line[packet->parsed_lines].len - 16;
                packet->http_num_headers++;
            }
            /* "Cookie:" header line in HTTP. */
            if(packet->line[packet->parsed_lines].len > 8
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Cookie: ", 8) == 0) {
                packet->http_cookie.ptr = &packet->line[packet->parsed_lines].ptr[8];
                packet->http_cookie.len = packet->line[packet->parsed_lines].len - 8;
                packet->http_num_headers++;
            }
            /* "Origin:" header line in HTTP. */
            if(packet->line[packet->parsed_lines].len > 8
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Origin: ", 8) == 0) {
                packet->http_origin.ptr = &packet->line[packet->parsed_lines].ptr[8];
                packet->http_origin.len = packet->line[packet->parsed_lines].len - 8;
                packet->http_num_headers++;
            }
            /* "X-Session-Type:" header line in HTTP. */
            if(packet->line[packet->parsed_lines].len > 16
                    && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "X-Session-Type: ", 16) == 0) {
                packet->http_x_session_type.ptr = &packet->line[packet->parsed_lines].ptr[16];
                packet->http_x_session_type.len = packet->line[packet->parsed_lines].len - 16;
                packet->http_num_headers++;
            }
            /* Identification and counting of other HTTP headers.
             * We consider the most common headers, but there are many others,
             * which can be seen at references below:
             * - https://tools.ietf.org/html/rfc7230
             * - https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
             */
            if((packet->line[packet->parsed_lines].len >  6 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Date: ", 6) == 0 ||
                            strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Vary: ", 6) == 0 ||
                            strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "ETag: ", 6) == 0 )) ||
                    (packet->line[packet->parsed_lines].len >  8 && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Pragma: ", 8) == 0) ||
                    (packet->line[packet->parsed_lines].len >  9 && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Expires: ", 9) == 0) ||
                    (packet->line[packet->parsed_lines].len > 12 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Set-Cookie: ", 12) == 0 ||
                                                                      strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Keep-Alive: ", 12) == 0 ||
                                                                      strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Connection: ", 12) == 0)) ||
                    (packet->line[packet->parsed_lines].len > 15 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Last-Modified: ", 15) == 0 ||
                                                                      strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept-Ranges: ", 15) == 0)) ||
                    (packet->line[packet->parsed_lines].len > 17 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept-Language: ", 17) == 0 ||
                                                                      strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept-Encoding: ", 17) == 0)) ||
                    (packet->line[packet->parsed_lines].len > 27 && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Upgrade-Insecure-Requests: ", 27) == 0)) {
                /* Just count. In the future, if needed, this if can be splited to parse these headers */
                packet->http_num_headers++;
            }


            if(packet->line[packet->parsed_lines].len == 0) {
                packet->empty_line_position = a;
                packet->empty_line_position_set = 1;
            }

            if(packet->parsed_lines >= (NDPI_MAX_PARSE_LINES_PER_PACKET - 1))
                return;

            packet->parsed_lines++;
            packet->line[packet->parsed_lines].ptr = &packet->payload[a + 2];
            packet->line[packet->parsed_lines].len = 0;

            a++; /* next char in the payload */
        }
    }

    if(packet->parsed_lines >= 1) {
        packet->line[packet->parsed_lines].len
            = (u_int16_t)(((unsigned long) &packet->payload[packet->payload_packet_len]) -
                    ((unsigned long) packet->line[packet->parsed_lines].ptr));
        packet->parsed_lines++;
    }
}

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

void ndpi_parse_packet_line_info_any(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow)
{
    struct ndpi_packet_struct *packet = &flow->packet;
    u_int32_t a;
    u_int16_t end = packet->payload_packet_len;

    if(packet->packet_lines_parsed_complete != 0)
        return;

    packet->packet_lines_parsed_complete = 1;
    packet->parsed_lines = 0;

    if(packet->payload_packet_len == 0)
        return;

    packet->line[packet->parsed_lines].ptr = packet->payload;
    packet->line[packet->parsed_lines].len = 0;

    for(a = 0; a < end; a++) {
        if(packet->payload[a] == 0x0a) {
            packet->line[packet->parsed_lines].len = (u_int16_t)(
                    ((unsigned long) &packet->payload[a]) -
                    ((unsigned long) packet->line[packet->parsed_lines].ptr));

            if(a > 0 && packet->payload[a-1] == 0x0d)
                packet->line[packet->parsed_lines].len--;

            if(packet->parsed_lines >= (NDPI_MAX_PARSE_LINES_PER_PACKET - 1))
                break;

            packet->parsed_lines++;
            packet->line[packet->parsed_lines].ptr = &packet->payload[a + 1];
            packet->line[packet->parsed_lines].len = 0;

            if((a + 1) >= packet->payload_packet_len)
                break;

            //a++;
        }
    }
}

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

u_int16_t ndpi_check_for_email_address(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow, u_int16_t counter)
{

    struct ndpi_packet_struct *packet = &flow->packet;

    NDPI_LOG_DBG2(ndpi_struct, "called ndpi_check_for_email_address\n");

    if(packet->payload_packet_len > counter && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z')
                || (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z')
                || (packet->payload[counter] >= '0' && packet->payload[counter] <= '9')
                || packet->payload[counter] == '-' || packet->payload[counter] == '_')) {
        NDPI_LOG_DBG2(ndpi_struct, "first letter\n");
        counter++;
        while (packet->payload_packet_len > counter
                && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z')
                    || (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z')
                    || (packet->payload[counter] >= '0' && packet->payload[counter] <= '9')
                    || packet->payload[counter] == '-' || packet->payload[counter] == '_'
                    || packet->payload[counter] == '.')) {
            NDPI_LOG_DBG2(ndpi_struct, "further letter\n");
            counter++;
            if(packet->payload_packet_len > counter && packet->payload[counter] == '@') {
                NDPI_LOG_DBG2(ndpi_struct, "@\n");
                counter++;
                while (packet->payload_packet_len > counter
                        && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z')
                            || (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z')
                            || (packet->payload[counter] >= '0' && packet->payload[counter] <= '9')
                            || packet->payload[counter] == '-' || packet->payload[counter] == '_')) {
                    NDPI_LOG_DBG2(ndpi_struct, "letter\n");
                    counter++;
                    if(packet->payload_packet_len > counter && packet->payload[counter] == '.') {
                        NDPI_LOG_DBG2(ndpi_struct, ".\n");
                        counter++;
                        if(packet->payload_packet_len > counter + 1
                                && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z')
                                    && (packet->payload[counter + 1] >= 'a' && packet->payload[counter + 1] <= 'z'))) {
                            NDPI_LOG_DBG2(ndpi_struct, "two letters\n");
                            counter += 2;
                            if(packet->payload_packet_len > counter
                                    && (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) {
                                NDPI_LOG_DBG2(ndpi_struct, "whitespace1\n");
                                return counter;
                            } else if(packet->payload_packet_len > counter && packet->payload[counter] >= 'a'
                                    && packet->payload[counter] <= 'z') {
                                NDPI_LOG_DBG2(ndpi_struct, "one letter\n");
                                counter++;
                                if(packet->payload_packet_len > counter
                                        && (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) {
                                    NDPI_LOG_DBG2(ndpi_struct, "whitespace2\n");
                                    return counter;
                                } else if(packet->payload_packet_len > counter && packet->payload[counter] >= 'a'
                                        && packet->payload[counter] <= 'z') {
                                    counter++;
                                    if(packet->payload_packet_len > counter
                                            && (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) {
                                        NDPI_LOG_DBG2(ndpi_struct, "whitespace3\n");
                                        return counter;
                                    } else {
                                        return 0;
                                    }
                                } else {
                                    return 0;
                                }
                            } else {
                                return 0;
                            }
                        } else {
                            return 0;
                        }
                    }
                }
                return 0;
            }
        }
    }
    return 0;
}

#ifdef NDPI_ENABLE_DEBUG_MESSAGES
/* ********************************************************************************* */

void ndpi_debug_get_last_log_function_line(struct ndpi_detection_module_struct
        *ndpi_struct, const char **file, const char **func, u_int32_t * line)
{
    *file = "";
    *func = "";

    if(ndpi_struct->ndpi_debug_print_file != NULL)
        *file = ndpi_struct->ndpi_debug_print_file;

    if(ndpi_struct->ndpi_debug_print_function != NULL)
        *func = ndpi_struct->ndpi_debug_print_function;

    *line = ndpi_struct->ndpi_debug_print_line;
}
#endif

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

u_int8_t ndpi_detection_get_l4(const u_int8_t * l3, u_int16_t l3_len, const u_int8_t ** l4_return, u_int16_t * l4_len_return,
        u_int8_t * l4_protocol_return, u_int32_t flags)
{
    return ndpi_detection_get_l4_internal(NULL, l3, l3_len, l4_return, l4_len_return, l4_protocol_return, flags);
}

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

void ndpi_set_detected_protocol(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        u_int16_t detected_protocol)
{
    struct ndpi_id_struct *src = flow->src;
    struct ndpi_id_struct *dst = flow->dst;

    ndpi_int_change_protocol(ndpi_struct, flow, detected_protocol);

    if(src != NULL) {
        NDPI_ADD_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, detected_protocol);
    }

    if(dst != NULL) {
        NDPI_ADD_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, detected_protocol);
    }
}

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

u_int16_t ndpi_get_flow_masterprotocol(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow) {
    return(flow->detected_protocol);
}

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

void ndpi_int_change_flow_protocol(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        u_int16_t detected_protocol) {
    if(!flow) return;

    flow->detected_protocol = detected_protocol;
}

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

void ndpi_int_change_packet_protocol(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow, u_int16_t detected_protocol) {
    struct ndpi_packet_struct *packet = &flow->packet;
    /* NOTE: everything below is identically to change_flow_protocol
     *        except flow->packet If you want to change something here,
     *        don't! Change it for the flow function and apply it here
     *        as well */

    if(!packet)
        return;

    packet->detected_protocol = detected_protocol;
}

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

/* generic function for changing the protocol
 *
 * what it does is:
 * 1.update the flow protocol stack with the new protocol
 * 2.update the packet protocol stack with the new protocol
 */
void ndpi_int_change_protocol(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        u_int16_t detected_protocol) {
    ndpi_int_change_flow_protocol(ndpi_struct, flow, detected_protocol);
    ndpi_int_change_packet_protocol(ndpi_struct, flow, detected_protocol);
}

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

/* turns a packet back to unknown */
void ndpi_int_reset_packet_protocol(struct ndpi_packet_struct *packet) {
    packet->detected_protocol = NDPI_PROTOCOL_UNKNOWN;
}

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

void ndpi_int_reset_protocol(struct ndpi_flow_struct *flow) {
    if(flow) {
        flow->detected_protocol = NDPI_PROTOCOL_UNKNOWN;
    }
}

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

void NDPI_PROTOCOL_IP_clear(ndpi_ip_addr_t * ip) {
    memset(ip, 0, sizeof(ndpi_ip_addr_t));
}

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

/* NTOP */
int NDPI_PROTOCOL_IP_is_set(const ndpi_ip_addr_t * ip)
{
    return memcmp(ip, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sizeof(ndpi_ip_addr_t)) != 0;
}

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

/* check if the source ip address in packet and ip are equal */
/* NTOP */
int ndpi_packet_src_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_ip_addr_t * ip)
{

#ifdef NDPI_DETECTION_SUPPORT_IPV6

    /* IPv6 */
    if(packet->iphv6 != NULL) {

        if(packet->iphv6->ip6_src.u6_addr.u6_addr32[0] == ip->ipv6.u6_addr.u6_addr32[0] &&
                packet->iphv6->ip6_src.u6_addr.u6_addr32[1] == ip->ipv6.u6_addr.u6_addr32[1] &&
                packet->iphv6->ip6_src.u6_addr.u6_addr32[2] == ip->ipv6.u6_addr.u6_addr32[2] &&
                packet->iphv6->ip6_src.u6_addr.u6_addr32[3] == ip->ipv6.u6_addr.u6_addr32[3])
            return 1;
        //else
        return 0;
    }
#endif

    /* IPv4 */
    if(packet->iph->saddr == ip->ipv4)
        return 1;
    return 0;
}

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

/* check if the destination ip address in packet and ip are equal */
int ndpi_packet_dst_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_ip_addr_t * ip)
{

#ifdef NDPI_DETECTION_SUPPORT_IPV6

    /* IPv6 */
    if(packet->iphv6 != NULL) {

        if(packet->iphv6->ip6_dst.u6_addr.u6_addr32[0] == ip->ipv6.u6_addr.u6_addr32[0] &&
                packet->iphv6->ip6_dst.u6_addr.u6_addr32[1] == ip->ipv6.u6_addr.u6_addr32[1] &&
                packet->iphv6->ip6_dst.u6_addr.u6_addr32[2] == ip->ipv6.u6_addr.u6_addr32[2] &&
                packet->iphv6->ip6_dst.u6_addr.u6_addr32[3] == ip->ipv6.u6_addr.u6_addr32[3])
            return 1;
        //else
        return 0;
    }
#endif

    /* IPv4 */
    if(packet->iph->saddr == ip->ipv4)
        return 1;
    return 0;
}

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

/* get the source ip address from packet and put it into ip */
/* NTOP */
void ndpi_packet_src_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_addr_t * ip)
{
    NDPI_PROTOCOL_IP_clear(ip);

#ifdef NDPI_DETECTION_SUPPORT_IPV6

    /* IPv6 */
    if(packet->iphv6 != NULL) {

        ip->ipv6.u6_addr.u6_addr32[0] = packet->iphv6->ip6_src.u6_addr.u6_addr32[0];
        ip->ipv6.u6_addr.u6_addr32[1] = packet->iphv6->ip6_src.u6_addr.u6_addr32[1];
        ip->ipv6.u6_addr.u6_addr32[2] = packet->iphv6->ip6_src.u6_addr.u6_addr32[2];
        ip->ipv6.u6_addr.u6_addr32[3] = packet->iphv6->ip6_src.u6_addr.u6_addr32[3];

    } else
#endif

        /* IPv4 */
        ip->ipv4 = packet->iph->saddr;
}

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

/* get the destination ip address from packet and put it into ip */
/* NTOP */
void ndpi_packet_dst_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_addr_t * ip)
{
    NDPI_PROTOCOL_IP_clear(ip);

#ifdef NDPI_DETECTION_SUPPORT_IPV6

    if(packet->iphv6 != NULL) {

        ip->ipv6.u6_addr.u6_addr32[0] = packet->iphv6->ip6_dst.u6_addr.u6_addr32[0];
        ip->ipv6.u6_addr.u6_addr32[1] = packet->iphv6->ip6_dst.u6_addr.u6_addr32[1];
        ip->ipv6.u6_addr.u6_addr32[2] = packet->iphv6->ip6_dst.u6_addr.u6_addr32[2];
        ip->ipv6.u6_addr.u6_addr32[3] = packet->iphv6->ip6_dst.u6_addr.u6_addr32[3];

    } else

#endif

        ip->ipv4 = packet->iph->daddr;
}

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

#ifdef NDPI_ENABLE_DEBUG_MESSAGES
/* get the string representation of ip
 * returns a pointer to a static string
 * only valid until the next call of this function */
char *ndpi_get_ip_string(struct ndpi_detection_module_struct *ndpi_struct,
        const ndpi_ip_addr_t * ip)
{
    const u_int8_t *a = (const u_int8_t *) &ip->ipv4;

#ifdef NDPI_DETECTION_SUPPORT_IPV6
    if(ip->ipv6.u6_addr.u6_addr32[0] != 0 ||
            ip->ipv6.u6_addr.u6_addr32[1] != 0 ||
            ip->ipv6.u6_addr.u6_addr32[2] != 0 ||
            ip->ipv6.u6_addr.u6_addr32[3] != 0) {

        const u_int16_t *b = ip->ipv6.u6_addr.u6_addr16;
        snprintf(ndpi_struct->ip_string, 32, "%x:%x:%x:%x:%x:%x:%x:%x",
                ntohs(b[0]), ntohs(b[1]), ntohs(b[2]), ntohs(b[3]),
                ntohs(b[4]), ntohs(b[5]), ntohs(b[6]), ntohs(b[7]));

        return ndpi_struct->ip_string;
    }
#endif

    snprintf(ndpi_struct->ip_string, 32, "%u.%u.%u.%u", a[0], a[1], a[2], a[3]);

    return ndpi_struct->ip_string;

}

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

/* get the string representation of the source ip address from packet */
char *ndpi_get_packet_src_ip_string(struct ndpi_detection_module_struct *ndpi_struct,
        const struct ndpi_packet_struct *packet)
{
    ndpi_ip_addr_t ip;
    ndpi_packet_src_ip_get(packet, &ip);
    return ndpi_get_ip_string(ndpi_struct, &ip);
}

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

/* get the string representation of the destination ip address from packet */
char *ndpi_get_packet_dst_ip_string(struct ndpi_detection_module_struct *ndpi_struct,
        const struct ndpi_packet_struct *packet) {
    ndpi_ip_addr_t ip;
    ndpi_packet_dst_ip_get(packet, &ip);
    return ndpi_get_ip_string(ndpi_struct, &ip);
}
#endif /* NDPI_ENABLE_DEBUG_MESSAGES */

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

u_int16_t ntohs_ndpi_bytestream_to_number(const u_int8_t * str,
        u_int16_t max_chars_to_read,
        u_int16_t * bytes_read) {
    u_int16_t val = ndpi_bytestream_to_number(str, max_chars_to_read, bytes_read);
    return ntohs(val);
}

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

u_int8_t ndpi_is_proto(ndpi_protocol proto, u_int16_t p) {
    return(((proto.app_protocol == p) || (proto.master_protocol == p)) ? 1 : 0);
}

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

u_int16_t ndpi_get_lower_proto(ndpi_protocol proto) {
    return((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN) ?
            proto.master_protocol : proto.app_protocol);
}

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

u_int16_t ndpi_guess_undetected_protocol(
        struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        u_int8_t proto, u_int16_t sport, u_int16_t dport) {

    ndpi_default_ports_tree_node_t *ret;

    if (proto != IPPROTO_TCP && proto != IPPROTO_UDP)
        return(NDPI_PROTOCOL_UNKNOWN);

    if ((ret = ndpi_guess_protocol_node(ndpi_struct,
                    proto, sport, dport)) == NULL)
        return(NDPI_PROTOCOL_UNKNOWN);

    return(ret->proto->proto_id);
}

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

char* ndpi_protocol2id(struct ndpi_detection_module_struct *ndpi_mod,
        ndpi_protocol proto, char *buf, u_int buf_len) {
    if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN)
            && (proto.master_protocol != proto.app_protocol)) {
        if(proto.app_protocol != NDPI_PROTOCOL_UNKNOWN)
            snprintf(buf, buf_len, "%u.%u",
                    proto.master_protocol, proto.app_protocol);
        else
            snprintf(buf, buf_len, "%u", proto.master_protocol);
    } else
        snprintf(buf, buf_len, "%u", proto.app_protocol);

    return(buf);
}

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

char* ndpi_protocol2name(struct ndpi_detection_module_struct *ndpi_mod,
        ndpi_protocol proto, char *buf, u_int buf_len) {
    if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN)
            && (proto.master_protocol != proto.app_protocol)) {
        if(proto.app_protocol != NDPI_PROTOCOL_UNKNOWN)
            snprintf(buf, buf_len, "%s.%s",
                    ndpi_get_proto_name(ndpi_mod, proto.master_protocol),
                    ndpi_get_proto_name(ndpi_mod, proto.app_protocol));
        else
            snprintf(buf, buf_len, "%s",
                    ndpi_get_proto_name(ndpi_mod, proto.master_protocol));
    } else
        snprintf(buf, buf_len, "%s",
                ndpi_get_proto_name(ndpi_mod, proto.app_protocol));

    return(buf);
}

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

char* ndpi_get_proto_name(struct ndpi_detection_module_struct *ndpi_mod, u_int16_t proto_id) {
    if((proto_id >= ndpi_mod->ndpi_num_supported_protocols)
            || ((proto_id < (NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS))
                && (ndpi_mod->proto_defaults[proto_id].proto_name == NULL)))
        proto_id = NDPI_PROTOCOL_UNKNOWN;

    return(ndpi_mod->proto_defaults[proto_id].proto_name);
}

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

void ndpi_dump_protocols(struct ndpi_detection_module_struct *ndpi_mod) {
    int i;
    for(i=0; i<(int)ndpi_mod->ndpi_num_supported_protocols; i++)
        printf("[%3d] %s\n", i, ndpi_mod->proto_defaults[i].proto_name);
}

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

/*
 * Find the first occurrence of find in s, where the search is limited to the
 * first slen characters of s.
 */
char* ndpi_strnstr(const char *s, const char *find, size_t slen) {
    char c, sc;
    size_t len;

    if((c = *find++) != '\0') {
        len = strlen(find);
        do {
            do {
                if(slen-- < 1 || (sc = *s++) == '\0')
                    return (NULL);
            } while (sc != c);
            if(len > slen)
                return (NULL);
        } while (strncmp(s, find, len) != 0);
        s--;
    }
    return ((char *)s);
}

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

/*
 * Same as ndpi_strnstr but case-insensitive
 */
char* ndpi_strncasestr(const char *s, const char *find, size_t slen) {
    char c, sc;
    size_t len;

    if((c = *find++) != '\0') {
        len = strnlen(find, slen);
        do {
            do {
                if(slen-- < 1 || (sc = *s++) == '\0')
                    return (NULL);
            } while (sc != c);
            if(len > slen)
                return (NULL);
        } while (strncasecmp(s, find, len) != 0);
        s--;
    }

    return ((char *)s);
}

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

int ndpi_match_prefix(const u_int8_t *payload, size_t payload_len,
        const char *str, size_t str_len)
{
    int rc = str_len <= payload_len ? memcmp(payload, str, str_len) == 0 : 0;

    return rc;
}

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

static int _ndpi_match_string_app_proto(struct ndpi_detection_module_struct *ndpi_struct,
        char *string_to_match, u_int string_to_match_len,
        ndpi_protocol_match_result *ret_match,
        u_int8_t is_host_match) {

    AC_TEXT_t ac_input_text;
    AC_REP_t match = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED };
    ndpi_automa *automa = is_host_match ? &ndpi_struct->host_automa : &ndpi_struct->content_automa;

    if((automa->ac_automa == NULL) || (string_to_match_len == 0)) return(NDPI_PROTOCOL_UNKNOWN);

    if (automa->lock != NULL) pthread_mutex_lock(automa->lock);

    if(!automa->ac_automa_finalized) {
        ac_automata_finalize((AC_AUTOMATA_t*)automa->ac_automa);
        automa->ac_automa_finalized = 1;
    }

    ac_input_text.astring = string_to_match, ac_input_text.length = string_to_match_len;

    ac_automata_search(((AC_AUTOMATA_t*)automa->ac_automa), &ac_input_text, (void*)&match);

    ac_automata_reset(((AC_AUTOMATA_t*)automa->ac_automa));

    if (automa->lock != NULL) pthread_mutex_unlock(automa->lock);

    return(match.number);
}

#undef _ND_HEAD_HOST_MATCH

#ifndef _ND_HEAD_HOST_MATCH
#define _ND_HOST_MATCH_LEVEL    3
#else
#define _ND_HOST_MATCH_LEVEL    4
#endif
#define _ND_HOST_MATCH_LEN      (256 + 3)

int ndpi_match_string_app_proto(struct ndpi_detection_module_struct *ndpi_struct,
        char *string_to_match,
        u_int string_to_match_len,
        ndpi_protocol_match_result *ret_match,
        u_int8_t is_host_match) {

    int i, match_id = NDPI_PROTOCOL_UNKNOWN;
    char host_match_pattern[_ND_HOST_MATCH_LEVEL][_ND_HOST_MATCH_LEN];

    if (! is_host_match) {
        return _ndpi_match_string_app_proto(ndpi_struct,
                string_to_match, string_to_match_len, ret_match, is_host_match
                );
    }

    snprintf(host_match_pattern[0], _ND_HOST_MATCH_LEN, "^%s$", string_to_match);
    snprintf(host_match_pattern[1], _ND_HOST_MATCH_LEN, "%s$", string_to_match);
#ifdef _ND_HEAD_HOST_MATCH
    snprintf(host_match_pattern[2], _ND_HOST_MATCH_LEN, "^%s", string_to_match);
    snprintf(host_match_pattern[3], _ND_HOST_MATCH_LEN, "%s", string_to_match);
#else
    snprintf(host_match_pattern[2], _ND_HOST_MATCH_LEN, "%s", string_to_match);
#endif

    for (i = 0; i < _ND_HOST_MATCH_LEVEL; i++) {
        if ((match_id = _ndpi_match_string_app_proto(ndpi_struct,
                        host_match_pattern[i], strlen(host_match_pattern[i]),
                        ret_match, is_host_match
                        )) != NDPI_PROTOCOL_UNKNOWN) break;
    }

    return match_id;
}

#ifdef HAVE_HYPERSCAN

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

static int hyperscanEventHandler(unsigned int id, unsigned long long from,
        unsigned long long to, unsigned int flags, void *ctx) {
    *((int *)ctx) = (int)id;

    NDPI_LOG_DBG2(ndpi_struct, "[NDPI] Match with: %d [from: %llu][to: %llu]\n", id, from, to);

    /* return HS_SCAN_TERMINATED; */
    return 0; /* keep searching */
}

#endif

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

static int ndpi_automa_match_string_app_proto(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        char *string_to_match, u_int string_to_match_len,
        ndpi_protocol_match_result *ret_match,
        u_int8_t is_host_match) {
    int matching_protocol_id;

#ifndef HAVE_HYPERSCAN
    matching_protocol_id = ndpi_match_string_app_proto(ndpi_struct, string_to_match,
            string_to_match_len, ret_match,
            is_host_match);

#else
    struct hs *hs = (struct hs*)ndpi_struct->hyperscan;
    hs_error_t status;

    matching_protocol_id = NDPI_PROTOCOL_UNKNOWN;
    /*
       TODO HYPERSCAN
       In case of match fill up ret_match and set flow protocol + category
       */
    status = hs_scan(hs->database, string_to_match,
            string_to_match_len, 0, hs->scratch,
            hyperscanEventHandler, &matching_protocol_id);

    if(status == HS_SUCCESS) {
        NDPI_LOG_DBG2(ndpi_struct, "[NDPI] Hyperscan engine completed normally. Result: %s [%d][%s]\n",
                ndpi_get_proto_name(ndpi_struct, matching_protocol_id), matching_protocol_id, string_to_match);
    } else if(status == HS_SCAN_TERMINATED) {
        NDPI_LOG_DBG2(ndpi_struct, "[NDPI] Hyperscan engine was terminated by callback. Result: %s [%d][%s]\n",
                ndpi_get_proto_name(ndpi_struct, matching_protocol_id), matching_protocol_id, string_to_match);
    } else {
        NDPI_LOG_DBG2(ndpi_struct, "[NDPI] Hyperscan returned with error.\n");
    }

    ret_match->protocol_id = matching_protocol_id;
#endif

#ifdef DEBUG
    {
        char m[256];
        int len = ndpi_min(sizeof(m), string_to_match_len);

        strncpy(m, string_to_match, len);
        m[len] = '\0';

        NDPI_LOG_DBG2(ndpi_struct, "[NDPI] ndpi_match_string_app_proto(%s): %s\n",
                m, ndpi_struct->proto_defaults[id].proto_name);
    }
#endif

#ifdef DEBUG
    if (matching_protocol_id == NDPI_PROTOCOL_UNKNOWN) {
        string_to_match[string_to_match_len] = '\0';
        NDPI_LOG_DBG2(ndpi_struct, "[NTOP] Unable to find a match for '%s'\n", string_to_match);
    }
#endif

    return(matching_protocol_id);
}

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

int ndpi_match_host_app_proto(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        char *string_to_match, u_int string_to_match_len,
        ndpi_protocol_match_result *ret_match) {
    return(ndpi_automa_match_string_app_proto(ndpi_struct,
                flow, string_to_match, string_to_match_len,
                ret_match, 1));
}

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

int ndpi_match_content_app_proto(struct ndpi_detection_module_struct *ndpi_struct,
        struct ndpi_flow_struct *flow,
        char *string_to_match, u_int string_to_match_len,
        ndpi_protocol_match_result *ret_match) {
    return(ndpi_automa_match_string_app_proto(ndpi_struct, flow,
                string_to_match, string_to_match_len,
                ret_match, 0));
}

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

void ndpi_free_flow(struct ndpi_flow_struct *flow) {
    if(flow) {
        if(flow->http.url)
            ndpi_free(flow->http.url);
        if(flow->http.content_type)
            ndpi_free(flow->http.content_type);
        ndpi_free(flow);
    }
}

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

char* ndpi_revision() { return(NDPI_GIT_RELEASE); }

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

#ifdef WIN32

/*  http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/port/gettimeofday.c;h=75a91993b74414c0a1c13a2a09ce739cb8aa8a08;hb=HEAD */
int gettimeofday(struct timeval * tp, struct timezone * tzp) {
    /* FILETIME of Jan 1 1970 00:00:00. */
    const unsigned __int64 epoch = (__int64)(116444736000000000);

    FILETIME    file_time;
    SYSTEMTIME  system_time;
    ULARGE_INTEGER ularge;

    GetSystemTime(&system_time);
    SystemTimeToFileTime(&system_time, &file_time);
    ularge.LowPart = file_time.dwLowDateTime;
    ularge.HighPart = file_time.dwHighDateTime;

    tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L);
    tp->tv_usec = (long) (system_time.wMilliseconds * 1000);

    return 0;
}
#endif

int NDPI_BITMASK_COMPARE(NDPI_PROTOCOL_BITMASK a, NDPI_PROTOCOL_BITMASK b) {
    int i;

    for(i=0; i<NDPI_NUM_FDS_BITS; i++) {
        if(a.fds_bits[i] & b.fds_bits[i])
            return(1);
    }

    return(0);
}

int NDPI_BITMASK_IS_EMPTY(NDPI_PROTOCOL_BITMASK a) {
    int i;

    for(i=0; i<NDPI_NUM_FDS_BITS; i++)
        if(a.fds_bits[i] != 0)
            return(0);

    return(1);
}

void NDPI_DUMP_BITMASK(NDPI_PROTOCOL_BITMASK a) {
    int i;
    for(i=0; i<NDPI_NUM_FDS_BITS; i++)
        printf("[%d=%u]", i, a.fds_bits[i]);

    printf("\n");
}

u_int8_t ndpi_get_api_version() {
    return(NDPI_API_VERSION);
}

ndpi_proto_defaults_t* ndpi_get_proto_defaults(struct ndpi_detection_module_struct *ndpi_mod) {
    return(ndpi_mod->proto_defaults);
}

u_int ndpi_get_ndpi_num_supported_protocols(struct ndpi_detection_module_struct *ndpi_mod) {
    return(ndpi_mod->ndpi_num_supported_protocols);
}

u_int ndpi_get_ndpi_num_custom_protocols(struct ndpi_detection_module_struct *ndpi_mod) {
    return(ndpi_mod->ndpi_num_custom_protocols);
}

u_int ndpi_get_ndpi_detection_module_size() {
    return(sizeof(struct ndpi_detection_module_struct));
}

void ndpi_set_log_level(struct ndpi_detection_module_struct *ndpi_mod, u_int l) {
    ndpi_mod->ndpi_log_level = l;
}

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

/* LRU cache */

struct ndpi_lru_cache* ndpi_lru_cache_init(u_int32_t num_entries) {
    struct ndpi_lru_cache *c = (struct ndpi_lru_cache*)malloc(sizeof(struct ndpi_lru_cache));

    if(!c) return(NULL);

    c->entries = (u_int32_t*)calloc(num_entries, sizeof(u_int32_t));

    if(!c->entries) {
        free(c);
        return(NULL);
    } else
        c->num_entries = num_entries;

    return(c);
}

void ndpi_lru_free_cache(struct ndpi_lru_cache *c) {
    free(c->entries);
    free(c);
}


u_int8_t ndpi_lru_find_cache(struct ndpi_lru_cache *c, u_int32_t key, u_int8_t clean_key_when_found) {
    u_int32_t slot = key % c->num_entries;

    if(c->entries[slot] == key) {
        if(clean_key_when_found) c->entries[slot] = 0;
        return(1);
    } else
        return(0);
}

void ndpi_lru_add_to_cache(struct ndpi_lru_cache *c, u_int32_t key) {
    u_int32_t slot = key % c->num_entries;

    c->entries[slot] = key;
}

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

/*
NOTE:
- Leave fields empty/zero when information is missing (e.g. with ICMP ports are zero)
- The hash_buf must be 30+1 bits or longer
- Return code: 0 = OK, -1 otherwise
*/

int ndpi_flowv4_flow_hash(u_int8_t l4_proto, u_int32_t src_ip,
        u_int32_t dst_ip, u_int16_t src_port, u_int16_t dst_port,
        u_int8_t icmp_type, u_int8_t icmp_code,
        u_char *hash_buf, u_int8_t hash_buf_len) {

    return(0); /* OK */
}

int ndpi_flowv6_flow_hash(u_int8_t l4_proto, struct ndpi_in6_addr *src_ip, struct ndpi_in6_addr *dst_ip,
        u_int16_t src_port, u_int16_t dst_port, u_int8_t icmp_type, u_int8_t icmp_code,
        u_char *hash_buf, u_int8_t hash_buf_len) {

    return(0); /* OK */
}

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

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

struct cipher_weakness {
    u_int16_t cipher_id;
    ndpi_cipher_weakness weakness_type;
};

static struct cipher_weakness safe_ssl_ciphers[] = {
    /* https://community.qualys.com/thread/18212-how-does-qualys-determine-the-server-cipher-suites */
    /* INSECURE */
    { 0xc011, NDPI_CIPHER_INSECURE }, /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
    { 0x0005, NDPI_CIPHER_INSECURE }, /* TLS_RSA_WITH_RC4_128_SHA */
    { 0x0004, NDPI_CIPHER_INSECURE }, /* TLS_RSA_WITH_RC4_128_MD5 */
    /* WEAK */
    { 0x009d, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
    { 0x003d, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
    { 0x0035, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_AES_256_CBC_SHA */
    { 0x0084, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
    { 0x009c, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
    { 0x003c, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
    { 0x002f, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_AES_128_CBC_SHA */
    { 0x0041, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
    { 0xc012, NDPI_CIPHER_WEAK }, /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
    { 0x0016, NDPI_CIPHER_WEAK }, /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
    { 0x000a, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
    { 0x0096, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_SEED_CBC_SHA */
    { 0x0007, NDPI_CIPHER_WEAK }, /* TLS_RSA_WITH_IDEA_CBC_SHA */

    { 0x0, NDPI_CIPHER_SAFE    } /* END */
};

u_int8_t ndpi_is_safe_ssl_cipher(u_int16_t cipher) {
    u_int i;

    for(i=0; safe_ssl_ciphers[i].cipher_id  != 0; i++) {
        if(safe_ssl_ciphers[i].cipher_id == cipher) {
#ifdef CERTIFICATE_DEBUG
            printf("%s %s(%04X / %u)\n",
                    (safe_ssl_ciphers[i].weakness_type == NDPI_CIPHER_WEAK) ? "WEAK" : "INSECURE",
                    __FUNCTION__, cipher, cipher);
#endif

            return(safe_ssl_ciphers[i].weakness_type);
        }
    }

    return(NDPI_CIPHER_SAFE); /* We're safe */
}
