Initial import

This commit is contained in:
Jan-Piet Mens
2015-08-14 18:40:35 +02:00
parent 75da27b5c0
commit a3a518a24d
23 changed files with 4099 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
*.o
ot-recorder
ocat
*.dSYM
.DS_Store
store/

17
Makefile Normal file
View File

@@ -0,0 +1,17 @@
CFLAGS=-Wall -Werror -g
all: ot-recorder #### ot-reader
ot-reader: ot-reader.c json.o utstring.h ghash.o mkpath.o jget.o
cc $(CFLAGS) ot-reader.c -o ot-reader json.o ghash.o mkpath.o jget.o -lhiredis
ot-recorder: ot-recorder.c json.o utarray.h utstring.h geo.o geohash.o mkpath.o file.o safewrite.o base64.o ghash.o config.h udata.h
cc $(CFLAGS) ot-recorder.c -o ot-recorder json.o geo.o geohash.o mkpath.o file.o safewrite.o base64.o ghash.o -lmosquitto -lcurl -lhiredis
geo.o: geo.h geo.c udata.h
geohash.o: geohash.h geohash.c udata.h
file.o: file.h file.c config.h
base64.o: base64.h base64.c
ghash.o: ghash.h ghash.c config.h udata.h
safewrite.o: safewrite.h safewrite.c
jget.o: jget.c jget.h json.h

151
base64.c Executable file
View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the Kungliga Tekniska
* Hgskolan and its contributors.
*
* 4. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
/*RCSID("$Id: base64.c,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $");*/
#endif
#include <stdlib.h>
#include <string.h>
#include "base64.h"
static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int pos(char c)
{
char *p;
for(p = base64; *p; p++)
if(*p == c)
return p - base64;
return -1;
}
int base64_encode(const void *data, int size, char **str)
{
char *s, *p;
int i;
int c;
unsigned char *q;
p = s = (char*)malloc(size*4/3+4);
if (p == NULL)
return -1;
q = (unsigned char*)data;
i=0;
for(i = 0; i < size;){
c=q[i++];
c*=256;
if(i < size)
c+=q[i];
i++;
c*=256;
if(i < size)
c+=q[i];
i++;
p[0]=base64[(c&0x00fc0000) >> 18];
p[1]=base64[(c&0x0003f000) >> 12];
p[2]=base64[(c&0x00000fc0) >> 6];
p[3]=base64[(c&0x0000003f) >> 0];
if(i > size)
p[3]='=';
if(i > size+1)
p[2]='=';
p+=4;
}
*p=0;
*str = s;
return strlen(s);
}
int base64_decode(const char *str, void *data)
{
const char *p;
unsigned char *q;
int c;
int x;
int done = 0;
q=(unsigned char*)data;
for(p=str; *p && !done; p+=4){
x = pos(p[0]);
if(x >= 0)
c = x;
else{
done = 3;
break;
}
c*=64;
x = pos(p[1]);
if(x >= 0)
c += x;
else
return -1;
c*=64;
if(p[2] == '=')
done++;
else{
x = pos(p[2]);
if(x >= 0)
c += x;
else
return -1;
}
c*=64;
if(p[3] == '=')
done++;
else{
if(done)
return -1;
x = pos(p[3]);
if(x >= 0)
c += x;
else
return -1;
}
if(done < 3)
*q++=(c&0x00ff0000)>>16;
if(done < 2)
*q++=(c&0x0000ff00)>>8;
if(done < 1)
*q++=(c&0x000000ff)>>0;
}
return q - (unsigned char*)data;
}

47
base64.h Executable file
View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the Kungliga Tekniska
* Hgskolan and its contributors.
*
* 4. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/* $Id: base64.h,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $ */
#ifndef _BASE64_H_
#define _BASE64_H_
int base64_encode(const void *data, int size, char **str);
int base64_decode(const char *str, void *data);
#endif

3
config.h Normal file
View File

@@ -0,0 +1,3 @@
#define HAVE_REDIS
#define JSONDIR "store"
int mkpath(char *path);

104
file.c Normal file
View File

@@ -0,0 +1,104 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <time.h>
#include "file.h"
static void ut_lower(UT_string *us)
{
char *p;
for (p = utstring_body(us); p && *p; p++) {
if (!isalnum(*p) || isspace(*p))
*p = '-';
else if (isupper(*p))
*p = tolower(*p);
}
}
static void ut_clean(UT_string *us)
{
char *p;
for (p = utstring_body(us); p && *p; p++) {
if (isspace(*p))
*p = '-';
}
}
static const char *yyyymm(time_t t) {
static char buf[] = "YYYY-MM";
strftime(buf, sizeof(buf), "%Y-%m", gmtime(&t));
return(buf);
}
/* Return an open append file pointer to storage for user/device,
creating directories on the fly. If device is NULL, omit it.
*/
FILE *pathn(char *mode, char *prefix, UT_string *user, UT_string *device, char *suffix)
{
static UT_string *path = NULL;
time_t now;
utstring_renew(path);
ut_lower(user);
if (device) {
ut_lower(device);
}
//utstring_printf(path, "%s/%s/%-1.1s/%-2.2s",
// JSONDIR, prefix, utstring_body(user), utstring_body(user));
utstring_printf(path, "%s/%s/%s/%s", JSONDIR, prefix, utstring_body(user), utstring_body(device));
ut_clean(path);
if (mkpath(utstring_body(path)) < 0) {
perror(utstring_body(path));
return (NULL);
}
#if 0
if (device) {
utstring_printf(path, "/%s-%s.%s",
utstring_body(user), utstring_body(device), suffix);
} else {
utstring_printf(path, "/%s.%s",
utstring_body(user), suffix);
}
#endif
time(&now);
utstring_printf(path, "/%s.%s", yyyymm(now), suffix);
ut_clean(path);
return (fopen(utstring_body(path), mode));
}
#if 0
int main(int argc, char **argv)
{
UT_string *user, *dev;
FILE *fp;
utstring_new(user);
utstring_new(dev);
utstring_printf(user, "%s", argv[1]);
utstring_printf(dev, "%s", argv[2]);
fp = pathn(user, dev);
fputs("hello\n", fp);
return 0;
}
#endif

5
file.h Normal file
View File

@@ -0,0 +1,5 @@
#include "config.h"
#include "utstring.h"
FILE *pathn(char *mode, char *prefix, UT_string *user, UT_string *device, char *suffix);

179
geo.c Normal file
View File

