Переглянути джерело

Merge pull request #149 from lubosz/vive-imu

VIVE IMU improvements.
Special thanks to @pH5 for his work in ouvrt on the Vive.
TheOnlyJoey 7 роки тому
батько
коміт
e7ee4295ba

+ 1 - 1
CMakeLists.txt

@@ -97,7 +97,7 @@ if(OPENHMD_DRIVER_HTC_VIVE)
 	${CMAKE_CURRENT_LIST_DIR}/src/drv_htc_vive/vive.c
 	${CMAKE_CURRENT_LIST_DIR}/src/drv_htc_vive/packet.c
 	#${CMAKE_CURRENT_LIST_DIR}/src/ext_deps/miniz.c
-	${CMAKE_CURRENT_LIST_DIR}/src/ext_deps/mjson.c
+	${CMAKE_CURRENT_LIST_DIR}/src/ext_deps/nxjson.c
 	)
 	add_definitions(-DDRIVER_HTC_VIVE)
 

+ 1 - 1
meson.build

@@ -58,7 +58,7 @@ if _drivers.contains('vive')
 	sources += [
 		'src/drv_htc_vive/vive.c',
 		'src/drv_htc_vive/packet.c',
-		'src/ext_deps/mjson.c'
+		'src/ext_deps/nxjson.c'
 	]
 	c_args += '-DDRIVER_HTC_VIVE'
 	deps += dep_hidapi

+ 1 - 1
src/Makefile.am

@@ -31,7 +31,7 @@ if BUILD_DRIVER_HTC_VIVE
 libopenhmd_la_SOURCES += \
 	drv_htc_vive/vive.c \
 	drv_htc_vive/packet.c \
-	ext_deps/mjson.c
+	ext_deps/nxjson.c
 
 libopenhmd_la_CPPFLAGS += $(hidapi_CFLAGS) -DDRIVER_HTC_VIVE
 libopenhmd_la_LDFLAGS += $(hidapi_LIBS)

+ 54 - 19
src/drv_htc_vive/packet.c

@@ -1,5 +1,7 @@
 #include "vive.h"
-#include "vive_config.h"
+
+#include "../ext_deps/miniz.h"
+#include "../ext_deps/nxjson.h"
 
 #ifdef _MSC_VER
 #define inline __inline
@@ -26,7 +28,7 @@ inline static uint32_t read32(const unsigned char** buffer)
 	return ret;
 }
 
