view mapsearch.c @ 1798:76dce42776f8

Set center coordinates to zero by default.
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 29 Oct 2017 21:12:06 +0200
parents bd0e0b62c8f9
children 7c7f24e27a60
line wrap: on
line source

/*
 * PupuMaps Search WebSockets server
 * Written by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
 * (C) Copyright 2017 Tecnic Software productions (TNSP)
 */
#include "th_args.h"
#include "th_config.h"
#include "liblocfile.h"
#include "libmaputils.h"
#include <libwebsockets.h>


/* Default settings etc. constants
 */
#define SET_MAX_MAPS        16  // Maximum number of maps allowed to be loaded
#define SET_MAX_LISTEN      4   // Maximum number of interfaces to listen
#define SET_MAX_MATCHES     32  // Maximum number of match results per query


// List of default SSL/TLS ciphers to use/allowed
#define SET_DEF_CIPHERS	\
        "ECDHE-ECDSA-AES256-GCM-SHA384:"	\
        "ECDHE-RSA-AES256-GCM-SHA384:"		\
        "DHE-RSA-AES256-GCM-SHA384:"		\
        "ECDHE-RSA-AES256-SHA384:"		\
        "HIGH:!aNULL:!eNULL:!EXPORT:"		\
        "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"	\
        "!SHA1:!DHE-RSA-AES128-GCM-SHA256:"	\
        "!DHE-RSA-AES128-SHA256:"		\
        "!AES128-GCM-SHA256:"			\
        "!AES128-SHA256:"			\
        "!DHE-RSA-AES256-SHA256:"		\
        "!AES256-GCM-SHA384:"			\
        "!AES256-SHA256"


/* Structure that holds information about one listen interface
 */
typedef struct
{
    char *interface;         // Listen interface (* = listen all)
    int port;                // Port number

    BOOL useIPv6;
    BOOL useSSL;             // Use SSL/TLS?
    char *sslCertFile,       // Certificate file
         *sslKeyFile,        // Key file
         *sslCAFile;         // CA file

    struct lws_vhost *vhost; // LWS vhost info
} MAPListenerCtx;


/* Structure for holding information about one map and its locations
 */
typedef struct
{
    char *mapFilename;       // Filename of the map data
    LocFileInfo locFile;     // Location file info struct
    MapBlock *map;           // Map data, when loaded
    MapLocations loc;        // Map locations, when loaded
} MAPInfoCtx;


typedef struct
{
    char *map;
    int mx, my, wx, wy;
    double accuracy;
    BOOL centered;
} MAPMatch;


/* Options
 */
MAPInfoCtx optMaps[SET_MAX_MAPS];
MAPListenerCtx *optListenTo[SET_MAX_LISTEN];
int     optNMaps = 0;
int     optNListenTo = 0;
char    *optCleanChars = " *@?%C";
char    *optSSLCipherList = SET_DEF_CIPHERS;
struct lws_context *setLWSContext = NULL;
int     optWorldXC = 0, optWorldYC = 0;


/* Arguments
 */
static const th_optarg optList[] =
{
    { 0, '?', "help",          "Show this help and be so very much verbose that it almost hurts you", OPT_NONE },
    { 1, 'v', "verbose",       "Be more verbose", OPT_NONE },
    { 2, 'l', "listen",        "Listen to interface (see below)", OPT_ARGREQ },
    { 3,   0, "ssl-ciphers",   "Specify list of SSL/TLS ciphers", OPT_ARGREQ },
    { 4,   0, "clean-chars",   "String of characters to 'clean up' from map blocks.", OPT_ARGREQ },
    { 5, 'w', "world-origin",  "Specify the world origin <x, y> which map offsets relate to.", OPT_ARGREQ },
};