@@ -0,0 +1,179 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <curl/curl.h>
#include "utstring.h"
#include "geo.h"
#include "json.h"
#define GURL "http://maps.googleapis.com/maps/api/geocode/json?latlng=%lf,%lf&sensor=false&language=EN"
static CURL *curl;
static size_t writemem(void *contents, size_t size, size_t nmemb, void *userp)
{
UT_string *cbuf = (UT_string *)userp;
size_t realsize = size * nmemb;
utstring_bincpy(cbuf, contents, realsize);
return (realsize);
}
static int goog_decode(UT_string *geodata, UT_string *addr, UT_string *cc)
{
JsonNode *json, *results, *address, *ac, *zeroth;
/*
* We are parsing this. I want the formatted_address in `addr' and
* the country code short_name in `cc'
*
* {
* "results" : [
* {
* "address_components" : [
* {
* "long_name" : "New Zealand",
* "short_name" : "NZ",
* "types" : [ "country", "political" ]
* }, ...
* ],
* "formatted_address" : "59 Example Street, Christchurch 8081, New Zealand",
*/
if ((json = json_decode(utstring_body(geodata))) == NULL) {
return (0);
}
if ((results = json_find_member(json, "results")) != NULL) {
if ((zeroth = json_find_element(results, 0)) != NULL) {
address = json_find_member(zeroth, "formatted_address");
if ((address != NULL) && (address->tag == JSON_STRING)) {
utstring_printf(addr, "%s", address->string_);
}
}
/* Country */
if ((ac = json_find_member(zeroth, "address_components")) != NULL) {
JsonNode *comp, *j;
int have_cc = 0;
json_foreach(comp, ac) {
JsonNode *a;
if ((j = json_find_member(comp, "types")) != NULL) {
json_foreach(a, j) {
if ((a->tag == JSON_STRING) && (strcmp(a->string_, "country") == 0)) {
JsonNode *c;
if ((c = json_find_member(comp, "short_name")) != NULL) {
utstring_printf(cc, "%s", c->string_);
have_cc = 1;
break;
}
}
}
}
if (have_cc)
break;
}
}
}
json_delete(json);
return (1);
}
JsonNode *revgeo(double lat, double lon, UT_string *addr, UT_string *cc)
{
static UT_string *url;
static UT_string *cbuf; /* Buffer for curl GET */
CURLcode res;
int rc;
JsonNode *geo;
time_t now;
if ((geo = json_mkobject()) == NULL) {
return (NULL);
}
if (lat == 0.0L && lon == 0.0L) {
utstring_printf(addr, "Unknown (%lf,%lf)", lat, lon);
utstring_printf(cc, "__");
return (geo);
}
utstring_renew(url);
utstring_renew(cbuf);
utstring_printf(url, GURL, lat, lon);
curl_easy_setopt(curl, CURLOPT_URL, utstring_body(url));
curl_easy_setopt(curl, CURLOPT_USERAGENT, "ot-recorder-agent/1.0");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writemem);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)cbuf);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
utstring_printf(addr, "revgeo failed for (%lf,%lf)", lat, lon);
utstring_printf(cc, "__");
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
json_delete(geo);
return (NULL);
}
// printf("%s\n", utstring_body(url));
if (!(rc = goog_decode(cbuf, addr, cc))) {
utstring_printf(addr, "Unknown (%lf,%lf)", lat, lon);
utstring_printf(cc, "__");
}
fprintf(stderr, "revgeo returns %d: %s\n", rc, utstring_body(addr));
time(&now);
json_append_member(geo, "cc", json_mkstring(utstring_body(cc)));
json_append_member(geo, "addr", json_mkstring(utstring_body(addr)));
json_append_member(geo, "tst", json_mknumber((double)now));
return (geo);
}
void revgeo_init()
{
curl = curl_easy_init();
}
#if 0
int main()
{
double lat = 52.034403, lon = 8.476544;
double clat = 51.197500, clon = 6.699179;
static UT_string *location;
int rc;
curl = curl_easy_init();
utstring_renew(location);
rc = revgeo(lat, lon, location);
if (rc == 1) {
puts(utstring_body(location));
}
utstring_renew(location);
rc = revgeo(clat, clon, location);
if (rc == 1) {
puts(utstring_body(location));
}
curl_easy_cleanup(curl);
return (0);
}
#endif

4
geo.h Normal file
View File

@@ -0,0 +1,4 @@
#include "json.h"
JsonNode *revgeo(double lat, double lon, UT_string *addr, UT_string *cc);
void revgeo_init();

293
geohash.c Normal file
View File

@@ -0,0 +1,293 @@
/*
* geohash.c
* libgeohash
*
* Created by Derek Smith on 10/6/09.
* Copyright (c) 2010, SimpleGeo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer. Redistributions in binary form must
* reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the SimpleGeo nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "geohash.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define MAX_LAT 90.0
#define MIN_LAT -90.0
#define MAX_LONG 180.0
#define MIN_LONG -180.0
#define NORTH 0
#define EAST 1
#define SOUTH 2
#define WEST 3
#define LENGTH_OF_DEGREE 111100 // meters
typedef struct IntervalStruct {
double high;
double low;
} Interval;
/* Normal 32 characer map used for geohashing */
static char char_map[32] = "0123456789bcdefghjkmnpqrstuvwxyz";
/*
* The follow character maps were created by Dave Troy and used in his Javascript Geohashing
* library. http://github.com/davetroy/geohash-js
*/
static char *even_neighbors[] = {"p0r21436x8zb9dcf5h7kjnmqesgutwvy",
"bc01fg45238967deuvhjyznpkmstqrwx",
"14365h7k9dcfesgujnmqp0r2twvyx8zb",
"238967debc01fg45kmstqrwxuvhjyznp"
};
static char *odd_neighbors[] = {"bc01fg45238967deuvhjyznpkmstqrwx",
"p0r21436x8zb9dcf5h7kjnmqesgutwvy",
"238967debc01fg45kmstqrwxuvhjyznp",
"14365h7k9dcfesgujnmqp0r2twvyx8zb"
};
static char *even_borders[] = {"prxz", "bcfguvyz", "028b", "0145hjnp"};
static char *odd_borders[] = {"bcfguvyz", "prxz", "0145hjnp", "028b"};
unsigned int index_for_char(char c, char *string) {
int index = -1;
int string_amount = strlen(string);
int i;
for(i = 0; i < string_amount; i++) {
if(c == string[i]) {
index = i;
break;
}
}
return index;
}
char* get_neighbor(char *hash, int direction) {
int hash_length = strlen(hash);
char last_char = hash[hash_length - 1];
int is_odd = hash_length % 2;
char **border = is_odd ? odd_borders : even_borders;
char **neighbor = is_odd ? odd_neighbors : even_neighbors;
char *base = malloc(sizeof(char) * 1);
base[0] = '\0';
strncat(base, hash, hash_length - 1);
if(index_for_char(last_char, border[direction]) != -1)
base = get_neighbor(base, direction);
int neighbor_index = index_for_char(last_char, neighbor[direction]);
last_char = char_map[neighbor_index];
char *last_hash = malloc(sizeof(char) * 2);
last_hash[0] = last_char;
last_hash[1] = '\0';
strcat(base, last_hash);
free(last_hash);
return base;
}
char* geohash_encode(double lat, double lng, int precision) {
if(precision < 1 || precision > 12)
precision = 6;
char* hash = NULL;
if(lat <= 90.0 && lat >= -90.0 && lng <= 180.0 && lng >= -180.0) {
hash = (char*)malloc(sizeof(char) * (precision + 1));
hash[precision] = '\0';
precision *= 5.0;
Interval lat_interval = {MAX_LAT, MIN_LAT};
Interval lng_interval = {MAX_LONG, MIN_LONG};
Interval *interval;
double coord, mid;
int is_even = 1;
unsigned int hashChar = 0;
int i;
for(i = 1; i <= precision; i++) {
if(is_even) {
interval = &lng_interval;
coord = lng;
} else {
interval = &lat_interval;
coord = lat;
}
mid = (interval->low + interval->high) / 2.0;
hashChar = hashChar << 1;
if(coord > mid) {
interval->low = mid;
hashChar |= 0x01;
} else
interval->high = mid;
if(!(i % 5)) {
hash[(i - 1) / 5] = char_map[hashChar];
hashChar = 0;
}
is_even = !is_even;
}
}
return hash;
}
GeoCoord geohash_decode(char *hash) {
GeoCoord coordinate = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
if(hash) {
int char_amount = strlen(hash);
if(char_amount) {
unsigned int char_mapIndex;
Interval lat_interval = {MAX_LAT, MIN_LAT};
Interval lng_interval = {MAX_LONG, MIN_LONG};
Interval *interval;
int is_even = 1;
double delta;
int i, j;
for(i = 0; i < char_amount; i++) {
char_mapIndex = index_for_char(hash[i], (char*)char_map);
//JPM always false if(char_mapIndex < 0)
//JPM break;
// Interpret the last 5 bits of the integer
for(j = 0; j < 5; j++) {
interval = is_even ? &lng_interval : &lat_interval;
delta = (interval->high - interval->low) / 2.0;
if((char_mapIndex << j) & 0x0010)
interval->low += delta;
else
interval->high -= delta;
is_even = !is_even;
}
}
coordinate.latitude = lat_interval.high - ((lat_interval.high - lat_interval.low) / 2.0);
coordinate.longitude = lng_interval.high - ((lng_interval.high - lng_interval.low) / 2.0);
coordinate.north = lat_interval.high;
coordinate.east = lng_interval.high;
coordinate.south = lat_interval.low;
coordinate.west = lng_interval.low;
}
}
return coordinate;
}
char** geohash_neighbors(char *hash) {
char** neighbors = NULL;
if(hash) {
// N, NE, E, SE, S, SW, W, NW
neighbors = (char**)malloc(sizeof(char*) * 8);
neighbors[0] = get_neighbor(hash, NORTH);
neighbors[1] = get_neighbor(neighbors[0], EAST);
neighbors[2] = get_neighbor(hash, EAST);
neighbors[3] = get_neighbor(neighbors[2], SOUTH);
neighbors[4] = get_neighbor(hash, SOUTH);
neighbors[5] = get_neighbor(neighbors[4], WEST);
neighbors[6] = get_neighbor(hash, WEST);
neighbors[7] = get_neighbor(neighbors[6], NORTH);
}
return neighbors;
}
GeoBoxDimension geohash_dimensions_for_precision(int precision) {
GeoBoxDimension dimensions = {0.0, 0.0};
if(precision > 0) {
int lat_times_to_cut = precision * 5 / 2;
int lng_times_to_cut = precision * 5 / 2 + (precision % 2 ? 1 : 0);
double width = 360.0;
double height = 180.0;
int i;
for(i = 0; i < lat_times_to_cut; i++)
height /= 2.0;
for(i = 0; i < lng_times_to_cut; i++)
width /= 2.0;
dimensions.width = width;
dimensions.height = height;
}
return dimensions;
}

