Mercurial > hg > batmud > maputils
view mapsearch.c @ 1786:eb98e57d3969
Bring in mapBlockGetEntropy() as we will be using it in the search server.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sun, 29 Oct 2017 04:57:42 +0200 |
parents | 7ec862ed6514 |
children | 513c467f3a87 |
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; /* 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; 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; } 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: { 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 file '%s', map '%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; }