static const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp(void)
{
    th_print_banner(stdout, th_prog_name, "[options] <map spec>");
    th_args_help(stdout, optList, optListN, 0);

    fprintf(stdout,
        "\n"
        "Listening interface(s) are specified with following syntax:\n"
        "-l \"[@]<interface/IP/host>:<port>[=<SSL/TLS spec>]\"\n"
        "\n"
        "IPv6 addresses should be specified with the square bracket notation.\n"
        "To force IPv6, use IPv6 address [] or prefix spec with @. In order to\n"
        "listen to all interfaces, you can specify an asterisk (*) as host. Example:\n"
        "\n"
        "-l *:3491 -l *:3492=<ssl_cert_file.crt>:<ssl_key_file.key>:<ca_file.crt>\n"
        "\n"
        "Would listen for normal WebSocket (ws://) connections on port 3491 and for\n"
        "secure SSL/TLS WebSocket (wss://) connections on port 3492 of all interfaces.\n"
        "\n"
        "Maps and location files for each map are specified as follows:\n"
        "<filename.map>:<locfilename.loc>:<map/continent name>[:<world x-offset>:<world y-offset>]\n"
    );
}


BOOL mapParseCoordPair(const char *str, int *xc, int *yc)
{
    char *piece, *tmp, *fmt = th_strdup(str);
    BOOL ret = FALSE;

    if ((piece = strchr(fmt, ':')) == NULL)
        goto err;
    *piece++ = 0;

    tmp = th_strdup_trim(fmt, TH_TRIM_BOTH);
    *xc = atoi(tmp);
    th_free(tmp);

    tmp = th_strdup_trim(piece, TH_TRIM_BOTH);
    *yc = atoi(tmp);
    th_free(tmp);

    ret = TRUE;

err:
    th_free(fmt);
    return ret;
}


BOOL mapParseMapSpec(const char *str, MAPInfoCtx *info)
{
    char *piece, *start, *fmt = th_strdup(str);
    BOOL ret = FALSE;

    // Check for map filename end
    if ((piece = strchr(fmt, ':')) == NULL)
        goto err;
    *piece++ = 0;

    info->mapFilename = th_strdup_trim(fmt, TH_TRIM_BOTH);
    start = piece;

    // Check for loc filename end
    if ((piece = strchr(start, ':')) == NULL)
        goto err;
    *piece++ = 0;

    info->locFile.filename = th_strdup_trim(start, TH_TRIM_BOTH);
    start = piece;

    // Check for world x-offset separator
    if ((piece = strchr(start, ':')) != NULL)
        *piece++ = 0;

    info->locFile.continent = th_strdup_trim(start, TH_TRIM_BOTH);

    // Get world X/Y offsets, if any
    if (piece != NULL &&
        !mapParseCoordPair(piece, &info->locFile.x, &info->locFile.y))
        goto err;

    ret = TRUE;

err:
    th_free(fmt);
    return ret;
}


MAPListenerCtx *mapNewListenCtx(void)
{
    return th_malloc0(sizeof(MAPListenerCtx));
}


void mapFreeListenCtxR(MAPListenerCtx *ctx)
{
    if (ctx != NULL)
    {
        th_free(ctx->interface);
        th_free(ctx->sslCertFile);
        th_free(ctx->sslKeyFile);
        th_free(ctx->sslCAFile);
    }
}


void mapFreeListenCtx(MAPListenerCtx *ctx)
{
    mapFreeListenCtxR(ctx);
    th_free(ctx);
}