77
geohash.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* geohash.h
* libgeohash
*
* Created by Derek Smith on 10/6/09.
* Copyright (c) 2010, SimpleGeo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer. Redistributions in binary form must
* reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the SimpleGeo nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Metric in meters
typedef struct GeoBoxDimensionStruct {
double height;
double width;
} GeoBoxDimension;
typedef struct GeoCoordStruct {
double latitude;
double longitude;
double north;
double east;
double south;
double west;
GeoBoxDimension dimension;
} GeoCoord;
/*
* Creates a the hash at the specified precision. If precision is set to 0.
* or less than it defaults to 12.
*/
extern char* geohash_encode(double lat, double lng, int precision);
/*
* Returns the latitude and longitude used to create the hash along with
* the bounding box for the encoded coordinate.
*/
extern GeoCoord geohash_decode(char* hash);
/*
* Return an array of geohashes that represent the neighbors of the passed
* in value. The neighbors are indexed as followed:
*
* N, NE, E, SE, S, SW, W, NW
* 0, 1, 2, 3, 4, 5, 6, 7
*/
extern char** geohash_neighbors(char* hash);
/*
* Returns the width and height of a precision value.
*/
extern GeoBoxDimension geohash_dimensions_for_precision(int precision);

182
ghash.c Normal file
View File

@@ -0,0 +1,182 @@
#include <stdlib.h>
#include <unistd.h>
#include "ghash.h"
void redis_ping(redisContext **redis)
{
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
redisReply *r;
int i = 0;
do {
if ((r = redisCommand(*redis,"PING")) != NULL) {
// printf("PING: %s\n", r->str);
freeReplyObject(r);
return;
}
fprintf(stderr, "REDIS: %d %s\n", (*redis)->err, (*redis)->errstr);
*redis = redisConnectWithTimeout("localhost", 6379, timeout);
fprintf(stderr, "Reconnecting to Redis...\n");
sleep(5);
} while (i++ < 10);
}
void ghash_store_redis(redisContext **redis, char *ghash, char *addr, char *cc)
{
redisReply *r;
redis_ping(redis);
r = redisCommand(*redis, "HMSET ghash:%s cc %s addr %s", ghash, cc, addr);
if (r) /* FIXME */
return;
}
void last_storeredis(redisContext **redis, char *username, char *device, char *jsonstring)
{
redisReply *r;
redis_ping(redis);
r = redisCommand(*redis, "SET lastpos:%s-%s %s", username, device, jsonstring);
if (r) /* FIXME */
return;
}
int ghash_get_redis_cache(redisContext **redis, char *ghash, UT_string *addr, UT_string *cc)
{
redisReply *reply;
int found = FALSE;
redis_ping(redis);
reply = redisCommand(*redis, "HGETALL ghash:%s", ghash);
if (reply == NULL) {
fprintf(stderr, "REDIS: %d %s\n", (*redis)->err, (*redis)->errstr);
return (FALSE);
}
if ( reply->type == REDIS_REPLY_ERROR ) {
fprintf(stderr, "Error: %s\n", reply->str );
return (FALSE);
}
else if ( reply->type != REDIS_REPLY_ARRAY )
printf( "Unexpected type: %d\n", reply->type );
if (reply->type == REDIS_REPLY_ARRAY) {
int i;
char *key, *val;
if (reply->elements >= 1) {
for (i = 0; i < (reply->elements - 1); i += 2) {
key = reply->element[i]->str;
val = reply->element[i+1]->str;
if (!strcmp(key, "addr"))
utstring_printf(addr, "%s", val);
else if (!strcmp(key, "cc"))
utstring_printf(cc, "%s", val);
}
found = TRUE;
}
}
freeReplyObject(reply);
return (found);
}
int ghash_readcache(struct udata *ud, char *ghash, UT_string *addr, UT_string *cc)
{
int cached = FALSE;
char gfile[BUFSIZ];
FILE *fp;
/* FIXME */
#ifdef HAVE_REDIS
if (ud->useredis) {
if (ghash_get_redis_cache( &ud->redis, ghash, addr, cc) == TRUE) {
return (TRUE);
}
}
#endif
if (ud->usefiles) {
/* if ghash file is available, read cc:addr into that */
snprintf(gfile, BUFSIZ, "%s/ghash/%s.json", JSONDIR, ghash);
fprintf(stderr, "Reading GhashCache from %s\n", gfile);
if ((fp = fopen(gfile, "r")) != NULL) {
char buf[BUFSIZ];
/* FIXME: read JSON */
if (fgets(buf, sizeof(buf), fp) != NULL) {
JsonNode *json, *j;
if ((json = json_decode(buf)) == NULL) {
puts(" FIXME: can't decode JSON");
} else {
if ((j = json_find_member(json, "cc")) != NULL) {
if (j->tag == JSON_STRING) {
utstring_printf(cc, "%s", j->string_);
}
}
if ((j = json_find_member(json, "addr")) != NULL) {
if (j->tag == JSON_STRING) {
utstring_printf(addr, "%s", j->string_);
}
}
json_delete(json);
puts("**** GOTCHA");
cached = TRUE;
}
fclose(fp);
}
} else {
fprintf(stderr, "Not cached: ghash: %s\n", gfile);
}
}
return (cached);
}
void ghash_storecache(struct udata *ud, JsonNode *geo, char *ghash, char *addr, char *cc)
{
char gfile[BUFSIZ];
FILE *fp;
#ifdef HAVE_REDIS
if (ud->useredis) {
ghash_store_redis(&ud->redis, ghash, addr, cc);
}
#endif
if (ud->usefiles) {
snprintf(gfile, BUFSIZ, "%s/ghash", JSONDIR);
if (mkpath(gfile) < 0) {
perror(gfile);
} else {
char *js;
/* I am storing the ghash *in* the JSON purpose */
json_append_member(geo, "cc", json_mkstring(ghash));
if ((js = json_stringify(geo, NULL)) != NULL) {
snprintf(gfile, BUFSIZ, "%s/ghash/%s.json", JSONDIR, ghash);
if ((fp = fopen(gfile, "w")) != NULL) {
fprintf(fp, "%s\n", js);
fclose(fp);
}
free(js);
}
}
}
}

19
ghash.h Normal file
View File

@@ -0,0 +1,19 @@
#include "config.h"
#include "udata.h"
#include <hiredis/hiredis.h>
#include "geohash.h"
#include "utstring.h"
#include "json.h"
#ifndef TRUE
# define TRUE 1
# define FALSE 0
#endif
void redis_ping(redisContext **redis);
// void ghash_store( redisContext **redis, char *ghash, char *addr, char *cc);
// int ghash_cached( redisContext **redis, char *ghash, UT_string *addr, UT_string *cc);
void last_storeredis(redisContext **redis, char *username, char *device, char *jsonstring);
int ghash_readcache(struct udata *ud, char *ghash, UT_string *addr, UT_string *cc);
void ghash_storecache(struct udata *ud, JsonNode *geo, char *ghash, char *addr, char *cc);

3
jget.h Normal file
View File

@@ -0,0 +1,3 @@
#include "json.h"
JsonNode *jget(JsonNode *json, char *args);

1381
json.c Normal file

File diff suppressed because it is too large Load Diff

117
json.h Normal file
View File