-bool vive_decode_sensor_packet(vive_sensor_packet* pkt, const unsigned char* buffer, int size)
+bool vive_decode_sensor_packet(vive_headset_imu_packet* pkt, const unsigned char* buffer, int size)
 {
 	if(size != 52){
 		LOGE("invalid vive sensor packet size (expected 52 but got %d)", size);
@@ -70,47 +72,80 @@ void trim(const char* src, char* buff, const unsigned int sizeBuff)
     buff[i] = '\0';
 }
 
-bool vive_decode_config_packet(vive_config_packet* pkt, const unsigned char* buffer, uint16_t size)
-{/*
+void get_vec3f_from_json(const nx_json* json, const char* name, vec3f* result)
+{
+	const nx_json* acc_bias_arr = nx_json_get(json, name);
+
+	for (int i = 0; i < acc_bias_arr->length; i++) {
+		const nx_json* item = nx_json_item(acc_bias_arr, i);
+		result->arr[i] = (float) item->dbl_value;
+	}
+}
+
+void print_vec3f(const char* title, vec3f *vec)
+{
+  LOGI("%s = %f %f %f\n", title, vec->x, vec->y, vec->z);
+}
+
+bool vive_decode_config_packet(vive_imu_config* result,
+                               const unsigned char* buffer,
+                               uint16_t size)
+{
+	/*
 	if(size != 4069){
 		LOGE("invalid vive sensor packet size (expected 4069 but got %d)", size);
 		return false;
 	}*/
 
-	pkt->report_id = 17;
-	pkt->length = size;
+	vive_config_packet pkt;
+
+	pkt.report_id = VIVE_CONFIG_READ_PACKET_ID;
+	pkt.length = size;
 
 	unsigned char output[32768];
 	mz_ulong output_size = 32768;
 
 	//int cmp_status = uncompress(pUncomp, &uncomp_len, pCmp, cmp_len);
-	int cmp_status = uncompress(output, &output_size, buffer, (mz_ulong)pkt->length);
+	int cmp_status = uncompress(output, &output_size,
+	                            buffer, (mz_ulong)pkt.length);
 	if (cmp_status != Z_OK){
 		LOGE("invalid vive config, could not uncompress");
 		return false;
 	}
 
-	LOGE("Decompressed from %u to %u bytes\n", (mz_uint32)pkt->length, (mz_uint32)output_size);
+	LOGD("Decompressed from %u to %u bytes\n",
+	     (mz_uint32)pkt.length, (mz_uint32)output_size);
 
 	//LOGD("Debug print all the RAW JSON things!\n%s", output);
 	//pUncomp should now be the uncompressed data, lets get the json from it
 	/** DEBUG JSON PARSER CODE **/
-	trim((char*)output,(char*)output,output_size);
+	trim((char*)output, (char*)output, (unsigned int)output_size);
 	//LOGD("%s\n",output);
 	/*
 	FILE* dfp;
 	dfp = fopen("jsondebug.json","w");
 	json_enable_debug(3, dfp);*/
-	int status = json_read_object((char*)output, sensor_offsets, NULL);
-	LOGI("\n--- Converted Vive JSON Data ---\n\n");
-	LOGI("acc_bias = %f %f %f\n", acc_bias[0], acc_bias[1], acc_bias[2]);
-	LOGI("acc_scale = %f %f %f\n", acc_scale[0], acc_scale[1], acc_scale[2]);
-	LOGI("gyro_bias = %f %f %f\n", gyro_bias[0], gyro_bias[1], gyro_bias[2]);
-	LOGI("gyro_scale = %f %f %f\n", gyro_scale[0], gyro_scale[1], gyro_scale[2]);
-	LOGI("\n--- End of Vive JSON Data ---\n\n");
-
-	if (status != 0)
-		puts(json_error_string(status));
+	const nx_json* json = nx_json_parse((char*)output, 0);
+
+	if (json) {
+		get_vec3f_from_json(json, "acc_bias", &result->acc_bias);
+		get_vec3f_from_json(json, "acc_scale", &result->acc_scale);
+		get_vec3f_from_json(json, "gyro_bias", &result->gyro_bias);
+		get_vec3f_from_json(json, "gyro_scale", &result->gyro_scale);
+
+		nx_json_free(json);
+
+		LOGI("\n--- Converted Vive JSON Data ---\n\n");
+		print_vec3f("acc_bias", &result->acc_bias);
+		print_vec3f("acc_scale", &result->acc_scale);
+		print_vec3f("gyro_bias", &result->gyro_bias);
+		print_vec3f("gyro_scale", &result->gyro_scale);
+		LOGI("\n--- End of Vive JSON Data ---\n\n");
+	} else {
+		LOGE("Could not parse JSON data.\n");
+		return false;
+	}
+
 	/** END OF DEBUG JSON PARSER CODE **/
 
 //	free(pCmp);

+ 114 - 46
src/drv_htc_vive/vive.c

@@ -16,7 +16,7 @@
 #define VIVE_WATCHMAN_DONGLE     0x2101
 #define VIVE_LIGHTHOUSE_FPGA_RX  0x2000
 
-#define VIVE_TIME_DIV 48000000.0f
+#define VIVE_CLOCK_FREQ 48000000.0f // Hz = 48 MHz
 
 #include <string.h>
 #include <wchar.h>
@@ -38,27 +38,37 @@ typedef struct {
 	uint32_t last_ticks;
 	uint8_t last_seq;
 
-	vive_config_packet vive_config;
 	vec3f gyro_error;
 	filter_queue gyro_q;
+
+	vive_imu_config imu_config;
+
 } vive_priv;
 
-void vec3f_from_vive_vec_accel(const int16_t* smp, vec3f* out_vec)
+void vec3f_from_vive_vec_accel(const vive_imu_config* config,
+                               const int16_t* smp,
+                               vec3f* out)
 {
-	float gravity = 9.81f;
-	float scaler = 4.0f * gravity / 32768.0f;
+	float range = config->acc_range / 32768.0f;
+	out->x = range * config->acc_scale.x * (float) smp[0] - config->acc_bias.x;
+	out->y = range * config->acc_scale.y * (float) smp[1] - config->acc_bias.y;
+	out->z = range * config->acc_scale.z * (float) smp[2] - config->acc_bias.z;
 
-	out_vec->x = (float)smp[0] * scaler;
-	out_vec->y = (float)smp[1] * scaler * -1;
-	out_vec->z = (float)smp[2] * scaler * -1;
+	out->y *= -1;
+	out->z *= -1;
 }
 
-void vec3f_from_vive_vec_gyro(const int16_t* smp, vec3f* out_vec)
+void vec3f_from_vive_vec_gyro(const vive_imu_config* config,
+                              const int16_t* smp,
+                              vec3f* out)
 {
-	float scaler = 8.7f / 32768.0f;
-	out_vec->x = (float)smp[0] * scaler;
-	out_vec->y = (float)smp[1] * scaler * -1;
-	out_vec->z = (float)smp[2] * scaler * -1;
+	float range = config->gyro_range / 32768.0f;
+	out->x = range * config->gyro_scale.x * (float)smp[0] - config->gyro_bias.x;
+	out->y = range * config->gyro_scale.y * (float)smp[1] - config->gyro_bias.x;
+	out->z = range * config->gyro_scale.z * (float)smp[2] - config->gyro_bias.x;
+
+	out->y *= -1;
+	out->z *= -1;
 }
 
 static bool process_error(vive_priv* priv)
@@ -76,7 +86,7 @@ static bool process_error(vive_priv* priv)
 	return false;
 }
 
-vive_sensor_sample* get_next_sample(vive_sensor_packet* pkt, int last_seq)
+vive_headset_imu_sample* get_next_sample(vive_headset_imu_packet* pkt, int last_seq)
 {
 	int diff[3];
 
@@ -115,10 +125,10 @@ static void update_device(ohmd_device* device)
 
 	while((size = hid_read(priv->imu_handle, buffer, FEATURE_BUFFER_SIZE)) > 0){
 		if(buffer[0] == VIVE_IRQ_SENSORS){
-			vive_sensor_packet pkt;
+			vive_headset_imu_packet pkt;
 			vive_decode_sensor_packet(&pkt, buffer, size);
 
-			vive_sensor_sample* smp = NULL;
+			vive_headset_imu_sample* smp = NULL;
 
 			while((smp = get_next_sample(&pkt, priv->last_seq)) != NULL)
 			{
@@ -129,12 +139,12 @@ static void update_device(ohmd_device* device)
 				t1 = smp->time_ticks;
 				t2 = priv->last_ticks;
 
-				float dt = (t1 - t2) / VIVE_TIME_DIV;
+				float dt = (t1 - t2) / VIVE_CLOCK_FREQ;
 
 				priv->last_ticks = smp->time_ticks;
 
-				vec3f_from_vive_vec_accel(smp->acc, &priv->raw_accel);
-				vec3f_from_vive_vec_gyro(smp->rot, &priv->raw_gyro);
+				vec3f_from_vive_vec_accel(&priv->imu_config, smp->acc, &priv->raw_accel);
+				vec3f_from_vive_vec_gyro(&priv->imu_config, smp->rot, &priv->raw_gyro);
 
 				if(process_error(priv)){
 					vec3f mag = {{0.0f, 0.0f, 0.0f}};
@@ -276,6 +286,85 @@ static hid_device* open_device_idx(int manufacturer, int product, int iface, int
 	return ret;
 }
 
+void vive_read_config(vive_priv* priv)
+{
+	unsigned char buffer[128];
+	int bytes;
+
+	LOGI("Getting feature report 16 to 39\n");
+	buffer[0] = VIVE_CONFIG_START_PACKET_ID;
+	bytes = hid_get_feature_report(priv->imu_handle, buffer, sizeof(buffer));
+	printf("got %i bytes\n", bytes);
+	for (int i = 0; i < bytes; i++) {
+		printf("%02x ", buffer[i]);
+	}
+	printf("\n\n");
+
+	unsigned char* packet_buffer = malloc(4096);
+
+	int offset = 0;
+	while (buffer[1] != 0) {
+		buffer[0] = VIVE_CONFIG_READ_PACKET_ID;
+		bytes = hid_get_feature_report(priv->imu_handle, buffer, sizeof(buffer));
+
+    memcpy((uint8_t*)packet_buffer + offset, buffer+2, buffer[1]);
+    offset += buffer[1];
+  }
+  packet_buffer[offset] = '\0';
+  //LOGD("Result: %s\n", packet_buffer);
+  vive_decode_config_packet(&priv->imu_config, packet_buffer, offset);
+
+  free(packet_buffer);
+}
+
+#define OHMD_GRAVITY_EARTH 9.80665 // m/s²
+
+int vive_get_range_packet(vive_priv* priv)
+{
+  unsigned char buffer[64];
+
+  int ret;
+  int i;
+
+  buffer[0] = VIVE_IMU_RANGE_MODES_PACKET_ID;
+
+  ret = hid_get_feature_report(priv->imu_handle, buffer, sizeof(buffer));
+  if (ret < 0)
+    return ret;
+
+  if (!buffer[1] || !buffer[2]) {
+    ret = hid_get_feature_report(priv->imu_handle, buffer, sizeof(buffer));
+    if (ret < 0)
+      return ret;
+
+    if (!buffer[1] || !buffer[2]) {
+      LOGE("unexpected range mode report: %02x %02x %02x",
+        buffer[0], buffer[1], buffer[2]);
+      for (i = 0; i < 61; i++)
+        LOGE(" %02x", buffer[3+i]);
+      LOGE("\n");
+    }
+  }
+
+  if (buffer[1] > 4 || buffer[2] > 4)
+    return -1;
+
+  /*
+   * Convert MPU-6500 gyro full scale range (+/-250°/s, +/-500°/s,
+   * +/-1000°/s, or +/-2000°/s) into rad/s, accel full scale range
+   * (+/-2g, +/-4g, +/-8g, or +/-16g) into m/s².
+   */
+  double gyro_range = M_PI / 180.0 * (250 << buffer[0]);
+  priv->imu_config.gyro_range = (float) gyro_range;
+  LOGI("gyro_range %f\n", gyro_range);
+
+  double acc_range = OHMD_GRAVITY_EARTH * (2 << buffer[1]);
+  priv->imu_config.acc_range = (float) acc_range;
+  LOGI("acc_range %f\n", acc_range);
+
+  return 0;
+}
+
 static ohmd_device* open_device(ohmd_driver* driver, ohmd_device_desc* desc)
 {
 	vive_priv* priv = ohmd_alloc(driver->ctx, sizeof(vive_priv));
@@ -323,33 +412,12 @@ static ohmd_device* open_device(ohmd_driver* driver, ohmd_device_desc* desc)
 	//hret = hid_send_feature_report(priv->hmd_handle, vive_magic_enable_lighthouse, sizeof(vive_magic_enable_lighthouse));
 	//LOGD("enable lighthouse magic: %d\n", hret);
 
-	unsigned char buffer[128];
-	int bytes;
+	vive_read_config(priv);
 
-	LOGI("Getting feature report 16 to 39\n");
-	buffer[0] = 16;
-	bytes = hid_get_feature_report(priv->imu_handle, buffer, sizeof(buffer));
-	printf("got %i bytes\n", bytes);
-	for (int i = 0; i < bytes; i++) {
-		printf("%02x ", buffer[i]);
-	}
-	printf("\n\n");
-
-	unsigned char* packet_buffer = malloc(4096);
-
-	int offset = 0;
-	while (buffer[1] != 0) {
-		buffer[0] = 17;
-		bytes = hid_get_feature_report(priv->imu_handle, buffer, sizeof(buffer));
-
- 		memcpy((uint8_t*)packet_buffer + offset, buffer+2, buffer[1]);
- 		offset += buffer[1];
+	if (vive_get_range_packet(priv) != 0)
+	{
+		LOGE("Could not get range packet.\n");
 	}
-	packet_buffer[offset] = '\0';
-	//LOGD("Result: %s\n", packet_buffer);
-	vive_decode_config_packet(&priv->vive_config, packet_buffer, offset);
-
-	free(packet_buffer);
 
 	// Set default device properties
 	ohmd_set_default_device_properties(&priv->base.properties);
@@ -359,8 +427,8 @@ static ohmd_device* open_device(ohmd_driver* driver, ohmd_device_desc* desc)
 	priv->base.properties.vsize = 0.068234f;
 	priv->base.properties.hres = 2160;
 	priv->base.properties.vres = 1200;
-	priv->base.properties.lens_sep = 0.063500;
-	priv->base.properties.lens_vpos = 0.049694;
+	priv->base.properties.lens_sep = 0.063500f;
+	priv->base.properties.lens_vpos = 0.049694f;
 	priv->base.properties.fov = DEG_TO_RAD(111.435f); //TODO: Confirm exact mesurements
 	priv->base.properties.ratio = (2160.0f / 1200.0f) / 2.0f;
 

+ 28 - 6
src/drv_htc_vive/vive.h

@@ -9,7 +9,9 @@
 
 typedef enum
 {
-	VIVE_CONFIG_DATA = 17,
+	VIVE_IMU_RANGE_MODES_PACKET_ID = 1,
+	VIVE_CONFIG_START_PACKET_ID = 16,
+	VIVE_CONFIG_READ_PACKET_ID = 17,
 	VIVE_IRQ_SENSORS = 32,
 } vive_irq_cmd;
 
@@ -19,13 +21,13 @@ typedef struct
 	int16_t rot[3];
 	uint32_t time_ticks;
 	uint8_t seq;
-} vive_sensor_sample;
+} vive_headset_imu_sample;
 
 typedef struct
 {
 	uint8_t report_id;
-	vive_sensor_sample samples[3];
-} vive_sensor_packet;
+	vive_headset_imu_sample samples[3];
+} vive_headset_imu_packet;
 
 typedef struct
 {
@@ -34,8 +36,28 @@ typedef struct
 	unsigned char config_data[99999];
 } vive_config_packet;
 
+typedef struct
+{
+	uint8_t id;
+	uint8_t gyro_range;
+	uint8_t accel_range;
+	uint8_t unknown[61];
+} vive_imu_range_modes_packet;
+
+typedef struct
+{
+	vec3f acc_bias, acc_scale;
+	float acc_range;
+	vec3f gyro_bias, gyro_scale;
+	float gyro_range;
+} vive_imu_config;
+
 void vec3f_from_vive_vec(const int16_t* smp, vec3f* out_vec);
-bool vive_decode_sensor_packet(vive_sensor_packet* pkt, const unsigned char* buffer, int size);
-bool vive_decode_config_packet(vive_config_packet* pkt, const unsigned char* buffer, uint16_t size);
+bool vive_decode_sensor_packet(vive_headset_imu_packet* pkt,
+                               const unsigned char* buffer,
+                               int size);
+bool vive_decode_config_packet(vive_imu_config* result,
+                               const unsigned char* buffer,
+                               uint16_t size);
 
 #endif

+ 0 - 43
src/drv_htc_vive/vive_config.h

@@ -1,43 +0,0 @@
-/* Suppress the warnings for this include, since we don't care about them for external dependencies
- * Requires at least GCC 4.6 or higher
-*/
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstrict-aliasing"
-#pragma GCC diagnostic ignored "-Wswitch"
-#pragma GCC diagnostic ignored "-Wimplicit-function-declaration"
-#pragma GCC diagnostic ignored "-Wmisleading-indentation"
-#include "../ext_deps/miniz.c"
-#include "../ext_deps/mjson.h"
-#pragma GCC diagnostic pop
-
-//Temporary, will get 'structed up'
-static double acc_bias[3];
-static double acc_scale[3];
-static double gyro_bias[3];
-static double gyro_scale[3];
-//end of temporary
-
-//Vive config packet
-const struct json_attr_t sensor_offsets[] = {
-    {"acc_bias", t_array,   .addr.array.element_type = t_real,
-                            .addr.array.arr.reals.store = acc_bias,
-                            .addr.array.maxlen = 3},
-    {"acc_scale", t_array,  .addr.array.element_type = t_real,
-                            .addr.array.arr.reals.store = acc_scale,
-                            .addr.array.maxlen = 3},
-    {"device", t_object},
-    {"device_class", t_ignore},
-    {"device_pid", t_ignore},
-    {"device_serial_number", t_ignore},
-    {"device_vid", t_ignore},
-    {"display_edid", t_ignore},
-    {"display_gc", t_ignore},
-    {"display_mc", t_ignore},
-    {"gyro_bias", t_array,  .addr.array.element_type = t_real,
-                            .addr.array.arr.reals.store = gyro_bias,
-                            .addr.array.maxlen = 3},
-    {"gyro_scale", t_array, .addr.array.element_type = t_real,
-                            .addr.array.arr.reals.store = gyro_scale,
-                            .addr.array.maxlen = 3},
-    {NULL},
-};

+ 10 - 0
src/ext_deps/miniz.h

@@ -0,0 +1,10 @@
+/* Suppress the warnings for this include, since we don't care about them for external dependencies
+ * Requires at least GCC 4.6 or higher
+*/
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#pragma GCC diagnostic ignored "-Wswitch"
+#pragma GCC diagnostic ignored "-Wimplicit-function-declaration"
+#pragma GCC diagnostic ignored "-Wmisleading-indentation"
+#include "../ext_deps/miniz.c"
+#pragma GCC diagnostic pop

+ 0 - 779
src/ext_deps/mjson.c

@@ -1,779 +0,0 @@
-/****************************************************************************
-
-NAME
-   mjson.c - parse JSON into fixed-extent data structures
-
-DESCRIPTION
-   This module parses a large subset of JSON (JavaScript Object
-Notation).  Unlike more general JSON parsers, it doesn't use malloc(3)
-and doesn't support polymorphism; you need to give it a set of
-template structures describing the expected shape of the incoming
-JSON, and it will error out if that shape is not matched.  When the
-parse succeeds, attribute values will be extracted into static
-locations specified in the template structures.
-
-   The "shape" of a JSON object in the type signature of its
-attributes (and attribute values, and so on recursively down through
-all nestings of objects and arrays).  This parser is indifferent to
-the order of attributes at any level, but you have to tell it in
-advance what the type of each attribute value will be and where the
-parsed value will be stored. The template structures may supply
-default values to be used when an expected attribute is omitted.
-
-   The preceding paragraph told one fib.  A single attribute may
-actually have a span of multiple specifications with different
-syntactically distinguishable types (e.g. string vs. real vs. integer
-vs. boolean, but not signed integer vs. unsigned integer).  The parser
-will match the right spec against the actual data.
-
-   The dialect this parses has some limitations.  First, it cannot
-recognize the JSON "null" value. Second, all elements of an array must
-be of the same type. Third, characters may not be array elements (this
-restriction could be lifted)
-
-   There are separate entry points for beginning a parse of either
-JSON object or a JSON array. JSON "float" quantities are actually
-stored as doubles.
-
-   This parser processes object arrays in one of two different ways,
-defending on whether the array subtype is declared as object or
-structobject.
-
-   Object arrays take one base address per object subfield, and are
-mapped into parallel C arrays (one per subfield).  Strings are not
-supported in this kind of array, as they don't have a "natural" size
-to use as an offset multiplier.
-
-   Structobjects arrays are a way to parse a list of objects to a set
-of modifications to a corresponding array of C structs.  The trick is
-that the array object initialization has to specify both the C struct
-array's base address and the stride length (the size of the C struct).
-If you initialize the offset fields with the correct offsetof calls,
-everything will work. Strings are supported but all string storage
-has to be inline in the struct.
-
-PERMISSIONS
-   This file is Copyright (c) 2014 by Eric S. Raymond
-   BSD terms apply: see the file COPYING in the distribution root for details.
-
-***************************************************************************/
-/* The strptime prototype is not provided unless explicitly requested.
- * We also need to set the value high enough to signal inclusion of
- * newer features (like clock_gettime).  See the POSIX spec for more info:
- * http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_01_02 */
-#define _XOPEN_SOURCE 600
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <errno.h>
-#include <time.h>
-#include <math.h>	/* for HUGE_VAL */
-
-#include "mjson.h"
-
-#define DEBUG_ENABLE 1
-#ifdef DEBUG_ENABLE
-static int debuglevel = 0;
-static FILE *debugfp;
-
-void json_enable_debug(int level, FILE * fp)
-/* control the level and destination of debug trace messages */
-{
-    debuglevel = level;
-    debugfp = fp;
-}
-
-static void json_trace(int errlevel, const char *fmt, ...)
-/* assemble command in printf(3) style */
-{
-    if (errlevel <= debuglevel) {
-	char buf[BUFSIZ];
-	va_list ap;
-
-	(void)strncpy(buf, "json: ", BUFSIZ-1);
-	buf[BUFSIZ-1] = '\0';
-	va_start(ap, fmt);
-	(void)vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt,
-			ap);
-	va_end(ap);
-
-	(void)fputs(buf, debugfp);
-    }
-}
-
-# define json_debug_trace(args) (void) json_trace args
-#else
-# define json_debug_trace(args) /*@i1@*/do { } while (0)
-#endif /* DEBUG_ENABLE */
-
-/*@-immediatetrans -dependenttrans -usereleased -compdef@*/
-static /*@null@*/ char *json_target_address(const struct json_attr_t *cursor,
-					     /*@null@*/
-					     const struct json_array_t
-					     *parent, int offset)
-{
-    char *targetaddr = NULL;
-    if (parent == NULL || parent->element_type != t_structobject) {
-	/* ordinary case - use the address in the cursor structure */
-	switch (cursor->type) {
-	case t_ignore:
-	    targetaddr = NULL;
-	    break;
-	case t_integer:
-	    targetaddr = (char *)&cursor->addr.integer[offset];
-	    break;
-	case t_uinteger:
-	    targetaddr = (char *)&cursor->addr.uinteger[offset];
-	    break;
-	case t_time:
-	case t_real:
-	    targetaddr = (char *)&cursor->addr.real[offset];
-	    break;
-	case t_string:
-	    targetaddr = cursor->addr.string;
-	    break;
-	case t_boolean:
-	    targetaddr = (char *)&cursor->addr.boolean[offset];
-	    break;
-	case t_character:
-	    targetaddr = (char *)&cursor->addr.character[offset];
-	    break;
-	default:
-	    targetaddr = NULL;
-	    break;
-	}
-    } else
-	/* tricky case - hacking a member in an array of structures */
-	targetaddr =
-	    parent->arr.objects.base + (offset * parent->arr.objects.stride) +
-	    cursor->addr.offset;
-    json_debug_trace((1, "Target address for %s (offset %d) is %p\n",
-		      cursor->attribute, offset, targetaddr));
-    return targetaddr;
-}
-
-#ifdef TIME_ENABLE
-static double iso8601_to_unix( /*@in@*/ char *isotime)
-/* ISO8601 UTC to Unix UTC */
-{
-    char *dp = NULL;
-    double usec;
-    struct tm tm;
-
-   /*@i1@*/ dp = strptime(isotime, "%Y-%m-%dT%H:%M:%S", &tm);
-    if (dp == NULL)
-	return (double)HUGE_VAL;
-    if (*dp == '.')
-	usec = strtod(dp, NULL);
-    else
-	usec = 0;
-    return (double)timegm(&tm) + usec;
-}
-#endif /* TIME_ENABLE */
-
-/*@-immediatetrans -dependenttrans +usereleased +compdef@*/
-
-static int json_internal_read_object(const char *cp,
-				     const struct json_attr_t *attrs,
-				     /*@null@*/
-				     const struct json_array_t *parent,
-				     int offset,
-				     /*@null@*/ const char **end)
-{
-    /*@ -nullstate -nullderef -mustfreefresh -nullpass -usedef @*/
-    enum
-    { init, await_attr, in_attr, await_value, in_val_string,
-	in_escape, in_val_token, post_val, post_array
-    } state = 0;
-#ifdef DEBUG_ENABLE
-    char *statenames[] = {
-	"init", "await_attr", "in_attr", "await_value", "in_val_string",
-	"in_escape", "in_val_token", "post_val", "post_array",
-    };
-#endif /* DEBUG_ENABLE */
-    char attrbuf[JSON_ATTR_MAX + 1], *pattr = NULL;
-    char valbuf[JSON_VAL_MAX + 1], *pval = NULL;
-    bool value_quoted = false;
-    char uescape[5];		/* enough space for 4 hex digits and a NUL */
-    const struct json_attr_t *cursor;
-    int substatus, n, maxlen = 0;
-    unsigned int u;
-    const struct json_enum_t *mp;
-    char *lptr;
-
-#ifdef S_SPLINT_S
-    /* prevents gripes about buffers not being completely defined */
-    memset(valbuf, '\0', sizeof(valbuf));
-    memset(attrbuf, '\0', sizeof(attrbuf));
-#endif /* S_SPLINT_S */
-
-    if (end != NULL)
-	*end = NULL;		/* give it a well-defined value on parse failure */
-
-    /* stuff fields with defaults in case they're omitted in the JSON input */
-    for (cursor = attrs; cursor->attribute != NULL; cursor++)
-	if (!cursor->nodefault) {
-	    lptr = json_target_address(cursor, parent, offset);
-	    if (lptr != NULL)
-		switch (cursor->type) {
-		case t_integer:
-		    memcpy(lptr, &cursor->dflt.integer, sizeof(int));
-		    break;
-		case t_uinteger:
-		    memcpy(lptr, &cursor->dflt.uinteger, sizeof(unsigned int));
-		    break;
-		case t_time:
-		case t_real:
-		    memcpy(lptr, &cursor->dflt.real, sizeof(double));
-		    break;
-		case t_string:
-		    if (parent != NULL
-			&& parent->element_type != t_structobject
-			&& offset > 0)
-			return JSON_ERR_NOPARSTR;
-		    lptr[0] = '\0';
-		    break;
-		case t_boolean:
-		    memcpy(lptr, &cursor->dflt.boolean, sizeof(bool));
-		    break;
-		case t_character:
-		    lptr[0] = cursor->dflt.character;
-		    break;
-		case t_object:	/* silences a compiler warning */
-		case t_structobject:
-		case t_array:
-		case t_check:
-		case t_ignore:
-		    break;
-		}
-	}
-
-    json_debug_trace((1, "JSON parse of '%s' begins.\n", cp));
-
-    /* parse input JSON */
-    for (; *cp != '\0'; cp++) {
-	json_debug_trace((2, "State %-14s, looking at '%c' (%p)\n",
-			  statenames[state], *cp, cp));
-	switch (state) {
-	case init:
-	    if (isspace((unsigned char) *cp))
-		continue;
-	    else if (*cp == '{')
-		state = await_attr;
-	    else {
-		json_debug_trace((1,
-				  "Non-WS when expecting object start.\n"));
-		if (end != NULL)
-		    *end = cp;
-		return JSON_ERR_OBSTART;
-	    }
-	    break;
-	case await_attr:
-	    if (isspace((unsigned char) *cp))
-		continue;
-	    else if (*cp == '"') {
-		state = in_attr;
-		pattr = attrbuf;
-		if (end != NULL)
-		    *end = cp;
-	    } else if (*cp == '}')
-		break;
-	    else {
-		json_debug_trace((1, "Non-WS when expecting attribute.\n"));
-		if (end != NULL)
-		    *end = cp;
-		return JSON_ERR_ATTRSTART;
-	    }
-	    break;
-	case in_attr:
-	    if (pattr == NULL)
-		/* don't update end here, leave at attribute start */
-		return JSON_ERR_NULLPTR;
-	    if (*cp == '"') {
-		*pattr++ = '\0';
-		json_debug_trace((1, "Collected attribute name %s\n",
-				  attrbuf));
-		for (cursor = attrs; cursor->attribute != NULL; cursor++) {
-		    json_debug_trace((2, "Checking against %s\n",
-				      cursor->attribute));
-		    if (strcmp(cursor->attribute, attrbuf) == 0)
-			break;
-		}
-		if (cursor->attribute == NULL) {
-		    json_debug_trace((1,
-				      "Unknown attribute name '%s' (attributes begin with '%s').\n",
-				      attrbuf, attrs->attribute));
-		    /* don't update end here, leave at attribute start */
-		    return JSON_ERR_BADATTR;
-		}
-		state = await_value;
-		if (cursor->type == t_string)
-		    maxlen = (int)cursor->len - 1;
-		else if (cursor->type == t_check)
-		    maxlen = (int)strlen(cursor->dflt.check);
-		else if (cursor->type == t_time || cursor->type == t_ignore)
-		    maxlen = JSON_VAL_MAX;
-		else if (cursor->map != NULL)
-		    maxlen = (int)sizeof(valbuf) - 1;
-		pval = valbuf;
-	    } else if (pattr >= attrbuf + JSON_ATTR_MAX - 1) {
-		json_debug_trace((1, "Attribute name too long.\n"));
-		/* don't update end here, leave at attribute start */
-		return JSON_ERR_ATTRLEN;
-	    } else
-		*pattr++ = *cp;
-	    break;
-	case await_value:
-	    if (isspace((unsigned char) *cp) || *cp == ':')
-		continue;
-	    else if (*cp == '[') {
-		if (cursor->type != t_array) {
-		    json_debug_trace((1,
-				      "Saw [ when not expecting array.\n"));
-		    if (end != NULL)
-			*end = cp;
-		    return JSON_ERR_NOARRAY;
-		}
-		substatus = json_read_array(cp, &cursor->addr.array, &cp);
-		if (substatus != 0)
-		    return substatus;
-		state = post_array;
-	    } else if (cursor->type == t_array) {
-		json_debug_trace((1,
-				  "Array element was specified, but no [.\n"));
-		if (end != NULL)
-		    *end = cp;
-		return JSON_ERR_NOBRAK;
-	    } else if (*cp == '"') {
-		value_quoted = true;
-		state = in_val_string;
-		pval = valbuf;
-	    } else {
-		value_quoted = false;
-		state = in_val_token;
-		pval = valbuf;
-		*pval++ = *cp;
-	    }
-	    break;
-	case in_val_string:
-	    if (pval == NULL)
-		/* don't update end here, leave at value start */
-		return JSON_ERR_NULLPTR;
-	    if (*cp == '\\')
-		state = in_escape;
-	    else if (*cp == '"') {
-		*pval++ = '\0';
-		json_debug_trace((1, "Collected string value %s\n", valbuf));
-		state = post_val;
-	    } else if (pval > valbuf + JSON_VAL_MAX - 1
-		       || pval > valbuf + maxlen) {
-		json_debug_trace((1, "String value too long.\n"));
-		/* don't update end here, leave at value start */
-		return JSON_ERR_STRLONG;	/*  */
-	    } else
-		*pval++ = *cp;
-	    break;
-	case in_escape:
-	    if (pval == NULL)
-		/* don't update end here, leave at value start */
-		return JSON_ERR_NULLPTR;
-	    switch (*cp) {
-	    case 'b':
-		*pval++ = '\b';
-		break;
-	    case 'f':
-		*pval++ = '\f';
-		break;
-	    case 'n':
-		*pval++ = '\n';
-		break;
-	    case 'r':
-		*pval++ = '\r';
-		break;
-	    case 't':
-		*pval++ = '\t';
-		break;
-	    case 'u':
-		for (n = 0; n < 4 && cp[n] != '\0'; n++)
-		    uescape[n] = *cp++;
-		--cp;
-		(void)sscanf(uescape, "%04x", &u);
-		*pval++ = (char)u;	/* will truncate values above 0xff */
-		break;
-	    default:		/* handles double quote and solidus */
-		*pval++ = *cp;
-		break;
-	    }
-	    state = in_val_string;
-	    break;
-	case in_val_token:
-	    if (pval == NULL)
-		/* don't update end here, leave at value start */
-		return JSON_ERR_NULLPTR;
-	    if (isspace((unsigned char) *cp) || *cp == ',' || *cp == '}') {
-		*pval = '\0';
-		json_debug_trace((1, "Collected token value %s.\n", valbuf));
-		state = post_val;
-		if (*cp == '}' || *cp == ',')
-		    --cp;
-	    } else if (pval > valbuf + JSON_VAL_MAX - 1) {
-		json_debug_trace((1, "Token value too long.\n"));
-		/* don't update end here, leave at value start */
-		return JSON_ERR_TOKLONG;
-	    } else
-		*pval++ = *cp;
-	    break;
-	case post_val:
-	    /*
-	     * We know that cursor points at the first spec matching
-	     * the current attribute.  We don't know that it's *the*
-	     * correct spec; our dialect allows there to be any number
-	     * of adjacent ones with the same attrname but different
-	     * types.  Here's where we try to seek forward for a
-	     * matching type/attr pair if we're not looking at one.
-	     */
-	    for (;;) {
-		int seeking = cursor->type;
-		if (value_quoted && (cursor->type == t_string || cursor->type == t_time))
-		    break;
-		if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0)
-			&& seeking == t_boolean)
-		    break;
-		if (isdigit((unsigned char) valbuf[0])) {
-		    bool decimal = strchr(valbuf, '.') != NULL;
-		    if (decimal && seeking == t_real)
-			break;
-		    if (!decimal && (seeking == t_integer || seeking == t_uinteger))
-			break;
-		}
-		if (cursor[1].attribute==NULL)	/* out of possiblities */
-		    break;
-		if (strcmp(cursor[1].attribute, attrbuf)!=0)
-		    break;
-		++cursor;
-	    }
-	    if (value_quoted
-		&& (cursor->type != t_string && cursor->type != t_character
-		    && cursor->type != t_check && cursor->type != t_time
-		    && cursor->type != t_ignore && cursor->map == 0)) {
-		json_debug_trace((1,
-				  "Saw quoted value when expecting non-string.\n"));
-		return JSON_ERR_QNONSTRING;
-	    }
-	    if (!value_quoted
-		&& (cursor->type == t_string || cursor->type == t_check
-		    || cursor->type == t_time || cursor->map != 0)) {
-		json_debug_trace((1,
-				  "Didn't see quoted value when expecting string.\n"));
-		return JSON_ERR_NONQSTRING;
-	    }
-	    if (cursor->map != 0) {
-		for (mp = cursor->map; mp->name != NULL; mp++)
-		    if (strcmp(mp->name, valbuf) == 0) {
-			goto foundit;
-		    }
-		json_debug_trace((1, "Invalid enumerated value string %s.\n",
-				  valbuf));
-		return JSON_ERR_BADENUM;
-	      foundit:
-		(void)snprintf(valbuf, sizeof(valbuf), "%d", mp->value);
-	    }
-	    lptr = json_target_address(cursor, parent, offset);
-	    if (lptr != NULL)
-		switch (cursor->type) {
-		case t_integer:
-		    {
-			int tmp = atoi(valbuf);
-			memcpy(lptr, &tmp, sizeof(int));
-		    }
-		    break;
-		case t_uinteger:
-		    {
-			unsigned int tmp = (unsigned int)atoi(valbuf);
-			memcpy(lptr, &tmp, sizeof(unsigned int));
-		    }
-		    break;
-		case t_time:
-#ifdef TIME_ENABLE
-		    {
-			double tmp = iso8601_to_unix(valbuf);
-			memcpy(lptr, &tmp, sizeof(double));
-		    }
-#endif /* TIME_ENABLE */
-		    break;
-		case t_real:
-		    {
-			double tmp = atof(valbuf);
-			memcpy(lptr, &tmp, sizeof(double));
-		    }
-		    break;
-		case t_string:
-		    if (parent != NULL
-			&& parent->element_type != t_structobject
-			&& offset > 0)
-			return JSON_ERR_NOPARSTR;
-		    (void)strncpy(lptr, valbuf, cursor->len);
-		    valbuf[sizeof(valbuf)-1] = '\0';
-		    break;
-		case t_boolean:
-		    {
-			bool tmp = (strcmp(valbuf, "true") == 0);
-			memcpy(lptr, &tmp, sizeof(bool));
-		    }
-		    break;
-		case t_character:
-		    if (strlen(valbuf) > 1)
-			/* don't update end here, leave at value start */
-			return JSON_ERR_STRLONG;
-		    else
-			lptr[0] = valbuf[0];
-		    break;
-		case t_ignore:	/* silences a compiler warning */
-		case t_object:	/* silences a compiler warning */
-		case t_structobject:
-		case t_array:
-		    break;
-		case t_check:
-		    if (strcmp(cursor->dflt.check, valbuf) != 0) {
-			json_debug_trace((1,
-					  "Required attribute value %s not present.\n",
-					  cursor->dflt.check));
-			/* don't update end here, leave at start of attribute */
-			return JSON_ERR_CHECKFAIL;
-		    }
-		    break;
-		}
-	    /*@fallthrough@*/
-	case post_array:
-	    if (isspace((unsigned char) *cp))
-		continue;
-	    else if (*cp == ',')
-		state = await_attr;
-	    else if (*cp == '}') {
-		++cp;
-		goto good_parse;
-	    } else {
-		json_debug_trace((1, "Garbage while expecting comma or }\n"));
-		if (end != NULL)
-		    *end = cp;
-		return JSON_ERR_BADTRAIL;
-	    }
-	    break;
-	}
-    }
-
-  good_parse:
-    /* in case there's another object following, consume trailing WS */
-    while (isspace((unsigned char) *cp))
-	++cp;
-    if (end != NULL)
-	*end = cp;
-    json_debug_trace((1, "JSON parse ends.\n"));
-    return 0;
-    /*@ +nullstate +nullderef +mustfreefresh +nullpass +usedef @*/
-}
-
-int json_read_array(const char *cp, const struct json_array_t *arr,
-		    const char **end)
-{
-    /*@-nullstate -onlytrans@*/
-    int substatus, offset, arrcount;
-    char *tp;
-
-    if (end != NULL)
-	*end = NULL;		/* give it a well-defined value on parse failure */
-
-    json_debug_trace((1, "Entered json_read_array()\n"));
-
-    while (isspace((unsigned char) *cp))
-	cp++;
-    if (*cp != '[') {
-	json_debug_trace((1, "Didn't find expected array start\n"));
-	return JSON_ERR_ARRAYSTART;
-    } else
-	cp++;
-
-    tp = arr->arr.strings.store;
-    arrcount = 0;
-
-    /* Check for empty array */
-    while (isspace((unsigned char) *cp))
-	cp++;
-    if (*cp == ']')
-	goto breakout;
-
-    for (offset = 0; offset < arr->maxlen; offset++) {
-	char *ep = NULL;
-	json_debug_trace((1, "Looking at %s\n", cp));
-	switch (arr->element_type) {
-	case t_string:
-	    if (isspace((unsigned char) *cp))
-		cp++;
-	    if (*cp != '"')
-		return JSON_ERR_BADSTRING;
-	    else
-		++cp;
-	    arr->arr.strings.ptrs[offset] = tp;
-	    for (; tp - arr->arr.strings.store < arr->arr.strings.storelen;
-		 tp++)
-		if (*cp == '"') {
-		    ++cp;
-		    *tp++ = '\0';
-		    goto stringend;
-		} else if (*cp == '\0') {
-		    json_debug_trace((1,
-				      "Bad string syntax in string list.\n"));
-		    return JSON_ERR_BADSTRING;
-		} else {
-		    *tp = *cp++;
-		}
-	    json_debug_trace((1, "Bad string syntax in string list.\n"));
-	    return JSON_ERR_BADSTRING;
-	  stringend:
-	    break;
-	case t_object:
-	case t_structobject:
-	    substatus =
-		json_internal_read_object(cp, arr->arr.objects.subtype, arr,
-					  offset, &cp);
-	    if (substatus != 0) {
-		if (end != NULL)
-		    end = &cp;
-		return substatus;
-	    }
-	    break;
-	case t_integer:
-	    arr->arr.integers.store[offset] = (int)strtol(cp, &ep, 0);
-	    if (ep == cp)
-		return JSON_ERR_BADNUM;
-	    else
-		cp = ep;
-	    break;
-	case t_uinteger:
-	    arr->arr.uintegers.store[offset] = (unsigned int)strtoul(cp, &ep, 0);
-	    if (ep == cp)
-		return JSON_ERR_BADNUM;
-	    else
-		cp = ep;
-	    break;
-#ifdef TIME_ENABLE
-	case t_time:
-	    if (*cp != '"')
-		return JSON_ERR_BADSTRING;
-	    else
-		++cp;
-	    arr->arr.reals.store[offset] = iso8601_to_unix((char *)cp);
-	    if (arr->arr.reals.store[offset] >= HUGE_VAL)
-		return JSON_ERR_BADNUM;
-	    while (*cp && *cp != '"')
-		cp++;
-	    if (*cp != '"')
-		return JSON_ERR_BADSTRING;
-	    else
-		++cp; 
-	    break;
-#endif /* TIME_ENABLE */
-	case t_real:
-	    arr->arr.reals.store[offset] = strtod(cp, &ep);
-	    if (ep == cp)
-		return JSON_ERR_BADNUM;
-	    else
-		cp = ep;
-	    break;
-	case t_boolean:
-	    if (strncmp(cp, "true", 4) == 0) {
-		arr->arr.booleans.store[offset] = true;
-		cp += 4;
-	    }
-	    else if (strncmp(cp, "false", 5) == 0) {
-		arr->arr.booleans.store[offset] = false;
-		cp += 5;
-	    }
-	    break;
-	case t_character:
-	case t_array:
-	case t_check:
-	case t_ignore:
-	    json_debug_trace((1, "Invalid array subtype.\n"));
-	    return JSON_ERR_SUBTYPE;
-	}
-	arrcount++;
-	if (isspace((unsigned char) *cp))
-	    cp++;
-	if (*cp == ']') {
-	    json_debug_trace((1, "End of array found.\n"));
-	    goto breakout;
-	} else if (*cp == ',')
-	    cp++;
-	else {
-	    json_debug_trace((1, "Bad trailing syntax on array.\n"));
-	    return JSON_ERR_BADSUBTRAIL;
-	}
-    }
-    json_debug_trace((1, "Too many elements in array.\n"));
-    if (end != NULL)
-	*end = cp;
-    return JSON_ERR_SUBTOOLONG;
-  breakout:
-    if (arr->count != NULL)
-	*(arr->count) = arrcount;
-    if (end != NULL)
-	*end = cp;
-    /*@ -nullderef @*/
-    json_debug_trace((1, "leaving json_read_array() with %d elements\n",
-		      arrcount));
-    /*@ +nullderef @*/
-    return 0;
-    /*@+nullstate +onlytrans@*/
-}
-
-int json_read_object(const char *cp, const struct json_attr_t *attrs,
-		     /*@null@*/ const char **end)
-{
-    int st;
-
-    json_debug_trace((1, "json_read_object() sees '%s'\n", cp));
-    st = json_internal_read_object(cp, attrs, NULL, 0, end);
-    return st;
-}
-
-const /*@observer@*/ char *json_error_string(int err)
-{
-    const char *errors[] = {
-	"unknown error while parsing JSON",
-	"non-whitespace when expecting object start",
-	"non-whitespace when expecting attribute start",
-	"unknown attribute name",
-	"attribute name too long",
-	"saw [ when not expecting array",
-	"array element specified, but no [",
-	"string value too long",
-	"token value too long",
-	"garbage while expecting comma or } or ]",
-	"didn't find expected array start",
-	"error while parsing object array",
-	"too many array elements",
-	"garbage while expecting array comma",
-	"unsupported array element type",
-	"error while string parsing",
-	"check attribute not matched",
-	"can't support strings in parallel arrays",
-	"invalid enumerated value",
-	"saw quoted value when expecting nonstring",
-	"didn't see quoted value when expecting string",
-	"other data conversion error",
-	"unexpected null value or attribute pointer",
-    };
-
-    if (err <= 0 || err >= (int)(sizeof(errors) / sizeof(errors[0])))
-	return errors[0];
-    else
-	return errors[err];
-}
-
-/* end */
-

+ 0 - 141
src/ext_deps/mjson.h

@@ -1,141 +0,0 @@
-/* Structures for JSON parsing using only fixed-extent memory
- *
- * This file is Copyright (c) 2014 by Eric S. Raymond.
- * BSD terms apply: see the file COPYING in the distribution root for details.
- */
-
-#include <stdbool.h>
-#include <ctype.h>
-#include <stdio.h>
-#include <sys/types.h>
-
-#define NITEMS(x) (int)(sizeof(x)/sizeof(x[0]))
-
-typedef enum {t_integer, t_uinteger, t_real,
-	      t_string, t_boolean, t_character,
-	      t_time,
-	      t_object, t_structobject, t_array,
-	      t_check, t_ignore} 
-    json_type;
-
-struct json_enum_t {
-    char	*name;
-    int		value;
-};
-
-struct json_array_t {
-    json_type element_type;
-    union {
-	struct {
-	    const struct json_attr_t *subtype;
-	    char *base;
-	    size_t stride;
-	} objects;
-	struct {
-	    char **ptrs;
-	    char *store;
-	    int storelen;
-	} strings;
-	struct {
-	    int *store;
-	} integers;
-	struct {
-	    unsigned int *store;
-	} uintegers;
-	struct {
-	    double *store;
-	} reals;
-	struct {
-	    bool *store;
-	} booleans;
-    } arr;
-    int *count, maxlen;
-};
-
-struct json_attr_t {
-    char *attribute;
-    json_type type;
-    union {
-	int *integer;
-	unsigned int *uinteger;
-	double *real;
-	char *string;
-	bool *boolean;
-	char *character;
-	struct json_array_t array;
-	size_t offset;
-    } addr;
-    union {
-	int integer;
-	unsigned int uinteger;
-	double real;
-	bool boolean;
-	char character;
-	char *check;
-    } dflt;
-    size_t len;
-    const struct json_enum_t *map;
-    bool nodefault;
-};
-
-#define JSON_ATTR_MAX	31	/* max chars in JSON attribute name */
-#define JSON_VAL_MAX	512	/* max chars in JSON value part */
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-int json_read_object(const char *, const struct json_attr_t *,
-		     /*@null@*/const char **);
-int json_read_array(const char *, const struct json_array_t *,
-		    /*@null@*/const char **);
-const /*@observer@*/char *json_error_string(int);
-
-void json_enable_debug(int, FILE *);
-#ifdef __cplusplus
-}
-#endif
-
-#define JSON_ERR_OBSTART	1	/* non-WS when expecting object start */
-#define JSON_ERR_ATTRSTART	2	/* non-WS when expecting attrib start */
-#define JSON_ERR_BADATTR	3	/* unknown attribute name */
-#define JSON_ERR_ATTRLEN	4	/* attribute name too long */
-#define JSON_ERR_NOARRAY	5	/* saw [ when not expecting array */
-#define JSON_ERR_NOBRAK 	6	/* array element specified, but no [ */
-#define JSON_ERR_STRLONG	7	/* string value too long */
-#define JSON_ERR_TOKLONG	8	/* token value too long */
-#define JSON_ERR_BADTRAIL	9	/* garbage while expecting comma or } or ] */
-#define JSON_ERR_ARRAYSTART	10	/* didn't find expected array start */
-#define JSON_ERR_OBJARR 	11	/* error while parsing object array */
-#define JSON_ERR_SUBTOOLONG	12	/* too many array elements */
-#define JSON_ERR_BADSUBTRAIL	13	/* garbage while expecting array comma */
-#define JSON_ERR_SUBTYPE	14	/* unsupported array element type */
-#define JSON_ERR_BADSTRING	15	/* error while string parsing */
-#define JSON_ERR_CHECKFAIL	16	/* check attribute not matched */
-#define JSON_ERR_NOPARSTR	17	/* can't support strings in parallel arrays */
-#define JSON_ERR_BADENUM	18	/* invalid enumerated value */
-#define JSON_ERR_QNONSTRING	19	/* saw quoted value when expecting nonstring */
-#define JSON_ERR_NONQSTRING	19	/* didn't see quoted value when expecting string */
-#define JSON_ERR_MISC		20	/* other data conversion error */
-#define JSON_ERR_BADNUM		21	/* error while parsing a numerical argument */
-#define JSON_ERR_NULLPTR	22	/* unexpected null value or attribute pointer */
-
-/*
- * Use the following macros to declare template initializers for structobject
- * arrays.  Writing the equivalents out by hand is error-prone.
- *
- * STRUCTOBJECT takes a structure name s, and a fieldname f in s.
- *
- * STRUCTARRAY takes the name of a structure array, a pointer to a an
- * initializer defining the subobject type, and the address of an integer to
- * store the length in.
- */
-#define STRUCTOBJECT(s, f)	.addr.offset = offsetof(s, f)
-#define STRUCTARRAY(a, e, n) \
-	.addr.array.element_type = t_structobject, \
-	.addr.array.arr.objects.subtype = e, \
-	.addr.array.arr.objects.base = (char*)a, \
-	.addr.array.arr.objects.stride = sizeof(a[0]), \
-	.addr.array.count = n, \
-	.addr.array.maxlen = (int)(sizeof(a)/sizeof(a[0]))
-
-/* json.h ends here */

+ 376 - 0
src/ext_deps/nxjson.c

@@ -0,0 +1,376 @@
+/*
+ * nxjson
+ * Copyright 2018 Yaroslav Stavnichiy <yarosla@gmail.com>
+ * SPDX-License-Identifier: MIT
+ * Explicit permission to use this project
+ * under the MIT license has been given by Yaroslav Stavnichiy
+ * on Sun, May 20, 2018 at 1:29 PM (CET). Original license is LGPL v3
+ */
+// this file can be #included in your code
+#ifndef NXJSON_C
+#define NXJSON_C
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "nxjson.h"
+
+// redefine NX_JSON_CALLOC & NX_JSON_FREE to use custom allocator
+#ifndef NX_JSON_CALLOC
+#define NX_JSON_CALLOC() calloc(1, sizeof(nx_json))
+#define NX_JSON_FREE(json) free((void*)(json))
+#endif
+
+// redefine NX_JSON_REPORT_ERROR to use custom error reporting
+#ifndef NX_JSON_REPORT_ERROR
+#define NX_JSON_REPORT_ERROR(msg, p) fprintf(stderr, "NXJSON PARSE ERROR (%d): " msg " at %s\n", __LINE__, p)
+#endif
+
+#define IS_WHITESPACE(c) ((unsigned char)(c)<=(unsigned char)' ')
+
+static const nx_json dummy={ NX_JSON_NULL };
+
+static nx_json* create_json(nx_json_type type, const char* key, nx_json* parent) {
+  nx_json* js=NX_JSON_CALLOC();
+  assert(js);
+  js->type=type;
+  js->key=key;
+  if (!parent->last_child) {
+    parent->child=parent->last_child=js;
+  }
+  else {
+    parent->last_child->next=js;
+    parent->last_child=js;
+  }
+  parent->length++;
+  return js;
+}
+
+void nx_json_free(const nx_json* js) {
+  nx_json* p=js->child;
+  nx_json* p1;
+  while (p) {
+    p1=p->next;
+    nx_json_free(p);
+    p=p1;
+  }
+  NX_JSON_FREE(js);
+}
+
+static int unicode_to_utf8(unsigned int codepoint, char* p, char** endp) {
+  // code from http://stackoverflow.com/a/4609989/697313
+  if (codepoint<0x80) *p++=codepoint;
+  else if (codepoint<0x800) *p++=192+codepoint/64, *p++=128+codepoint%64;
+  else if (codepoint-0xd800u<0x800) return 0; // surrogate must have been treated earlier
+  else if (codepoint<0x10000) *p++=224+codepoint/4096, *p++=128+codepoint/64%64, *p++=128+codepoint%64;
+  else if (codepoint<0x110000) *p++=240+codepoint/262144, *p++=128+codepoint/4096%64, *p++=128+codepoint/64%64, *p++=128+codepoint%64;
+  else return 0; // error
+  *endp=p;
+  return 1;
+}
+
+nx_json_unicode_encoder nx_json_unicode_to_utf8=unicode_to_utf8;
+
+static inline int hex_val(char c) {
+  if (c>='0' && c<='9') return c-'0';
+  if (c>='a' && c<='f') return c-'a'+10;
+  if (c>='A' && c<='F') return c-'A'+10;
+  return -1;
+}
+
+static char* unescape_string(char* s, char** end, nx_json_unicode_encoder encoder) {
+  char* p=s;
+  char* d=s;
+  char c;
+  while ((c=*p++)) {
+    if (c=='"') {
+      *d='\0';
+      *end=p;
+      return s;
+    }
+    else if (c=='\\') {
+      switch (*p) {
+        case '\\':
+        case '/':
+        case '"':
+          *d++=*p++;
+          break;
+        case 'b':
+          *d++='\b'; p++;
+          break;
+        case 'f':
+          *d++='\f'; p++;
+          break;
+        case 'n':
+          *d++='\n'; p++;
+          break;
+        case 'r':
+          *d++='\r'; p++;
+          break;
+        case 't':
+          *d++='\t'; p++;
+          break;
+        case 'u': // unicode
+          if (!encoder) {
+            // leave untouched
+            *d++=c;
+            break;
+          }
+          char* ps=p-1;
+          int h1, h2, h3, h4;
+          if ((h1=hex_val(p[1]))<0 || (h2=hex_val(p[2]))<0 || (h3=hex_val(p[3]))<0 || (h4=hex_val(p[4]))<0) {
+            NX_JSON_REPORT_ERROR("invalid unicode escape", p-1);
+            return 0;
+          }
+          unsigned int codepoint=h1<<12|h2<<8|h3<<4|h4;
+          if ((codepoint & 0xfc00)==0xd800) { // high surrogate; need one more unicode to succeed
+            p+=6;
+            if (p[-1]!='\\' || *p!='u' || (h1=hex_val(p[1]))<0 || (h2=hex_val(p[2]))<0 || (h3=hex_val(p[3]))<0 || (h4=hex_val(p[4]))<0) {
+              NX_JSON_REPORT_ERROR("invalid unicode surrogate", ps);
+              return 0;
+            }
+            unsigned int codepoint2=h1<<12|h2<<8|h3<<4|h4;
+            if ((codepoint2 & 0xfc00)!=0xdc00) {
+              NX_JSON_REPORT_ERROR("invalid unicode surrogate", ps);
+              return 0;
+            }
+            codepoint=0x10000+((codepoint-0xd800)<<10)+(codepoint2-0xdc00);
+          }
+          if (!encoder(codepoint, d, &d)) {
+            NX_JSON_REPORT_ERROR("invalid codepoint", ps);
+            return 0;
+          }
+          p+=5;
+          break;
+        default:
+          // leave untouched
+          *d++=c;
+          break;
+      }
+    }
+    else {
+      *d++=c;
+    }
+  }
+  NX_JSON_REPORT_ERROR("no closing quote for string", s);
+  return 0;
+}
+
+static char* skip_block_comment(char* p) {
+  // assume p[-2]=='/' && p[-1]=='*'
+  char* ps=p-2;
+  if (!*p) {
+    NX_JSON_REPORT_ERROR("endless comment", ps);
+    return 0;
+  }
+  REPEAT:
+  p=strchr(p+1, '/');
+  if (!p) {
+    NX_JSON_REPORT_ERROR("endless comment", ps);
+    return 0;
+  }
+  if (p[-1]!='*') goto REPEAT;
+  return p+1;
+}
+
+static char* parse_key(const char** key, char* p, nx_json_unicode_encoder encoder) {
+  // on '}' return with *p=='}'
+  char c;
+  while ((c=*p++)) {
+    if (c=='"') {
+      *key=unescape_string(p, &p, encoder);
+      if (!*key) return 0; // propagate error
+      while (*p && IS_WHITESPACE(*p)) p++;
+      if (*p==':') return p+1;
+      NX_JSON_REPORT_ERROR("unexpected chars", p);
+      return 0;
+    }
+    else if (IS_WHITESPACE(c) || c==',') {
+      // continue
+    }
+    else if (c=='}') {
+      return p-1;
+    }
+    else if (c=='/') {
+      if (*p=='/') { // line comment
+        char* ps=p-1;
+        p=strchr(p+1, '\n');
+        if (!p) {
+          NX_JSON_REPORT_ERROR("endless comment", ps);
+          return 0; // error
+        }
+        p++;
+      }
+      else if (*p=='*') { // block comment
+        p=skip_block_comment(p+1);
+        if (!p) return 0;
+      }
+      else {
+        NX_JSON_REPORT_ERROR("unexpected chars", p-1);
+        return 0; // error
+      }
+    }
+    else {
+      NX_JSON_REPORT_ERROR("unexpected chars", p-1);
+      return 0; // error
+    }
+  }
+  NX_JSON_REPORT_ERROR("unexpected chars", p-1);
+  return 0; // error
+}
+
+static char* parse_value(nx_json* parent, const char* key, char* p, nx_json_unicode_encoder encoder) {
+  nx_json* js;
+  while (1) {
+    switch (*p) {
+      case '\0':
+        NX_JSON_REPORT_ERROR("unexpected end of text", p);
+        return 0; // error
+      case ' ': case '\t': case '\n': case '\r':
+      case ',':
+        // skip
+        p++;
+        break;
+      case '{':
+        js=create_json(NX_JSON_OBJECT, key, parent);
+        p++;
+        while (1) {
+          const char* new_key;
+          p=parse_key(&new_key, p, encoder);
+          if (!p) return 0; // error
+          if (*p=='}') return p+1; // end of object
+          p=parse_value(js, new_key, p, encoder);
+          if (!p) return 0; // error
+        }
+      case '[':
+        js=create_json(NX_JSON_ARRAY, key, parent);
+        p++;
+        while (1) {
+          p=parse_value(js, 0, p, encoder);
+          if (!p) return 0; // error
+          if (*p==']') return p+1; // end of array
+        }
+      case ']':
+        return p;
+      case '"':
+        p++;
+        js=create_json(NX_JSON_STRING, key, parent);
+        js->text_value=unescape_string(p, &p, encoder);
+        if (!js->text_value) return 0; // propagate error
+        return p;
+      case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
+        {
+          js=create_json(NX_JSON_INTEGER, key, parent);
+          char* pe;
+          js->int_value=strtoll(p, &pe, 0);
+          if (pe==p || errno==ERANGE) {
+            NX_JSON_REPORT_ERROR("invalid number", p);
+            return 0; // error
+          }
+          if (*pe=='.' || *pe=='e' || *pe=='E') { // double value
+            js->type=NX_JSON_DOUBLE;
+            js->dbl_value=strtod(p, &pe);
+            if (pe==p || errno==ERANGE) {
+              NX_JSON_REPORT_ERROR("invalid number", p);
+              return 0; // error
+            }
+          }
+          else {
+            js->dbl_value=js->int_value;
+          }
+          return pe;
+        }
+      case 't':
+        if (!strncmp(p, "true", 4)) {
+          js=create_json(NX_JSON_BOOL, key, parent);
+          js->int_value=1;
+          return p+4;
+        }
+        NX_JSON_REPORT_ERROR("unexpected chars", p);
+        return 0; // error
+      case 'f':
+        if (!strncmp(p, "false", 5)) {
+          js=create_json(NX_JSON_BOOL, key, parent);
+          js->int_value=0;
+          return p+5;
+        }
+        NX_JSON_REPORT_ERROR("unexpected chars", p);
+        return 0; // error
+      case 'n':
+        if (!strncmp(p, "null", 4)) {
+          create_json(NX_JSON_NULL, key, parent);
+          return p+4;
+        }
+        NX_JSON_REPORT_ERROR("unexpected chars", p);
+        return 0; // error
+      case '/': // comment
+        if (p[1]=='/') { // line comment
+          char* ps=p;
+          p=strchr(p+2, '\n');
+          if (!p) {
+            NX_JSON_REPORT_ERROR("endless comment", ps);
+            return 0; // error
+          }
+          p++;
+        }
+        else if (p[1]=='*') { // block comment
+          p=skip_block_comment(p+2);
+          if (!p) return 0;
+        }
+        else {
+          NX_JSON_REPORT_ERROR("unexpected chars", p);
+          return 0; // error
+        }
+        break;
+      default:
+        NX_JSON_REPORT_ERROR("unexpected chars", p);
+        return 0; // error
+    }
+  }
+}
+
+const nx_json* nx_json_parse_utf8(char* text) {
+  return nx_json_parse(text, unicode_to_utf8);
+}
+
+const nx_json* nx_json_parse(char* text, nx_json_unicode_encoder encoder) {
+  nx_json js={0};
+  if (!parse_value(&js, 0, text, encoder)) {
+    if (js.child) nx_json_free(js.child);
+    return 0;
+  }
+  return js.child;
+}
+
+const nx_json* nx_json_get(const nx_json* json, const char* key) {
+  if (!json || !key) return &dummy; // never return null
+  nx_json* js;
+  for (js=json->child; js; js=js->next) {
+    if (js->key && !strcmp(js->key, key)) return js;
+  }
+  return &dummy; // never return null
+}
+
+const nx_json* nx_json_item(const nx_json* json, int idx) {
+  if (!json) return &dummy; // never return null
+  nx_json* js;
+  for (js=json->child; js; js=js->next) {
+    if (!idx--) return js;
+  }
+  return &dummy; // never return null
+}
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* NXJSON_C */

+ 55 - 0
src/ext_deps/nxjson.h

@@ -0,0 +1,55 @@
+/*
+ * nxjson
+ * Copyright 2018 Yaroslav Stavnichiy <yarosla@gmail.com>
+ * SPDX-License-Identifier: MIT
+ * Explicit permission to use this project
+ * under the MIT license has been given by Yaroslav Stavnichiy
+ * on Sun, May 20, 2018 at 1:29 PM (CET). Original license is LGPL v3
+ */
+
+#ifndef NXJSON_H
+#define NXJSON_H
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+
+typedef enum nx_json_type {
+  NX_JSON_NULL,    // this is null value
+  NX_JSON_OBJECT,  // this is an object; properties can be found in child nodes
+  NX_JSON_ARRAY,   // this is an array; items can be found in child nodes
+  NX_JSON_STRING,  // this is a string; value can be found in text_value field
+  NX_JSON_INTEGER, // this is an integer; value can be found in int_value field
+  NX_JSON_DOUBLE,  // this is a double; value can be found in dbl_value field
+  NX_JSON_BOOL     // this is a boolean; value can be found in int_value field
+} nx_json_type;
+
+typedef struct nx_json {
+  nx_json_type type;       // type of json node, see above
+  const char* key;         // key of the property; for object's children only
+  const char* text_value;  // text value of STRING node
+  long long int_value;     // the value of INTEGER or BOOL node
+  double dbl_value;        // the value of DOUBLE node
+  int length;              // number of children of OBJECT or ARRAY
+  struct nx_json* child;   // points to first child
+  struct nx_json* next;    // points to next child
+  struct nx_json* last_child;
+} nx_json;
+
+typedef int (*nx_json_unicode_encoder)(unsigned int codepoint, char* p, char** endp);
+
+extern nx_json_unicode_encoder nx_json_unicode_to_utf8;
+
+const nx_json* nx_json_parse(char* text, nx_json_unicode_encoder encoder);
+const nx_json* nx_json_parse_utf8(char* text);
+void nx_json_free(const nx_json* js);
+const nx_json* nx_json_get(const nx_json* json, const char* key); // get object's property by key
+const nx_json* nx_json_item(const nx_json* json, int idx); // get array element by index
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* NXJSON_H */