MAPListenerCtx *mapParseListenerSpec(const char *cfmt)
{
    char *start, *end, *flags, *port = NULL, *interface, *fmt = th_strdup(cfmt);
    BOOL ret = FALSE;
    MAPListenerCtx *ctx;

    if ((ctx = mapNewListenCtx()) == NULL)
        goto out;

    interface = fmt;
    if (*interface == '@')
    {
        interface++;
        ctx->useIPv6 = TRUE;
    }

    if (*interface == '[')
    {
        // IPv6 IP address is handled in a special case
        interface++;
        if ((end = strchr(interface, ']')) == NULL)
        {
            THERR("Invalid IPv6 IP address '%s'.\n", cfmt);
            goto out;
        }
        *end++ = 0;
        ctx->useIPv6 = TRUE;
    }
    else
    {
        end = strchr(interface, ':');
    }

    // Find port number separator
    if (end == NULL || *end != ':')
    {
        THERR("Missing listening port in '%s'.\n", cfmt);
        goto out;
    }
    *end++ = 0;
    start = end;

    // Check for '=flags' at the end
    if ((flags = strchr(start, '=')) != NULL)
        *flags++ = 0;

    // Get the interface name
    ctx->interface = th_strdup_trim(interface, TH_TRIM_BOTH);
    if (strcmp(ctx->interface, "*") == 0)
    {
        th_free(ctx->interface);
        ctx->interface = NULL;
    }

    // Get port number
    if ((port = th_strdup_trim(start, TH_TRIM_BOTH)) == NULL)
    {
        THERR("Missing listening port in '%s'.\n", cfmt);
        goto out;
    }
    ctx->port = atoi(port);

    // Parse the SSL/TLS spec, if any
    if (flags != NULL)
    {
        char *cstart = flags, *cend;
        if ((cend = strchr(cstart, ':')) == NULL)
        {
            THERR("Invalid SSL/TLS spec '%s'\n", flags);
            goto out;
        }
        *cend++ = 0;

        ctx->sslCertFile = th_strdup_trim(cstart, TH_TRIM_BOTH);
        cstart = cend;

        if ((cend = strchr(cend, ':')) == NULL)
        {
            THERR("Invalid SSL/TLS spec '%s'\n", flags);
            goto out;
        }
        *cend++ = 0;

        ctx->sslKeyFile = th_strdup_trim(cstart, TH_TRIM_BOTH);
        ctx->sslCAFile = th_strdup_trim(cend, TH_TRIM_BOTH);
        ctx->useSSL = TRUE;
    }

    // Check for duplicates
    for (int n = 0; n < optNListenTo; n++)
    {
        MAPListenerCtx *chk = optListenTo[n];
        if ((
            (ctx->interface == NULL && chk->interface == NULL) ||
            (ctx->interface != NULL && chk->interface != NULL && strcmp(ctx->interface, chk->interface) == 0)) &&
            chk->port == ctx->port)
        {
            THERR("Duplicate listener spec (%s:%d)\n",
                ctx->interface != NULL ? ctx->interface : "*", ctx->port);
            goto out;
        }
    }
    ret = TRUE;

out:
    th_free(fmt);
    th_free(port);

    if (ret)
        return ctx;

    mapFreeListenCtx(ctx);
    return NULL;
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN)
    {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 1:
        th_verbosityLevel++;
        break;

    case 2:
        if (optNListenTo < SET_MAX_LISTEN)
        {
            MAPListenerCtx *ctx;
            if ((ctx = mapParseListenerSpec(optArg)) != NULL)
                optListenTo[optNListenTo++] = ctx;
            else
                return FALSE;
        }
        else
        {
            THERR("Maximum number of listener specs already specified.\n");
            return FALSE;
        }
        break;

    case 3:
        optSSLCipherList = optArg;
        break;

    case 4:
        optCleanChars = optArg;
        break;

    case 5:
        if (!mapParseCoordPair(optArg, &optWorldXC, &optWorldYC))
        {
            THERR("Invalid world origin coordinates '%s'.\n", optArg);
            return FALSE;
        }
        break;

    default:
        THERR("Unknown option '%s'.\n", currArg);
        return FALSE;
    }

    return TRUE;
}


BOOL argHandleFile(char *currArg)
{
    if (optNMaps < SET_MAX_MAPS)
    {
        if (!mapParseMapSpec(currArg, &optMaps[optNMaps]))
        {
            THERR("Invalid map spec '%s'.\n", currArg);
            return FALSE;
        }

        optNMaps++;
        return TRUE;
    }
    else
    {
        THERR("Maximum number of map specs already specified.\n");
        return FALSE;
    }
}