@@ -0,0 +1,117 @@
/*
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef CCAN_JSON_H
#define CCAN_JSON_H
#include <stdbool.h>
#include <stddef.h>
typedef enum {
JSON_NULL,
JSON_BOOL,
JSON_STRING,
JSON_NUMBER,
JSON_ARRAY,
JSON_OBJECT,
} JsonTag;
typedef struct JsonNode JsonNode;
struct JsonNode
{
/* only if parent is an object or array (NULL otherwise) */
JsonNode *parent;
JsonNode *prev, *next;
/* only if parent is an object (NULL otherwise) */
char *key; /* Must be valid UTF-8. */
JsonTag tag;
union {
/* JSON_BOOL */
bool bool_;
/* JSON_STRING */
char *string_; /* Must be valid UTF-8. */
/* JSON_NUMBER */
double number_;
/* JSON_ARRAY */
/* JSON_OBJECT */
struct {
JsonNode *head, *tail;
} children;
};
};
/*** Encoding, decoding, and validation ***/
JsonNode *json_decode (const char *json);
char *json_encode (const JsonNode *node);
char *json_encode_string (const char *str);
char *json_stringify (const JsonNode *node, const char *space);
void json_delete (JsonNode *node);
bool json_validate (const char *json);
/*** Lookup and traversal ***/
JsonNode *json_find_element (JsonNode *array, int index);
JsonNode *json_find_member (JsonNode *object, const char *key);
JsonNode *json_first_child (const JsonNode *node);
#define json_foreach(i, object_or_array) \
for ((i) = json_first_child(object_or_array); \
(i) != NULL; \
(i) = (i)->next)
/*** Construction and manipulation ***/
JsonNode *json_mknull(void);
JsonNode *json_mkbool(bool b);
JsonNode *json_mkstring(const char *s);
JsonNode *json_mknumber(double n);
JsonNode *json_mkarray(void);
JsonNode *json_mkobject(void);
void json_append_element(JsonNode *array, JsonNode *element);
void json_prepend_element(JsonNode *array, JsonNode *element);
void json_append_member(JsonNode *object, const char *key, JsonNode *value);
void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
void json_remove_from_parent(JsonNode *node);
/*** Debugging ***/
/*
* Look for structure and encoding problems in a JsonNode or its descendents.
*
* If a problem is detected, return false, writing a description of the problem
* to errmsg (unless errmsg is NULL).
*/
bool json_check(const JsonNode *node, char errmsg[256]);
#endif

74
mkpath.c Normal file
View File

@@ -0,0 +1,74 @@
/* $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $ */
/*
* Copyright (c) 1983, 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <string.h>
/* Code taken directly from mkdir(1).
* mkpath -- create directories.
* path - path
*/
int
mkpath(char *path)
{
struct stat sb;
char *slash;
int done = 0;
slash = path;
while (!done) {
slash += strspn(slash, "/");
slash += strcspn(slash, "/");
done = (*slash == '\0');
*slash = '\0';
if (stat(path, &sb)) {
if (errno != ENOENT || (mkdir(path, 0777) &&
errno != EEXIST)) {
warn("%s", path);
return (-1);
}
} else if (!S_ISDIR(sb.st_mode)) {
warnx("%s: %s", path, strerror(ENOTDIR));
return (-1);
}
*slash = '/';
}
return (0);
}

735
ot-recorder.c Normal file
View File