void mapSigHandler(uv_signal_t *watcher, int signum)
{
    (void) signum;

    THERR("Signal %d caught, exiting...\n", watcher->signum);

    switch (watcher->signum)
    {
        case SIGTERM:
        case SIGINT:
            break;

        default:
            signal(SIGABRT, SIG_DFL);
            abort();
            break;
    }

    lws_libuv_stop(setLWSContext);
}


void mapBlockGetDimensions(const unsigned char *data, const size_t len, int *width, int *height)
{
    size_t offs = 0;
    int x = 0, x2 = 0;
    BOOL flag = TRUE;

    *height = 0;
    *width = -1;

    while (offs < len)
    {
        const unsigned char c = data[offs++];
        if (c == '\n')
        {
            if (x == 0)
                return;

            if (x > *width)
                *width = x;

            x = x2 = 0;
            flag = TRUE;
        }
        else
        {
            if (flag)
            {
                (*height)++;
                flag = FALSE;
            }
            x2++;
            if (c != ' ')
                x = x2;
        }
    }

    if (x > 0)
        (*height)++;

    if (x > *width)
        *width = x;
}


BOOL mapBlockParse(const unsigned char *data, const size_t len, MapBlock *res)
{
    unsigned char *dp = res->data;
    size_t offs = 0;
    BOOL flag = FALSE;
    int x = 0, y = 0;

    while (offs < len && y < res->height)
    {
        const unsigned char c = data[offs++];
        if (c == '\n')
        {
            if (x == 0)
                return TRUE;
            else
            if (x != res->width)
            {
                THERR("Broken block line #%d width %d != %d!\n",
                    y, x, res->width);

                return FALSE;
            }
            flag = TRUE;
        }
        else
        {
            if (flag)
            {
                x = 0;
                y++;
                dp = res->data + (y * res->scansize);
                flag = FALSE;
            }
            dp[x++] = c;

            if (x > res->scansize)
            {
                THERR("Broken block line #%d width %d > scansize %d!\n",
                    y, x, res->scansize);

                return FALSE;
            }
        }
    }

    return TRUE;
}


BOOL mapBlockFindCenter(const MapBlock *block, const char *symbols, int *cx, int *cy, const int tolerance)
{
    const int
        x0 = (block->width * tolerance) / 100,
        x1 = (block->width * (100 - tolerance)) / 100,
        y0 = (block->height * tolerance) / 100,
        y1 = (block->height * (100 - tolerance)) / 100;

    *cx = *cy = 0;
    for (int yc = 0; yc < block->height; yc++)
    {
        unsigned char *dp = block->data + (yc * block->scansize);
        for (int xc = 0; xc < block->width; xc++)
        {
            if (xc >= x0 && xc <= x1 &&
                yc >= y0 && yc <= y1 &&
                strchr(symbols, dp[xc]) != NULL)
            {
                *cx = xc;
                *cy = yc;
                return TRUE;
            }
        }
    }

    return FALSE;
}


int mapBlockGetEntropy(const MapBlock *map, const char *exclude, const int nexclude)
{
    unsigned char *list;
    int num, i;

    // Allocate memory for entropy array
    if ((list = th_malloc0(256)) == NULL)
        return -1;

    // Collect sums into entropy array
    for (int y = 0; y < map->height; y++)
    {
        unsigned char *c = map->data + (y * map->scansize);
        for (int x = 0; x < map->width; x++)
        {
            list[*c]++;
            c++;
        }
    }

    // Handle exclusions
    if (exclude != NULL && nexclude > 0)
    {
        for (i = 0; i < nexclude; i++)
            list[(int) exclude[i]] = 0;
    }

    // Calculate simple entropy
    for (num = 0, i = 0; i < 256; i++)
        if (list[i]) num++;

    th_free(list);
    return num;
}


double mapMatchBlock(const MapBlock *map, const MapBlock *match, int ox, int oy, int fillCh, BOOL hardDrop)
{
    int n, k;

    n = k = 0;
    for (int y = 0; y < match->height; y++)
    {
        unsigned char *dp = match->data + (y * match->scansize);
        for (int x = 0; x < match->width; x++)
        {
            int c1, c2;
            const int
                dx = ox + x,
                dy = oy + y;

            k++;

            if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height)
                c2 = map->data[(dy * map->scansize) + dx];
            else
                c2 = fillCh;

            c1 = dp[x];

            if (c1 == 0 || c1 == c2)
                n++;
            else
            if (hardDrop)
                return -1;
        }
    }

    if (k > 0)
        return ((double) n * 100.0f) / (double) k;
    else
        return 0.0f;
}


void mapPerformSearch(struct lws *wsi, const unsigned char *data, const size_t len, char **verr)
{
    int width, height, centerX, centerY;
    MapBlock *pattern = NULL;
    MAPMatch matches[SET_MAX_MATCHES];
    int nmatches = 0, maxMatches = SET_MAX_MATCHES;
    BOOL centerFound;

    // Parse pattern block dimensions
    mapBlockGetDimensions(data, len, &width, &height);
    if (width <= 0 || height <= 0)
    {
        *verr = "Could not parse map block dimensions.";
        goto out;
    }

    if (width * height < 3)
    {
        *verr = "Search block pattern too small.";
        goto out;
    }

    if ((pattern = mapBlockAlloc(width, height)) == NULL)
        goto out;

    if (!mapBlockParse(data, len, pattern))
    {
        *verr = "Error parsing map block data.";
        goto out;
    }

    // Entropy check
    int entropy = mapBlockGetEntropy(pattern, optCleanChars, strlen(optCleanChars));

    if ((entropy < 2 && width < 10 && height < 10) ||
        (entropy < 3 && width * height < 6))
    {
        *verr = "Search block entropy insufficient.";
        goto out;
    }

    // Find pattern center marker, if any
    centerFound = mapBlockFindCenter(pattern, "*@", &centerX, &centerY, 20);

    // Clean the pattern from characters we do not want to match
    mapBlockClean(pattern, optCleanChars);

    //
    // Search the maps .. enabled or if none specified, all of them
    //
    for (int nmap = 0; nmap < optNMaps; nmap++)
    {
        MAPInfoCtx *info = &optMaps[nmap];

        for (int oy = 0; oy < info->map->height - height - 1; oy++)
        for (int ox = 0; ox < info->map->width - width - 1; ox++)
        {
            // Check for match
            double accuracy = mapMatchBlock(info->map, pattern, ox, oy, 0, TRUE);
            if (accuracy > 99.5f)
            {
                // Okay, add the match to our list
                MAPMatch *match = &matches[nmatches++];

                match->map = info->locFile.continent;
                match->mx = ox + centerX;
                match->my = oy + centerY;
                match->wx = optWorldXC + info->locFile.x + ox + centerX;
                match->wy = optWorldYC + info->locFile.y + oy + centerY;
                match->accuracy = accuracy;
                match->centered = centerFound;

                if (nmatches >= SET_MAX_MATCHES || nmatches >= maxMatches)
                    goto out;
            }
        }
    }

out:
    mapBlockFree(pattern);

    if (*verr != NULL)
        return;

    if (nmatches > 0)
    {
        // We got some matches, output them as a JSON array
        char *buf = NULL;
        size_t bufLen = 0, bufSize = 0;

        th_strbuf_puts(&buf, &bufSize, &bufLen, "RESULT:[");

        for (int n = 0; n < nmatches; n++)
        {
            MAPMatch *match = &matches[n];
            char *vstr = th_strdup_printf(
                "[%1.2f,%d,\"%s\",%d,%d,%d,%d]%s",
                match->accuracy,
                match->centered,
                match->map,
                match->mx, match->my,
                match->wx, match->wy,
                (n < nmatches - 1) ? "," : "");

            th_strbuf_puts(&buf, &bufSize, &bufLen, vstr);
            th_free(vstr);
        }

        th_strbuf_puts(&buf, &bufSize, &bufLen, "]");
        lws_write(wsi, (unsigned char *) buf, bufLen, LWS_WRITE_TEXT);
    }
    else
        *verr = "No matches found.";
}