@@ -0,0 +1,735 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <mosquitto.h>
#include <getopt.h>
#include <time.h>
#include "json.h"
#include <sys/utsname.h>
#include "utstring.h"
#include "utarray.h"
#include "geo.h"
#include "ghash.h"
#include "config.h"
#include "file.h"
#include "safewrite.h"
#include "base64.h"
#ifndef TRUE
# define TRUE (1)
# define FALSE (0)
#endif
#define SSL_VERIFY_PEER (1)
#define SSL_VERIFY_NONE (0)
/*
* 1 => 5,009.4km x 4,992.6km
* 2 => 1,252.3km x 624.1km
* 3 => 156.5km x 156km
* 4 => 39.1km x 19.5km
* 5 => 4.9km x 4.9km
* 6 => 1.2km x 609.4m
* 7 => 152.9m x 152.4m
* 8 => 38.2m x 19m
* 9 => 4.8m x 4.8m
* 10 => 1.2m x 59.5cm
*/
#define GEOHASH_PREC (7)
#define TOPIC_PARTS (4) /* owntracks/user/device/info */
#define TOPIC_SUFFIX "info"
static int run = 1;
double number(JsonNode *j, char *element)
{
JsonNode *m;
if ((m = json_find_member(j, element)) != NULL) {
if (m->tag == JSON_NUMBER) {
return (m->number_);
} else if (m->tag == JSON_STRING) {
return (atof(m->string_));
}
}
return (-7.0L);
}
JsonNode *extract(struct udata *ud, char *payload, char *tid, char *t, double *lat, double *lon, long *tst)
{
JsonNode *json, *j;
*tid = *t = 0;
*lat = *lon = -1.0L;
if ((json = json_decode(payload)) == NULL)
return (NULL);
if (ud->skipdemo && (json_find_member(json, "_demo") != NULL)) {
json_delete(json);
return (NULL);
}
if ((j = json_find_member(json, "_type")) == NULL) {
json_delete(json);
return (NULL);
}
if ((j->tag != JSON_STRING) || (strcmp(j->string_, "location") != 0)) {
json_delete(json);
return (NULL);
}
if ((j = json_find_member(json, "tid")) != NULL) {
if (j->tag == JSON_STRING) {
// printf("I got: [%s]\n", m->string_);
strcpy(tid, j->string_);
}
}
if ((j = json_find_member(json, "t")) != NULL) {
if (j && j->tag == JSON_STRING) {
strcpy(t, j->string_);
}
}
*tst = time(NULL);
if ((j = json_find_member(json, "tst")) != NULL) {
if (j && j->tag == JSON_STRING) {
*tst = strtoul(j->string_, NULL, 10);
} else {
*tst = (unsigned long)j->number_;
}
}
*lat = number(json, "lat");
*lon = number(json, "lon");
return (json);
}
static const char *isotime(time_t t) {
static char buf[] = "YYYY-MM-DDTHH:MM:SSZ";
strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&t));
return(buf);
}
static const char *ltime(time_t t) {
static char buf[] = "HH:MM:SS";
strftime(buf, sizeof(buf), "%T", localtime(&t));
return(buf);
}
void do_info(void *userdata, UT_string *username, UT_string *device, char *payload)
{
struct udata *ud = (struct udata *)userdata;
JsonNode *json, *j;
static UT_string *name = NULL, *face = NULL;
#ifdef HAVE_REDIS
redisReply *r;
#endif
FILE *fp;
char *img;
utstring_renew(name);
utstring_renew(face);
if ((json = json_decode(payload)) == NULL) {
puts("Can't decode INFO payload");
return;
}
if (ud->skipdemo && (json_find_member(json, "_demo") != NULL)) {
goto cleanup;
}
if ((j = json_find_member(json, "_type")) == NULL) {
goto cleanup;
}
if ((j->tag != JSON_STRING) || (strcmp(j->string_, "card") != 0)) {
goto cleanup;
}
if (ud->usefiles) {
/* I know the payload is valid JSON: write card */
if ((fp = pathn("wb", "cards", username, NULL, "json")) != NULL) {
fprintf(fp, "%s\n", payload);
fclose(fp);
}
}
if ((j = json_find_member(json, "name")) != NULL) {
if (j->tag == JSON_STRING) {
// printf("I got: [%s]\n", j->string_);
utstring_printf(name, "%s", j->string_);
}
}
if ((j = json_find_member(json, "face")) != NULL) {
if (j->tag == JSON_STRING) {
// printf("I got: [%s]\n", j->string_);
utstring_printf(face, "%s", j->string_);
}
}
fprintf(stderr, "* CARD: %s-%s %s\n", utstring_body(username), utstring_body(device), utstring_body(name));
#ifdef HAVE_REDIS
if (ud->useredis) {
redis_ping(&ud->redis);
r = redisCommand(ud->redis, "HMSET card:%s name %s face %s", utstring_body(username), utstring_body(name), utstring_body(face));
if (r) { ; } /* FIXME */
}
#endif
/* We have a base64-encoded "face". Decode it and store binary image */
if ((img = malloc(utstring_len(face))) != NULL) {
int imglen;
if ((imglen = base64_decode(utstring_body(face), img)) > 0) {
if (ud->usefiles) {
if ((fp = pathn("wb", "photos", username, NULL, "png")) != NULL) {
fwrite(img, sizeof(char), imglen, fp);
fclose(fp);
}
}
#ifdef HAVE_REDIS
if (ud->useredis) {
/* Add photo (binary) to Redis as photo:username */
redis_ping(&ud->redis);
r = redisCommand(ud->redis, "SET photo:%s %b", utstring_body(username), img, imglen);
}
#endif
}
free(img);
}
cleanup:
json_delete(json);
}
void do_msg(void *userdata, UT_string *username, UT_string *device, char *payload)
{
struct udata *ud = (struct udata *)userdata;
JsonNode *json, *j;
FILE *fp;
if ((json = json_decode(payload)) == NULL) {
puts("Can't decode INFO payload");
return;
}
if (ud->skipdemo && (json_find_member(json, "_demo") != NULL)) {
goto cleanup;
}
if ((j = json_find_member(json, "_type")) == NULL) {
goto cleanup;
}
if ((j->tag != JSON_STRING) || (strcmp(j->string_, "msg") != 0)) {
goto cleanup;
}
if (ud->usefiles) {
/* I know the payload is valid JSON: write message */
if ((fp = pathn("ab", "msg", username, NULL, "json")) != NULL) {
fprintf(fp, "%s\n", payload);
fclose(fp);
}
}
fprintf(stderr, "* MSG: %s-%s\n", utstring_body(username), utstring_body(device));
cleanup:
json_delete(json);
}
void republish(struct mosquitto *mosq, struct udata *userdata, char *username, char *topic, double lat, double lon, char *cc, char *addr, long tst, char *t)
{
struct udata *ud = (struct udata *)userdata;
JsonNode *json;
static UT_string *newtopic = NULL;
char *payload;
if (ud->pubprefix == NULL)
return;
if ((json = json_mkobject()) == NULL) {
return;
}
utstring_renew(newtopic);
utstring_printf(newtopic, "%s/%s", ud->pubprefix, topic);
json_append_member(json, "username", json_mkstring(username));
json_append_member(json, "topic", json_mkstring(topic));
json_append_member(json, "cc", json_mkstring(cc));
json_append_member(json, "addr", json_mkstring(addr));
json_append_member(json, "t", json_mkstring(t));
json_append_member(json, "tst", json_mknumber(tst));
json_append_member(json, "lat", json_mknumber(lat));
json_append_member(json, "lon", json_mknumber(lon));
if ((payload = json_stringify(json, NULL)) != NULL) {
mosquitto_publish(mosq, NULL, utstring_body(newtopic),
strlen(payload), payload, 1, true);
fprintf(stderr, "%s %s\n", utstring_body(newtopic), payload);
free(payload);
}
json_delete(json);
}
/*
* Decode OwnTracks CSV and return a new JsonNode to a JSON object.
*/
#define MILL 1000000.0
JsonNode *csv(char *payload, char *tid, char *t, double *lat, double *lon, long *tst)
{
JsonNode *json = NULL;
double dist = 0;
char tmptst[40];
double vel, trip, alt, cog;
if (sscanf(payload, "%[^,],%[^,],%[^,],%lf,%lf,%lf,%lf,%lf,%lf,%lf", tid, tmptst, t, lat, lon, &cog, &vel, &alt, &dist, &trip) != 10) {
fprintf(stderr, "**** payload not CSV: %s\n", payload);
return (NULL);
}
*lat /= MILL;
*lon /= MILL;
cog *= 10;
alt *= 10;
trip *= 1000;
*tst = strtoul(tmptst, NULL, 16);
sprintf(tmptst, "%ld", *tst);
json = json_mkobject();
json_append_member(json, "_type", json_mkstring("location"));
json_append_member(json, "t", json_mkstring(t));
json_append_member(json, "tid", json_mkstring(tid));
json_append_member(json, "tst", json_mkstring(tmptst));
json_append_member(json, "lat", json_mknumber(*lat));
json_append_member(json, "lon", json_mknumber(*lon));
json_append_member(json, "cog", json_mknumber(cog));
json_append_member(json, "vel", json_mknumber(vel));
json_append_member(json, "alt", json_mknumber(alt));
json_append_member(json, "dist", json_mknumber(dist));
json_append_member(json, "trip", json_mknumber(trip));
json_append_member(json, "csv", json_mkbool(1));
return (json);
}
#define RECFORMAT "%s\t%-18s\t%s\n"
void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *m)
{
JsonNode *json;
char tid[BUFSIZ], t[BUFSIZ], *p;
double lat, lon;
long tst;
struct udata *ud = (struct udata *)userdata;
FILE *fp;
char **topics;
int count = 0, cached;
static UT_string *basetopic = NULL, *username = NULL, *device = NULL, *addr = NULL, *cc = NULL, *ghash = NULL, *ts = NULL;
static UT_string *rest = NULL;
char *jsonstring;
time_t now;
/*
* mosquitto_message->
* int mid;
* char *topic;
* void *payload;
* int payloadlen;
* int qos;
* bool retain;
*/
if (m->payloadlen == 0) {
return;
}
printf("%s %s\n", m->topic, m->payload); fflush(stdout);
utstring_renew(ts);
utstring_renew(basetopic);
utstring_renew(username);
utstring_renew(device);
if (mosquitto_sub_topic_tokenise(m->topic, &topics, &count) != MOSQ_ERR_SUCCESS) {
return;
}
time(&now);
/* FIXME: handle null leading topic `/` */
utstring_printf(basetopic, "%s/%s/%s", topics[0], topics[1], topics[2]);
utstring_printf(username, "%s", topics[1]);
utstring_printf(device, "%s", topics[2]);
if ((count == TOPIC_PARTS) && (strcmp(topics[count-1], TOPIC_SUFFIX) == 0)) {
do_info(ud, username, device, m->payload);
}
/* owntracks/user/device/msg */
if ((count == TOPIC_PARTS) && (strcmp(topics[count-1], "msg") == 0)) {
if (m->retain == FALSE || ud->ignoreretained == FALSE) {
do_msg(ud, username, device, m->payload);
}
}
if (count != 3) {
/*
* Not a normal location publish. Build up a string consisting of the remaining
* topic parts, i.e. whatever is after base topic, and record with whatever
* (hopefully non-binary) payload we got.
*/
int j;
utstring_renew(rest);
for (j = 3; j < count; j++) {
utstring_printf(rest, "%s%c", topics[j], (j < count - 1) ? '/' : ' ');
}
if (ud->usefiles) {
if ((fp = pathn("a", "rec", username, device, "rec")) != NULL) {
/* TODO: check payload for binary characters and hex-dump? */
fprintf(fp, RECFORMAT, isotime(now), utstring_body(rest), m->payload);
fclose(fp);
}
}
mosquitto_sub_topic_tokens_free(&topics, count);
return;
}
mosquitto_sub_topic_tokens_free(&topics, count);
if (m->retain == TRUE && ud->ignoreretained) {
return;
}
/* Decode JSON payload and ensure _type: location */
json = extract(ud, m->payload, tid, t, &lat, &lon, &tst);
if (json == NULL) {
/* Is it OwnTracks Greenwich CSV? */
if ((json = csv(m->payload, tid, t, &lat, &lon, &tst)) == NULL) {
return;
}
fprintf(stderr, "+++++ %s\n", json_stringify(json, NULL));
}
#if 0
if (*t && (!strcmp(t, "p") || !strcmp(t, "b"))) {
// fprintf(stderr, "Ignore `t:%s' for %s\n", t, m->topic);
json_delete(json);
return;
}
#endif
utstring_renew(ghash);
p = geohash_encode(lat, lon, GEOHASH_PREC);
if (p != NULL) {
utstring_printf(ghash, "%s", p);
free(p);
}
utstring_renew(addr);
utstring_renew(cc);
cached = FALSE;
/* FIXME: */
cached = ghash_readcache(ud, utstring_body(ghash), addr, cc);
if (!cached) {
JsonNode *geo;
if ((geo = revgeo(lat, lon, addr, cc)) != NULL) {
fprintf(stderr, "REVGEO: %s\n", utstring_body(addr));
ghash_storecache(ud, geo, utstring_body(ghash), utstring_body(addr), utstring_body(cc));
json_delete(geo);
}
}
/* FIXME: check if this is a location type */
/*
* We have exactly three topic parts (owntracks/user/device), and valid JSON.
* Add a few bits to the JSON, and record it on a per-user/device basis.
*/
json_append_member(json, "ghash", json_mkstring(utstring_body(ghash)));
if ((jsonstring = json_stringify(json, NULL)) != NULL) {
#ifdef HAVE_REDIS
if (ud->useredis) {
/* add last to Redis as "lastpos:username-device" */
last_storeredis(&ud->redis, utstring_body(username), utstring_body(device), jsonstring);
}
#endif
if (ud->usefiles) {
if ((fp = pathn("a", "rec", username, device, "rec")) != NULL) {
// fprintf(fp, "%s\n", jsonstring);
fprintf(fp, RECFORMAT, isotime(now), "*", jsonstring);
/* Now safewrite the last location */
utstring_printf(ts, "%s/last", JSONDIR);
if (mkpath(utstring_body(ts)) < 0) {
perror(utstring_body(ts));
}
utstring_printf(ts, "/%s-%s.json",
utstring_body(username), utstring_body(device));
safewrite(utstring_body(ts), jsonstring);
fclose(fp);
}
}
free(jsonstring);
}
/* publish */
republish(mosq, ud, utstring_body(username), m->topic, lat, lon, utstring_body(cc), utstring_body(addr), tst, t);
fprintf(stderr, "%c %s %-7s %s [%s] %s\n",
(cached) ? '*' : '-',
utstring_body(ghash), m->topic, ltime(tst), utstring_body(cc), utstring_body(addr));
json_delete(json);
}
void on_connect(struct mosquitto *mosq, void *userdata, int rc)
{
struct udata *ud = (struct udata *)userdata;
int mid;
char **m = NULL;
while ((m = (char **)utarray_next(ud->topics, m))) {
fprintf(stderr, "Subscribing to %s\n", *m);
mosquitto_subscribe(mosq, &mid, *m, 0);
}
}
void on_disconnect(struct mosquitto *mosq, void *userdata, int reason)
{
#ifdef HAVE_REDIS
struct udata *ud = (struct udata *)userdata;
#endif
fprintf(stderr, "Disconnected. Reason: %d [%s]\n", reason, mosquitto_strerror(reason));
if (reason == 0) { // client wish
#ifdef HAVE_REDIS
redisFree(ud->redis);
#endif
//SQL sqlite3_finalize(ud->raw);
//SQL sqlite3_finalize(ud->lastloc);
//SQL sqlite3_close(ud->db);
run = FALSE;
}
}
static void catcher(int sig)
{
fprintf(stderr, "Going down on signal %d\n", sig);
exit(1);
}
void usage(char *prog)
{
fprintf(stderr, "Usage: %s [-D] [-F] [-N] [-P prefix] [-R] topic [topic...]\n", prog);
exit(2);
}
int main(int argc, char **argv)
{
struct mosquitto *mosq = NULL;
char err[1024], *p, *username, *password, *cafile;
char *hostname = "localhost";
int port = 1883;
int rc, i, ch;
static struct udata udata, *ud = &udata;
struct utsname uts;
UT_string *clientid;
#ifdef HAVE_REDIS
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
#endif
char *progname = *argv;
udata.usefiles = TRUE;
udata.ignoreretained = TRUE;
udata.pubprefix = NULL;
udata.skipdemo = TRUE;
udata.useredis = TRUE;
while ((ch = getopt(argc, argv, "DFRNP:")) != EOF) {
switch (ch) {
case 'D':
ud->skipdemo = FALSE;
break;
case 'F':
ud->usefiles = FALSE;
break;
case 'N':
ud->useredis = FALSE;
break;
case 'P':
udata.pubprefix = strdup(optarg);
break;
case 'R':
ud->ignoreretained = FALSE;
break;
default:
usage(*argv);
break;
}
}
argc -= (optind - 1);
argv += (optind - 1);
if (argc < 2) {
usage(progname);
return (-1);
}
if ((p = getenv("OTR_HOST")) != NULL) {
hostname = strdup(p);
}
if ((p = getenv("OTR_PORT")) != NULL) {
port = atoi(p);
}
revgeo_init();
utstring_new(clientid);
mosquitto_lib_init();
utstring_printf(clientid, "ot-recorder");
if (uname(&uts) == 0) {
utstring_printf(clientid, "-%s", uts.nodename);
}
utstring_printf(clientid, "-%d", getpid());
signal(SIGINT, catcher);
#ifdef HAVE_REDIS
ud->redis = redisConnectWithTimeout("localhost", 6379, timeout);
#endif
mosq = mosquitto_new(utstring_body(clientid), true, (void *)&udata);
if (!mosq) {
fprintf(stderr, "Error: Out of memory.\n");
mosquitto_lib_cleanup();
return 1;
}
/*
* Pushing list of topics into the array so that we can (re)subscribe on_connect()
*/
utarray_new(ud->topics, &ut_str_icd);
for (i = 1; i < argc; i++) {
utarray_push_back(ud->topics, &argv[i]);
}
mosquitto_reconnect_delay_set(mosq,
2, /* delay */
20, /* delay_max */
0); /* exponential backoff */
mosquitto_message_callback_set(mosq, on_message);
mosquitto_connect_callback_set(mosq, on_connect);
mosquitto_disconnect_callback_set(mosq, on_disconnect);
if ((username = getenv("OTR_USER")) != NULL) {
if ((password = getenv("OTR_PASS")) != NULL) {
mosquitto_username_pw_set(mosq, username, password);
}
}
cafile = getenv("OTR_CAFILE");
if (cafile && *cafile) {
rc = mosquitto_tls_set(mosq,
cafile, /* cafile */
NULL, /* capath */
NULL, /* certfile */
NULL, /* keyfile */
NULL /* pw_callback() */
);
if (rc != MOSQ_ERR_SUCCESS) {
fprintf(stderr, "Cannot set TLS CA: %s (check path names)\n",
mosquitto_strerror(rc));
exit(3);
}
mosquitto_tls_opts_set(mosq,
SSL_VERIFY_PEER,
NULL, /* tls_version: "tlsv1.2", "tlsv1" */
NULL /* ciphers */
);
}
rc = mosquitto_connect(mosq, hostname, port, 60);
if (rc) {
if (rc == MOSQ_ERR_ERRNO) {
strerror_r(errno, err, 1024);
fprintf(stderr, "Error: %s\n", err);
} else {
fprintf(stderr, "Unable to connect (%d) [%s].\n", rc, mosquitto_strerror(rc));
}
mosquitto_lib_cleanup();
return rc;
}
while (run) {
mosquitto_loop_forever(mosq, -1, 1);
}
mosquitto_disconnect(mosq);
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
return (0);
}