int mapLWSCallback(struct lws *wsi,
    enum lws_callback_reasons reason,
    void *user, void *in, size_t len)
{
    (void) user;

    switch (reason)
    {
        case LWS_CALLBACK_CLIENT_WRITEABLE:
            THPRINT(0, "Connection established.\n");
            break;

        case LWS_CALLBACK_RECEIVE:
            {
            char *data = (char *) in;
            unsigned char *udata = (unsigned char *) in;
            char *verr = NULL;

            // Check what the request is about?
            if (len >= 10 && strncmp(data, "MAPSEARCH\n", 10) == 0)
            {
                // A map search query!
                if (len <= 10 + 1)
                    verr = "Invalid search query.";
                else
                    mapPerformSearch(wsi, udata + 10, len - 10, &verr);
            }
            else
            if (len >= 7 && strncmp(data, "GETMAPS", 7) == 0)
            {
                // Client wants a list of available maps
                char *buf = NULL;
                size_t bufLen = 0, bufSize = 0;

                th_strbuf_puts(&buf, &bufSize, &bufLen, "MAPS:[");

                for (int n = 0; n < optNMaps; n++)
                {
                    MAPInfoCtx *info = &optMaps[n];
                    char *vstr = th_strdup_printf(
                        "[\"%s\",%d,%d]%s",
                        info->locFile.continent,
                        info->locFile.x + optWorldXC,
                        info->locFile.y + optWorldYC,
                        (n < optNMaps - 1) ? "," : "");

                    th_strbuf_puts(&buf, &bufSize, &bufLen, vstr);
                    th_free(vstr);
                }

                th_strbuf_puts(&buf, &bufSize, &bufLen, "]");
                lws_write(wsi, (unsigned char *) buf, bufLen, LWS_WRITE_TEXT);
            }
            else
            {
                // Unknown or invalid query
                verr = "Invalid command/search query, not enough data.";
            }

            // Check for errors ..
            if (verr != NULL)
            {
                THERR("%s\n", verr);
                char *vstr = th_strdup_printf("ERROR:%s", verr);
                lws_write(wsi, (unsigned char *) vstr, strlen(vstr), LWS_WRITE_TEXT);
                th_free(vstr);
            }

            // End communication
            verr = "END";
            lws_write(wsi, (unsigned char *) verr, strlen(verr), LWS_WRITE_TEXT);
            lws_close_reason(wsi, LWS_CLOSE_STATUS_NOSTATUS, NULL, 0);
            }
            break;

        default:
            break;
    }

    return 0;
}


static const struct lws_extension mapLWSExtensions[] =
{
    {
       "permessage-deflate",
        lws_extension_callback_pm_deflate,
        "permessage-deflate"
    },
    {
        "deflate-frame",
        lws_extension_callback_pm_deflate,
        "deflate_frame"
    },
    { NULL, NULL, NULL }
};


static const struct lws_protocols mapLWSProtocols[] =
{
    { "default", &mapLWSCallback, 0, 0, 0, NULL },
    { NULL, NULL, 0, 0, 0, NULL }
};


int main(int argc, char *argv[])
{

    // Initialize
    th_init("MapSearch", "Map Search WebSockets server", "0.1", NULL, NULL);
    th_verbosityLevel = 0;

    memset(&optMaps, 0, sizeof(optMaps));
    memset(&optListenTo, 0, sizeof(optListenTo));

    // Parse command line arguments
    BOOL argsOK = th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, 0);

    if (!argsOK)
        return -2;

    if (optNMaps == 0)
    {
        THERR("No maps specified.\n");
        goto exit;
    }

    if (optNListenTo == 0)
    {
        THERR("No listeners specified.\n");
        goto exit;
    }

    // Load maps
    THMSG(1, "Trying to load %d map specs ..\n", optNMaps);
    for (int n = 0; n < optNMaps; n++)
    {
        MAPInfoCtx *info = &optMaps[n];
        FILE *fh;

        THMSG(1, "Map ID '%s', data '%s', locations '%s' @ %d, %d\n",
            info->locFile.continent,
            info->mapFilename,
            info->locFile.filename,
            info->locFile.x, info->locFile.y);

        if ((info->map = mapBlockParseFile(info->mapFilename, FALSE)) == NULL)
        {
            THERR("Could not read map data file '%s'.\n", info->mapFilename);
            goto exit;
        }

        if ((fh = fopen(info->locFile.filename, "rb")) == NULL)
        {
            THERR("Could not open location file '%s' for reading.\n",
                info->locFile.filename);
            goto exit;
        }

        if (!locParseLocStream(fh, &info->locFile, &(info->loc), info->locFile.x, info->locFile.y))
        {
            fclose(fh);
            goto exit;
        }

        fclose(fh);
    }

    // Initialize libwebsockets and create context
    THMSG(1, "Creating libwebsockets context.\n");

    MAPListenerCtx *ctx = optListenTo[0];
    struct lws_context_creation_info info;
    memset(&info, 0, sizeof(info));

    info.port = ctx->port;
    info.iface = ctx->interface;
    info.max_http_header_pool = 16;
    info.options =
        LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
	LWS_SERVER_OPTION_VALIDATE_UTF8 |
	LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
	LWS_SERVER_OPTION_LIBUV; // do we need this?

    info.protocols = mapLWSProtocols;
    info.extensions = mapLWSExtensions;
    info.timeout_secs = 5;
    info.ssl_cipher_list = optSSLCipherList;

    if ((setLWSContext = lws_create_context(&info)) == NULL)
    {
        THERR("libwebsocket initialization failed.\n");
        goto exit;
    }

    // Create listener LWS vhosts ..
    for (int n = 0; n < optNListenTo; n++)
    {
        ctx = optListenTo[n];

        if (ctx->useSSL)
        {
            THMSG(1, "Listen to %s:%d (wss) [cert='%s', key='%s', ca='%s']\n",
                ctx->interface != NULL ? ctx->interface : "*", ctx->port,
                ctx->sslCertFile, ctx->sslKeyFile, ctx->sslCAFile);
        }
        else
        {
            THMSG(1, "Listen to %s:%d (ws)\n",
                ctx->interface != NULL ? ctx->interface : "*", ctx->port);
        }

        info.port = ctx->port;
        info.iface = ctx->interface;
        if ((ctx->vhost = lws_create_vhost(setLWSContext, &info)) == NULL)
        {
            THERR("LWS vhost creation failed!\n");
            goto exit;
        }
    }

    // Set up signal handlers
    THMSG(1, "Setting up signal handlers.\n");
    lws_uv_sigint_cfg(setLWSContext, 1, mapSigHandler);

    THMSG(1, "Waiting for connections...\n");
    if (lws_uv_initloop(setLWSContext, NULL, 0))
    {
        THERR("lws_uv_initloop() failed.\n");
        goto exit;
    }

    // Start running ..
    lws_libuv_run(setLWSContext, 0);

exit:
    // Shutdown sequence
    THMSG(1, "Server shutting down.\n");

    if (setLWSContext != NULL)
        lws_context_destroy(setLWSContext);

    for (int n = 0; n < optNListenTo; n++)
        mapFreeListenCtx(optListenTo[n]);

    for (int n = 0; n < optNMaps; n++)
    {
        MAPInfoCtx *info = &optMaps[n];

        th_free(info->mapFilename);
        th_free(info->locFile.filename);
        th_free(info->locFile.continent);

        mapBlockFree(info->map);
        locFreeMapLocations(&info->loc);
    }

    return 0;
}