56
safewrite.c Normal file
View File

@@ -0,0 +1,56 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "safewrite.h"
int safewrite(char *filename, char *buf)
{
char *tmpfile = malloc(strlen(filename) + 3);
mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
int fd;
if (tmpfile == NULL)
return (-1);
sprintf(tmpfile, "%s~", filename);
if (unlink(tmpfile) == -1) {
if (errno != ENOENT) {
fprintf(stderr, "Failed to remove %s (errno=%d)\n", tmpfile, errno);
free(tmpfile);
return (-1);
}
}
if ((fd = open(tmpfile, O_RDWR|O_CREAT|O_TRUNC, mode)) == -1) {
fprintf(stderr, "Failed to create %s (errno=%d)\n", tmpfile, errno);
free(tmpfile);
return (-1);
}
if (write(fd, buf, strlen(buf)) != strlen(buf)) {
fprintf(stderr, "Failed to write to %s (errno=%d)\n", tmpfile, errno);
free(tmpfile);
close(fd);
return (-1);
}
/* Ensure NL-terminated */
if (buf[strlen(buf) - 1] != '\n') {
write(fd, "\n", 1);
}
close(fd);
if ((rename(tmpfile, filename)) == -1) {
fprintf(stderr, "Failed to rename %s to %s (errno=%d)\n", tmpfile, filename, errno);
free(tmpfile);
}
free(tmpfile);
return (0);
}

3
safewrite.h Normal file
View File

@@ -0,0 +1,3 @@
int safewrite(char *filename, char *buf);

18
udata.h Normal file
View File

@@ -0,0 +1,18 @@
#include "config.h"
#include "utarray.h"
#ifdef HAVE_REDIS
# include <hiredis/hiredis.h>
#endif
struct udata {
UT_array *topics; /* Array of topics to subscribe to */
#ifdef HAVE_REDIS
redisContext *redis;
#endif
int usefiles; /* True if files to be written */
int ignoreretained; /* True if retained messages should be ignored */
char *pubprefix; /* If not NULL (default), republish modified payload to <pubprefix>/topic */
int skipdemo; /* True if _demo users are to be skipped */
int useredis; /* True if we should do Redis (if we have it) */
};

232
utarray.h Normal file
View File

@@ -0,0 +1,232 @@
/*
Copyright (c) 2008-2014, Troy D. Hanson http://troydhanson.github.com/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* a dynamic array implementation using macros
*/
#ifndef UTARRAY_H
#define UTARRAY_H
#define UTARRAY_VERSION 1.9.9
#ifdef __GNUC__
#define _UNUSED_ __attribute__ ((__unused__))
#else
#define _UNUSED_
#endif
#include <stddef.h> /* size_t */
#include <string.h> /* memset, etc */
#include <stdlib.h> /* exit */
#define oom() exit(-1)
typedef void (ctor_f)(void *dst, const void *src);
typedef void (dtor_f)(void *elt);
typedef void (init_f)(void *elt);
typedef struct {
size_t sz;
init_f *init;
ctor_f *copy;
dtor_f *dtor;
} UT_icd;
typedef struct {
unsigned i,n;/* i: index of next available slot, n: num slots */
UT_icd icd; /* initializer, copy and destructor functions */
char *d; /* n slots of size icd->sz*/
} UT_array;
#define utarray_init(a,_icd) do { \
memset(a,0,sizeof(UT_array)); \
(a)->icd=*_icd; \
} while(0)
#define utarray_done(a) do { \
if ((a)->n) { \
if ((a)->icd.dtor) { \
size_t _ut_i; \
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
(a)->icd.dtor(utarray_eltptr(a,_ut_i)); \
} \
} \
free((a)->d); \
} \
(a)->n=0; \
} while(0)
#define utarray_new(a,_icd) do { \
a=(UT_array*)malloc(sizeof(UT_array)); \
utarray_init(a,_icd); \
} while(0)
#define utarray_free(a) do { \
utarray_done(a); \
free(a); \
} while(0)
#define utarray_reserve(a,by) do { \
if (((a)->i+(by)) > ((a)->n)) { \
while(((a)->i+(by)) > ((a)->n)) { (a)->n = ((a)->n ? (2*(a)->n) : 8); } \
if ( ((a)->d=(char*)realloc((a)->d, (a)->n*(a)->icd.sz)) == NULL) oom(); \
} \
} while(0)
#define utarray_push_back(a,p) do { \
utarray_reserve(a,1); \
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,(a)->i++), p); } \
else { memcpy(_utarray_eltptr(a,(a)->i++), p, (a)->icd.sz); }; \
} while(0)
#define utarray_pop_back(a) do { \
if ((a)->icd.dtor) { (a)->icd.dtor( _utarray_eltptr(a,--((a)->i))); } \
else { (a)->i--; } \
} while(0)
#define utarray_extend_back(a) do { \
utarray_reserve(a,1); \
if ((a)->icd.init) { (a)->icd.init(_utarray_eltptr(a,(a)->i)); } \
else { memset(_utarray_eltptr(a,(a)->i),0,(a)->icd.sz); } \
(a)->i++; \
} while(0)
#define utarray_len(a) ((a)->i)
#define utarray_eltptr(a,j) (((j) < (a)->i) ? _utarray_eltptr(a,j) : NULL)
#define _utarray_eltptr(a,j) ((char*)((a)->d + ((a)->icd.sz*(j) )))
#define utarray_insert(a,p,j) do { \
if (j > (a)->i) utarray_resize(a,j); \
utarray_reserve(a,1); \
if ((j) < (a)->i) { \
memmove( _utarray_eltptr(a,(j)+1), _utarray_eltptr(a,j), \
((a)->i - (j))*((a)->icd.sz)); \
} \
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,j), p); } \
else { memcpy(_utarray_eltptr(a,j), p, (a)->icd.sz); }; \
(a)->i++; \
} while(0)
#define utarray_inserta(a,w,j) do { \
if (utarray_len(w) == 0) break; \
if (j > (a)->i) utarray_resize(a,j); \
utarray_reserve(a,utarray_len(w)); \
if ((j) < (a)->i) { \
memmove(_utarray_eltptr(a,(j)+utarray_len(w)), \
_utarray_eltptr(a,j), \
((a)->i - (j))*((a)->icd.sz)); \
} \
if ((a)->icd.copy) { \
size_t _ut_i; \
for(_ut_i=0;_ut_i<(w)->i;_ut_i++) { \
(a)->icd.copy(_utarray_eltptr(a,j+_ut_i), _utarray_eltptr(w,_ut_i)); \
} \
} else { \
memcpy(_utarray_eltptr(a,j), _utarray_eltptr(w,0), \
utarray_len(w)*((a)->icd.sz)); \
} \
(a)->i += utarray_len(w); \
} while(0)
#define utarray_resize(dst,num) do { \
size_t _ut_i; \
if (dst->i > (size_t)(num)) { \
if ((dst)->icd.dtor) { \
for(_ut_i=num; _ut_i < dst->i; _ut_i++) { \
(dst)->icd.dtor(utarray_eltptr(dst,_ut_i)); \
} \
} \
} else if (dst->i < (size_t)(num)) { \
utarray_reserve(dst,num-dst->i); \
if ((dst)->icd.init) { \
for(_ut_i=dst->i; _ut_i < num; _ut_i++) { \
(dst)->icd.init(utarray_eltptr(dst,_ut_i)); \
} \
} else { \
memset(_utarray_eltptr(dst,dst->i),0,(dst)->icd.sz*(num-dst->i)); \
} \
} \
dst->i = num; \
} while(0)
#define utarray_concat(dst,src) do { \
utarray_inserta((dst),(src),utarray_len(dst)); \
} while(0)
#define utarray_erase(a,pos,len) do { \
if ((a)->icd.dtor) { \
size_t _ut_i; \
for(_ut_i=0; _ut_i < len; _ut_i++) { \
(a)->icd.dtor(utarray_eltptr((a),pos+_ut_i)); \
} \
} \
if ((a)->i > (pos+len)) { \
memmove( _utarray_eltptr((a),pos), _utarray_eltptr((a),pos+len), \
(((a)->i)-(pos+len))*((a)->icd.sz)); \
} \
(a)->i -= (len); \
} while(0)
#define utarray_renew(a,u) do { \
if (a) utarray_clear(a); \
else utarray_new((a),(u)); \
} while(0)
#define utarray_clear(a) do { \
if ((a)->i > 0) { \
if ((a)->icd.dtor) { \
size_t _ut_i; \
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
(a)->icd.dtor(utarray_eltptr(a,_ut_i)); \
} \
} \
(a)->i = 0; \
} \
} while(0)
#define utarray_sort(a,cmp) do { \
qsort((a)->d, (a)->i, (a)->icd.sz, cmp); \
} while(0)
#define utarray_find(a,v,cmp) bsearch((v),(a)->d,(a)->i,(a)->icd.sz,cmp)
#define utarray_front(a) (((a)->i) ? (_utarray_eltptr(a,0)) : NULL)
#define utarray_next(a,e) (((e)==NULL) ? utarray_front(a) : ((((a)->i) > (utarray_eltidx(a,e)+1)) ? _utarray_eltptr(a,utarray_eltidx(a,e)+1) : NULL))
#define utarray_prev(a,e) (((e)==NULL) ? utarray_back(a) : ((utarray_eltidx(a,e) > 0) ? _utarray_eltptr(a,utarray_eltidx(a,e)-1) : NULL))
#define utarray_back(a) (((a)->i) ? (_utarray_eltptr(a,(a)->i-1)) : NULL)
#define utarray_eltidx(a,e) (((char*)(e) >= (char*)((a)->d)) ? (((char*)(e) - (char*)((a)->d))/(size_t)(a)->icd.sz) : -1)
/* last we pre-define a few icd for common utarrays of ints and strings */
static void utarray_str_cpy(void *dst, const void *src) {
char **_src = (char**)src, **_dst = (char**)dst;
*_dst = (*_src == NULL) ? NULL : strdup(*_src);
}
static void utarray_str_dtor(void *elt) {
char **eltc = (char**)elt;
if (*eltc) free(*eltc);
}
static const UT_icd ut_str_icd _UNUSED_ = {sizeof(char*),NULL,utarray_str_cpy,utarray_str_dtor};
static const UT_icd ut_int_icd _UNUSED_ = {sizeof(int),NULL,NULL,NULL};
static const UT_icd ut_ptr_icd _UNUSED_ = {sizeof(void*),NULL,NULL,NULL};
#endif /* UTARRAY_H */

393
utstring.h Normal file
View File

@@ -0,0 +1,393 @@
/*
Copyright (c) 2008-2014, Troy D. Hanson http://troydhanson.github.com/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* a dynamic string implementation using macros
*/
#ifndef UTSTRING_H
#define UTSTRING_H
#define UTSTRING_VERSION 1.9.9
#ifdef __GNUC__
#define _UNUSED_ __attribute__ ((__unused__))
#else
#define _UNUSED_
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#define oom() exit(-1)
typedef struct {
char *d;
size_t n; /* allocd size */
size_t i; /* index of first unused byte */
} UT_string;
#define utstring_reserve(s,amt) \
do { \
if (((s)->n - (s)->i) < (size_t)(amt)) { \
(s)->d = (char*)realloc((s)->d, (s)->n + (amt)); \
if ((s)->d == NULL) oom(); \
(s)->n += (amt); \
} \
} while(0)
#define utstring_init(s) \
do { \
(s)->n = 0; (s)->i = 0; (s)->d = NULL; \
utstring_reserve(s,100); \
(s)->d[0] = '\0'; \
} while(0)
#define utstring_done(s) \
do { \
if ((s)->d != NULL) free((s)->d); \
(s)->n = 0; \
} while(0)
#define utstring_free(s) \
do { \
utstring_done(s); \
free(s); \
} while(0)
#define utstring_new(s) \
do { \
s = (UT_string*)calloc(sizeof(UT_string),1); \
if (!s) oom(); \
utstring_init(s); \
} while(0)
#define utstring_renew(s) \
do { \
if (s) { \
utstring_clear(s); \
} else { \
utstring_new(s); \
} \
} while(0)
#define utstring_clear(s) \
do { \
(s)->i = 0; \
(s)->d[0] = '\0'; \
} while(0)
#define utstring_bincpy(s,b,l) \
do { \
utstring_reserve((s),(l)+1); \
if (l) memcpy(&(s)->d[(s)->i], b, l); \
(s)->i += (l); \
(s)->d[(s)->i]='\0'; \
} while(0)
#define utstring_concat(dst,src) \
do { \
utstring_reserve((dst),((src)->i)+1); \
if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \
(dst)->i += (src)->i; \
(dst)->d[(dst)->i]='\0'; \
} while(0)
#define utstring_len(s) ((unsigned)((s)->i))
#define utstring_body(s) ((s)->d)
_UNUSED_ static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) {
int n;
va_list cp;
while (1) {
#ifdef _WIN32
cp = ap;
#else
va_copy(cp, ap);
#endif
n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp);
va_end(cp);
if ((n > -1) && ((size_t) n < (s->n-s->i))) {
s->i += n;
return;
}
/* Else try again with more space. */
if (n > -1) utstring_reserve(s,n+1); /* exact */
else utstring_reserve(s,(s->n)*2); /* 2x */
}
}
#ifdef __GNUC__
/* support printf format checking (2=the format string, 3=start of varargs) */
static void utstring_printf(UT_string *s, const char *fmt, ...)
__attribute__ (( format( printf, 2, 3) ));
#endif
_UNUSED_ static void utstring_printf(UT_string *s, const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
utstring_printf_va(s,fmt,ap);
va_end(ap);
}
/*******************************************************************************
* begin substring search functions *
******************************************************************************/
/* Build KMP table from left to right. */
_UNUSED_ static void _utstring_BuildTable(
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
i = 0;
j = i - 1;
P_KMP_Table[i] = j;
while (i < (long) P_NeedleLen)
{
while ( (j > -1) && (P_Needle[i] != P_Needle[j]) )
{
j = P_KMP_Table[j];
}
i++;
j++;
if (i < (long) P_NeedleLen)
{
if (P_Needle[i] == P_Needle[j])
{
P_KMP_Table[i] = P_KMP_Table[j];
}
else
{
P_KMP_Table[i] = j;
}
}
else
{
P_KMP_Table[i] = j;
}
}
return;
}
/* Build KMP table from right to left. */
_UNUSED_ static void _utstring_BuildTableR(
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
i = P_NeedleLen - 1;
j = i + 1;
P_KMP_Table[i + 1] = j;
while (i >= 0)
{
while ( (j < (long) P_NeedleLen) && (P_Needle[i] != P_Needle[j]) )
{
j = P_KMP_Table[j + 1];
}
i--;
j--;
if (i >= 0)
{
if (P_Needle[i] == P_Needle[j])
{
P_KMP_Table[i + 1] = P_KMP_Table[j + 1];
}
else
{
P_KMP_Table[i + 1] = j;
}
}
else
{
P_KMP_Table[i + 1] = j;
}
}
return;
}
/* Search data from left to right. ( Multiple search mode. ) */
_UNUSED_ static long _utstring_find(
const char *P_Haystack,
size_t P_HaystackLen,
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
long V_FindPosition = -1;
/* Search from left to right. */
i = j = 0;
while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) )
{
while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) )
{
i = P_KMP_Table[i];
}
i++;
j++;
if (i >= (int)P_NeedleLen)
{
/* Found. */
V_FindPosition = j - i;
break;
}
}
return V_FindPosition;
}
/* Search data from right to left. ( Multiple search mode. ) */
_UNUSED_ static long _utstring_findR(
const char *P_Haystack,
size_t P_HaystackLen,
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
long V_FindPosition = -1;
/* Search from right to left. */
j = (P_HaystackLen - 1);
i = (P_NeedleLen - 1);
while ( (j >= 0) && (j >= i) )
{
while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) )
{
i = P_KMP_Table[i + 1];
}
i--;
j--;
if (i < 0)
{
/* Found. */
V_FindPosition = j + 1;
break;
}
}
return V_FindPosition;
}
/* Search data from left to right. ( One time search mode. ) */
_UNUSED_ static long utstring_find(
UT_string *s,
long P_StartPosition, /* Start from 0. -1 means last position. */
const char *P_Needle,
size_t P_NeedleLen)
{
long V_StartPosition;
long V_HaystackLen;
long *V_KMP_Table;
long V_FindPosition = -1;
if (P_StartPosition < 0)
{
V_StartPosition = s->i + P_StartPosition;
}
else
{
V_StartPosition = P_StartPosition;
}
V_HaystackLen = s->i - V_StartPosition;
if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
{
V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
if (V_KMP_Table != NULL)
{
_utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table);
V_FindPosition = _utstring_find(s->d + V_StartPosition,
V_HaystackLen,
P_Needle,
P_NeedleLen,
V_KMP_Table);
if (V_FindPosition >= 0)
{
V_FindPosition += V_StartPosition;
}
free(V_KMP_Table);
}
}
return V_FindPosition;
}
/* Search data from right to left. ( One time search mode. ) */
_UNUSED_ static long utstring_findR(
UT_string *s,
long P_StartPosition, /* Start from 0. -1 means last position. */
const char *P_Needle,
size_t P_NeedleLen)
{
long V_StartPosition;
long V_HaystackLen;
long *V_KMP_Table;
long V_FindPosition = -1;
if (P_StartPosition < 0)
{
V_StartPosition = s->i + P_StartPosition;
}
else
{
V_StartPosition = P_StartPosition;
}
V_HaystackLen = V_StartPosition + 1;
if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
{
V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
if (V_KMP_Table != NULL)
{
_utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table);
V_FindPosition = _utstring_findR(s->d,
V_HaystackLen,
P_Needle,
P_NeedleLen,
V_KMP_Table);
free(V_KMP_Table);
}
}
return V_FindPosition;
}
/*******************************************************************************
* end substring search functions *
******************************************************************************/
#endif /* UTSTRING_H */