implemented json parsing, fixed litterally 200 errors since the IDE did not display them until now (I was already suspicious because everything went so well), I even fixed the library, some functionality is now commented since there are still errors. I marked them with a //TODO (mainly splitflap_wrapper.c) to conclude: it now builds againmain
parent
6b7d7e0635
commit
1aa3a601d7
@ -0,0 +1,7 @@ |
|||||||
|
idf_component_register( |
||||||
|
SRCS |
||||||
|
"microjson-1.6/mjson.c" |
||||||
|
|
||||||
|
INCLUDE_DIRS |
||||||
|
"./microjson-1.6" |
||||||
|
) |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
BSD LICENSE |
||||||
|
|
||||||
|
Copyright (c) 2015, Eric S. Raymond |
||||||
|
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. |
||||||
|
|
||||||
|
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. |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
# Makefile for the microjson project
|
||||||
|
|
||||||
|
# The version for release is derived from the mostt recent stanza in the news file.
|
||||||
|
VERSION=$(shell sed -n <NEWS.adoc '/::/s/^\([0-9][^:]*\).*/\1/p' | head -1)
|
||||||
|
|
||||||
|
CFLAGS = -O
|
||||||
|
|
||||||
|
# Add DEBUG_ENABLE for the tracing code
|
||||||
|
CFLAGS += -DDEBUG_ENABLE -g
|
||||||
|
# Add TIME_ENABLE to support RFC3339 time literals
|
||||||
|
CFLAGS += -DTIME_ENABLE
|
||||||
|
|
||||||
|
all: mjson.o test_microjson example1 example2 example3 example4 |
||||||
|
|
||||||
|
mjson.o: mjson.c mjson.h |
||||||
|
|
||||||
|
test_microjson: test_microjson.o mjson.o |
||||||
|
$(CC) $(CFLAGS) -o test_microjson test_microjson.o mjson.o
|
||||||
|
|
||||||
|
test_microjson_wignore: test_microjson_wignore.o mjson.o |
||||||
|
$(CC) $(CFLAGS) -o test_microjson_wignore test_microjson_wignore.o mjson.o
|
||||||
|
|
||||||
|
.SUFFIXES: .html .adoc .3 |
||||||
|
|
||||||
|
# Requires asciidoc and xsltproc/docbook stylesheets.
|
||||||
|
.adoc.html: |
||||||
|
asciidoc $*.adoc
|
||||||
|
.adoc.3: |
||||||
|
a2x --doctype manpage --format manpage $*.adoc
|
||||||
|
|
||||||
|
# Regression test
|
||||||
|
check: test_microjson test_microjson_wignore |
||||||
|
./test_microjson
|
||||||
|
./test_microjson_wignore
|
||||||
|
|
||||||
|
# Worked examples. These are essentially subsets of the regression test.
|
||||||
|
example1: example1.c mjson.c mjson.h |
||||||
|
example2: example2.c mjson.c mjson.h |
||||||
|
example3: example3.c mjson.c mjson.h |
||||||
|
example4: example4.c mjson.c mjson.h |
||||||
|
|
||||||
|
clean: |
||||||
|
rm -f mjson.o test_microjson.o test_microjson test_microjson_wignore example[1234]
|
||||||
|
rm -f microjson.html mjson.html
|
||||||
|
|
||||||
|
version: |
||||||
|
@echo $(VERSION)
|
||||||
|
|
||||||
|
SUPPRESSIONS = --suppress=unusedStructMember --suppress=unreadVariable
|
||||||
|
SUPPRESSIONS += -U__UNUSED__
|
||||||
|
cppcheck: |
||||||
|
cppcheck -I. --template gcc --enable=all $(SUPPRESSIONS) *.[ch]
|
||||||
|
|
||||||
|
SOURCES = Makefile *.[ch]
|
||||||
|
DOCS = README.adoc COPYING NEWS.adoc control microjson.adoc mjson.adoc
|
||||||
|
ALL = $(SOURCES) $(DOCS)
|
||||||
|
microjson-$(VERSION).tar.gz: $(ALL) |
||||||
|
tar --transform='s:^:microjson-$(VERSION)/:' --show-transformed-names -cvzf microjson-$(VERSION).tar.gz $(ALL)
|
||||||
|
|
||||||
|
dist: microjson-$(VERSION).tar.gz |
||||||
|
|
||||||
|
release: microjson-$(VERSION).tar.gz microjson.html mjson.html |
||||||
|
shipper version=$(VERSION) | sh -e -x
|
||||||
|
|
||||||
|
refresh: microjson.html mjson.html |
||||||
|
shipper -N -w version=$(VERSION) | sh -e -x
|
||||||
@ -0,0 +1,24 @@ |
|||||||
|
= microjson project news = |
||||||
|
|
||||||
|
1.6: 2020-07-12:: |
||||||
|
It's now possible to match all previously unspecified fields ignored. |
||||||
|
Whitespace after values is now skpped properly in more circumstances. |
||||||
|
Integer values 0 and 1 are accepted for bools. |
||||||
|
|
||||||
|
1.5: 2019-03-06:: |
||||||
|
It's now possible to read objects inside of objects (not just in arrays). |
||||||
|
|
||||||
|
1.4: 2018-08-09:: |
||||||
|
Add a belt-and suspenders bounds check side-ported from the GPSD code. |
||||||
|
|
||||||
|
1.3: 2014-09-28:: |
||||||
|
Add a time_t type that is an RFC3339 date, stored as Unix seconds. |
||||||
|
|
||||||
|
1.2: 2014-09-27:: |
||||||
|
Added a manual page. |
||||||
|
|
||||||
|
1.1: 2014-09-26:: |
||||||
|
Documentation improvements basde on initial feedback. |
||||||
|
|
||||||
|
1.0: 2014-09-25:: |
||||||
|
Initial release. |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
= README for microjson = |
||||||
|
|
||||||
|
This is a tiny JSON parser in C that uses only fixed-extent storage. |
||||||
|
|
||||||
|
This code parses the largest subset of JSON that can be unpacked into |
||||||
|
static C structures, without using malloc(3). It is very small and |
||||||
|
very low-footprint, intended for use in constrained embedded |
||||||
|
environments. It may also be of interest for code where malloc is |
||||||
|
verboten to reduce reliability and security vulnerabilities. |
||||||
|
|
||||||
|
Apologies about the name collision; I didn't think to check for other |
||||||
|
projects with this name when I first created it. |
||||||
|
|
||||||
|
There's a manual page, link:mjson.html[], and a tutorial: link:microjson.html[] |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
# This is not a real Debian control file |
||||||
|
# It's project metadata for the shipper tool |
||||||
|
|
||||||
|
Package: microjson |
||||||
|
|
||||||
|
Description: Tiny JSON parser in C that uses only fixed-extent storage. |
||||||
|
This code parses the largest subset of JSON that can be unpacked into static C |
||||||
|
structures, without using malloc(3). It is very small and very low-footprint, |
||||||
|
intended for use in constrained embedded environments. It may also be of |
||||||
|
interest for code where malloc is verboten to reduce reliability and security |
||||||
|
vulnerabilities. |
||||||
|
|
||||||
|
Homepage: http://www.catb.org/~esr/microjson |
||||||
|
|
||||||
|
XBS-HTML-Target: index.html |
||||||
|
|
||||||
|
XBS-Repository-URL: https://gitlab.com/esr/microjson |
||||||
|
|
||||||
|
#XBS-Project-Tags: JSON, C |
||||||
|
|
||||||
|
XBS-VC-Tag-Template: %(version)s |
||||||
|
|
||||||
@ -0,0 +1,34 @@ |
|||||||
|
/* example1.c - first example in the programming guide
|
||||||
|
* |
||||||
|
* This file is Copyright (c) 2014 by Eric S. Raymond |
||||||
|
* SPDX-License-Identifier: BSD-2-Clause |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <stdbool.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
static bool flag1, flag2; |
||||||
|
static int count; |
||||||
|
|
||||||
|
static const struct json_attr_t json_attrs[] = { |
||||||
|
{"count", t_integer, .addr.integer = &count}, |
||||||
|
{"flag1", t_boolean, .addr.boolean = &flag1,}, |
||||||
|
{"flag2", t_boolean, .addr.boolean = &flag2,}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int status = 0; |
||||||
|
|
||||||
|
status = json_read_object(argv[1], json_attrs, NULL); |
||||||
|
printf("status = %d, count = %d, flag1 = %d, flag2 = %d\n", |
||||||
|
status, count, flag1, flag2); |
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
} |
||||||
|
|
||||||
|
/* end */ |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
/* example2.c - second example in the programming guide
|
||||||
|
* |
||||||
|
* This file is Copyright (c) 2014 by Eric S. Raymond |
||||||
|
* SPDX-License-Identifier: BSD-2-Clause |
||||||
|
*/ |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
#define MAXCHANNELS 72 |
||||||
|
|
||||||
|
static bool usedflags[MAXCHANNELS]; |
||||||
|
static int PRN[MAXCHANNELS]; |
||||||
|
static int elevation[MAXCHANNELS]; |
||||||
|
static int azimuth[MAXCHANNELS]; |
||||||
|
static int visible; |
||||||
|
|
||||||
|
const struct json_attr_t sat_attrs[] = { |
||||||
|
{"PRN", t_integer, .addr.integer = PRN}, |
||||||
|
{"el", t_integer, .addr.integer = elevation}, |
||||||
|
{"az", t_integer, .addr.integer = azimuth}, |
||||||
|
{"used", t_boolean, .addr.boolean = usedflags}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
const struct json_attr_t json_attrs_sky[] = { |
||||||
|
{"class", t_check, .dflt.check = "SKY"}, |
||||||
|
{"satellites", t_array, .addr.array.element_type = t_object, |
||||||
|
.addr.array.arr.objects.subtype=sat_attrs, |
||||||
|
.addr.array.maxlen = MAXCHANNELS, |
||||||
|
.addr.array.count = &visible}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int i, status = 0; |
||||||
|
|
||||||
|
status = json_read_object(argv[1], json_attrs_sky, NULL); |
||||||
|
printf("%d satellites:\n", visible); |
||||||
|
for (i = 0; i < visible; i++) |
||||||
|
printf("PRN = %d, elevation = %d, azimuth = %d\n",
|
||||||
|
PRN[i], elevation[i], azimuth[i]); |
||||||
|
|
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
} |
||||||
|
|
||||||
|
/* end */ |
||||||
@ -0,0 +1,67 @@ |
|||||||
|
/* example2.c - first example in the programming guide
|
||||||
|
* |
||||||
|
* This file is Copyright (c) 2014 by Eric S. Raymond |
||||||
|
* SPDX-License-Identifier: BSD-2-Clause |
||||||
|
*/ |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <stddef.h> |
||||||
|
#include <limits.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
#define MAXUSERDEVS 4 |
||||||
|
|
||||||
|
struct devconfig_t { |
||||||
|
char path[PATH_MAX]; |
||||||
|
double activated; |
||||||
|
}; |
||||||
|
|
||||||
|
struct devlist_t { |
||||||
|
int ndevices; |
||||||
|
struct devconfig_t list[MAXUSERDEVS]; |
||||||
|
}; |
||||||
|
|
||||||
|
static struct devlist_t devicelist; |
||||||
|
|
||||||
|
static int json_devicelist_read(const char *buf) |
||||||
|
{ |
||||||
|
const struct json_attr_t json_attrs_subdevice[] = { |
||||||
|
{"path", t_string, STRUCTOBJECT(struct devconfig_t, path), |
||||||
|
.len = sizeof(devicelist.list[0].path)}, |
||||||
|
{"activated", t_real, STRUCTOBJECT(struct devconfig_t, activated)}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
const struct json_attr_t json_attrs_devices[] = { |
||||||
|
{"class", t_check,.dflt.check = "DEVICES"}, |
||||||
|
{"devices", t_array, STRUCTARRAY(devicelist.list, |
||||||
|
json_attrs_subdevice, |
||||||
|
&devicelist.ndevices)}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
int status; |
||||||
|
|
||||||
|
memset(&devicelist, '\0', sizeof(devicelist)); |
||||||
|
status = json_read_object(buf, json_attrs_devices, NULL); |
||||||
|
if (status != 0) { |
||||||
|
return status; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int i, status = 0; |
||||||
|
|
||||||
|
status = json_devicelist_read(argv[1]); |
||||||
|
printf("%d devices:\n", devicelist.ndevices); |
||||||
|
for (i = 0; i < devicelist.ndevices; i++) |
||||||
|
printf("%s @ %f\n",
|
||||||
|
devicelist.list[i].path, devicelist.list[i].activated); |
||||||
|
|
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
} |
||||||
|
|
||||||
|
/* end */ |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
/* example4.c - fourth example in the programming guide
|
||||||
|
* |
||||||
|
* This file is Copyright (c) 2020 by Eric S. Raymond |
||||||
|
* SPDX-License-Identifier: BSD-2-Clause |
||||||
|
*/ |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
#define ARR1_LENGTH 8 |
||||||
|
|
||||||
|
static bool flag1; |
||||||
|
static int arr1[ARR1_LENGTH]; |
||||||
|
static int arr1_count; |
||||||
|
|
||||||
|
const struct json_attr_t json_attrs_example4[] = { |
||||||
|
{"flag1", t_boolean, .addr.boolean = &flag1}, |
||||||
|
{"arr1", t_array, .addr.array.element_type = t_integer, |
||||||
|
.addr.array.arr.integers = arr1, |
||||||
|
.addr.array.maxlen = ARR1_LENGTH, |
||||||
|
.addr.array.count = &arr1_count}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
const char* end = (const char*) argv[1] + strlen((const char*) argv[1]); |
||||||
|
const char* cur = (const char*) argv[1]; |
||||||
|
|
||||||
|
while (cur < end) { |
||||||
|
int status = json_read_object(cur, json_attrs_example4, &cur); |
||||||
|
printf("status: %d, flag1: %d\n", status, flag1); |
||||||
|
for (int i = 0; i < arr1_count; i++) |
||||||
|
printf("arr1 = %d\n", arr1[i]); |
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
arr1_count = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* end */ |
||||||
@ -0,0 +1,497 @@ |
|||||||
|
= Building static JSON parsers with microjson = |
||||||
|
Eric S. Raymond <esr@thyrsus.com> |
||||||
|
|
||||||
|
== Overview == |
||||||
|
|
||||||
|
microjson is a tiny parser for the largest subset of JSON (JavaScript Object |
||||||
|
Notation) that can be unpacked to C static storage. It uses entirely |
||||||
|
fixed-extent memory, no malloc(). It is thus very suitable for use in |
||||||
|
memory-constrained environments such as embedded systems; also for |
||||||
|
long-running service daemons that must provably not leak memory. |
||||||
|
|
||||||
|
microjson is extremely well-tested code. This is essentially the same |
||||||
|
parser used in GPSD and its client libraries, which have hundreds of |
||||||
|
millions of deployments underneath Google Maps in Android phones. |
||||||
|
|
||||||
|
microjson parses JSON from string input and unpacks the content |
||||||
|
directly into static storage declared by the calling program. |
||||||
|
You 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. |
||||||
|
|
||||||
|
== How To Use This Document == |
||||||
|
|
||||||
|
This is a fast tutorial for working programmers. It teaches by |
||||||
|
examples; if you read the code carefully it will tell you |
||||||
|
more than the accompanying text. Just read it in sequence, trying not |
||||||
|
to skip anything. |
||||||
|
|
||||||
|
All the examples are shipped in the microjson distribution. Most are |
||||||
|
not synthetic toys, but stripped-down versions of working code from |
||||||
|
GPSD. Copy them freely. You may also want to look at the source for |
||||||
|
test_microjson.c, the regression test; it exercises most cases. |
||||||
|
|
||||||
|
== An Example == |
||||||
|
|
||||||
|
Here is nearly the simplest possible example: |
||||||
|
|
||||||
|
.Example 1 |
||||||
|
--------------------------------------------------------------------- |
||||||
|
/* |
||||||
|
* Parse JSON shaped like '{"flag1":true,"flag2":false,"count":42}' |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <stdbool.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
static bool flag1, flag2; |
||||||
|
static int count; |
||||||
|
|
||||||
|
static const struct json_attr_t json_attrs[] = { |
||||||
|
{"count", t_integer, .addr.integer = &count}, |
||||||
|
{"flag1", t_boolean, .addr.boolean = &flag1,}, |
||||||
|
{"flag2", t_boolean, .addr.boolean = &flag2,}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int status = 0; |
||||||
|
|
||||||
|
status = json_read_object(argv[1], json_attrs, NULL); |
||||||
|
printf("status = %d, count = %d, flag1 = %d, flag2 = %d\n", |
||||||
|
status, count, flag1, flag2); |
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
} |
||||||
|
--------------------------------------------------------------------- |
||||||
|
|
||||||
|
And here are some invocations: |
||||||
|
|
||||||
|
--------------------------------------------------------------------- |
||||||
|
$ example1 '{"flag1":true,"flag2":false,"count":42}' |
||||||
|
status = 0, count = 42, flag1 = 1, flag2 = 0 |
||||||
|
|
||||||
|
$ example1 '{"flag1":true,"flag2":false,"count":23}' |
||||||
|
status = 0, count = 23, flag1 = 1, flag2 = 0 |
||||||
|
|
||||||
|
$ example1 '{"whozis":true,"flag2":false,"count":23}' |
||||||
|
status = 3, count = 0, flag1 = 0, flag2 = 0 |
||||||
|
unknown attribute name |
||||||
|
|
||||||
|
$ example1 '{"flag1":true,"flag2":false,"count":23,"whozis":"whatsis"}' |
||||||
|
status = 3, count = 23, flag1 = 1, flag2 = 0 |
||||||
|
unknown attribute name |
||||||
|
|
||||||
|
--------------------------------------------------------------------- |
||||||
|
|
||||||
|
The +json_read_object()+ call unpacks the values in the argument JSON |
||||||
|
object into three static variables. In many uses the target locations |
||||||
|
would instead be storage in some static structure instance. |
||||||
|
|
||||||
|
In this example, the +json_attrs+ structure array associates each |
||||||
|
possible member name with a type and a target address. The function |
||||||
|
+json_read_object()+ treats this array of constants as parsing |
||||||
|
instructions. |
||||||
|
|
||||||
|
When an unexpected attribute name is encountered, the parser normally |
||||||
|
terminates, returning an error status (but it is possible to mage the |
||||||
|
parser ignore unknown attributes instead). Attributes and values |
||||||
|
parsed before a terminating error modify their target storage. |
||||||
|
|
||||||
|
The parser recognizes a wider range of types than this, and the |
||||||
|
template structures can specify defaults when an expected JSON |
||||||
|
attribute is omitted. Most of the rest is details. |
||||||
|
|
||||||
|
== Theory of Operation == |
||||||
|
|
||||||
|
The parser is a simple state machine that walks the input looking |
||||||
|
for syntactically well-formed attribute-value pairs. Each time it |
||||||
|
finds one, it looks up the name in the template structure array |
||||||
|
driving the parse. The type tells it how to interpret the |
||||||
|
value; the target address tells it where to put the value. |
||||||
|
|
||||||
|
Syntax errors, or any unknown attribute name, terminate the parse. |
||||||
|
That is unless the wildcard ignore option is used. |
||||||
|
|
||||||
|
One consequence to be aware of is that if an input JSON object |
||||||
|
contains multiple attribute-value pairs with the same attribute, |
||||||
|
the associated storage will be modified each time and only |
||||||
|
the last setting will be effective. |
||||||
|
|
||||||
|
=== Simple Value Types === |
||||||
|
|
||||||
|
The type field of a +json_attr_t+ structure can have the following |
||||||
|
'simple' alternatives, each corresponding to an atomic JSON value: |
||||||
|
|
||||||
|
+t_check+: Value of this attribute must match a specified string, |
||||||
|
or the parse will fail with a distinguishable error. |
||||||
|
|
||||||
|
+t_integer+: Parse a single signed integer literal, copy the value |
||||||
|
to a C +int+ location. Uses +strtol()+. |
||||||
|
|
||||||
|
+t_uinteger+: Parse a single signed integer literal, copy the value |
||||||
|
to a C +unsigned int+ location. Uses +strtoul()+. |
||||||
|
|
||||||
|
+t_real+: Parse a single signed float literal, copy the value |
||||||
|
to a C +double+ location. Uses +strtod()+. |
||||||
|
|
||||||
|
+t_boolean+: Accept one of the JSON literals +true+ or +false+, |
||||||
|
copy the value to a C +bool+ location. |
||||||
|
|
||||||
|
+t_string+: Accept a JSON string literal, copy the contents to a |
||||||
|
C char buffer. |
||||||
|
|
||||||
|
+t_character+: Accept a single-character JSON string literal, copy |
||||||
|
that character to a C +char+ location. |
||||||
|
|
||||||
|
+t_time+" Accept a string that is an RFC3339 timestamp (full ISO-8601 |
||||||
|
date/time in Zulu time with optional fractional decimal seconds). |
||||||
|
Store as a double value, seconds since Unix epoch. Accepted only |
||||||
|
if the code was built with -DTIME_ENABLE; introduces a dependency |
||||||
|
on the glibc function timegm(). |
||||||
|
|
||||||
|
Associated with each simple value type's storage (in the +addr+ |
||||||
|
union) is a correspondingly-named field in the +dflt+ union). |
||||||
|
This is a default value which is copied to the target storage |
||||||
|
when the JSON object does not contain the corresponding attribute. |
||||||
|
You can turn off this defaulting behavior by setting the +nodefault+ |
||||||
|
member to +true+. |
||||||
|
|
||||||
|
=== Enumerated-value types === |
||||||
|
|
||||||
|
The parser includes support for string attributes with controlled |
||||||
|
vocabularies. |
||||||
|
|
||||||
|
A +json_attr_t+ instance with a +t_integer+ or +t_uinteger+ type field |
||||||
|
can point at a map (an array of +json_enum_t+ structures) that lists |
||||||
|
names and pairs of integral values. If this is done, the parser |
||||||
|
expects the values of the JSON attribute to be strings but internally |
||||||
|
maps them to corresponding integer values before setting the target |
||||||
|
storage. An un-enumerated string value causes the parse to error out. |
||||||
|
|
||||||
|
(Case 8 in the unit test source code illustrates how to use this feature.) |
||||||
|
|
||||||
|
=== Compound Value Types === |
||||||
|
|
||||||
|
The following cases do not parse JSON value atoms: |
||||||
|
|
||||||
|
==== Skip fields ==== |
||||||
|
|
||||||
|
t_ignore: Value of this attribute is ignored. Significant because |
||||||
|
unexpected attribute names cause the parse to terminate with error. |
||||||
|
An empty attribute name may be used to wildcard ignore all unknown |
||||||
|
fields. This should rarely be used and always as penultimate to the |
||||||
|
terminating NULL. |
||||||
|
|
||||||
|
==== Sub-objects ==== |
||||||
|
|
||||||
|
t_object: It is possible to parse JSON objects within JSON objects. |
||||||
|
See case 14 in the unit test for an example. |
||||||
|
|
||||||
|
==== Parallel arrays === |
||||||
|
|
||||||
|
t_array: Value of this attribute is expected to be a homogenous array. |
||||||
|
Another field of the structure specifies the array's element type, |
||||||
|
which can be any simple type or t_object (meaning a JSON subobject). |
||||||
|
|
||||||
|
If the array has simple elements, three additional things must be |
||||||
|
specified: the base address of the array's storage, the maximum number |
||||||
|
of elements it can have, and an integer address where the parser will |
||||||
|
place a count of elements filled in. |
||||||
|
|
||||||
|
Simple array values always default to zero for numeric types, +false+ |
||||||
|
for booleans, and NULL for strings. |
||||||
|
|
||||||
|
The array element type may be +t_object+, as in the +satellites+ field |
||||||
|
in this example: |
||||||
|
|
||||||
|
.Example 2 |
||||||
|
------------------------------------------------------------------------ |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
#define MAXCHANNELS 72 |
||||||
|
|
||||||
|
static bool usedflags[MAXCHANNELS]; |
||||||
|
static int PRN[MAXCHANNELS]; |
||||||
|
static int elevation[MAXCHANNELS]; |
||||||
|
static int azimuth[MAXCHANNELS]; |
||||||
|
static int visible; |
||||||
|
|
||||||
|
const struct json_attr_t sat_attrs[] = { |
||||||
|
{"PRN", t_integer, .addr.integer = PRN}, |
||||||
|
{"el", t_integer, .addr.integer = elevation}, |
||||||
|
{"az", t_integer, .addr.integer = azimuth}, |
||||||
|
{"used", t_boolean, .addr.boolean = usedflags}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
const struct json_attr_t json_attrs_sky[] = { |
||||||
|
{"class", t_check, .dflt.check = "SKY"}, |
||||||
|
{"satellites", t_array, .addr.array.element_type = t_object, |
||||||
|
.addr.array.arr.objects.subtype=sat_attrs, |
||||||
|
.addr.array.maxlen = MAXCHANNELS, |
||||||
|
.addr.array.count = &visible}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int i, status = 0; |
||||||
|
|
||||||
|
status = json_read_object(argv[1], json_attrs_sky, NULL); |
||||||
|
printf("%d satellites:\n", visible); |
||||||
|
for (i = 0; i < visible; i++) |
||||||
|
printf("PRN = %d, elevation = %d, azimuth = %d\n", |
||||||
|
PRN[i], elevation[i], azimuth[i]); |
||||||
|
|
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
} |
||||||
|
------------------------------------------------------------------------ |
||||||
|
|
||||||
|
Here's an example invocation (string literal folded for readability): |
||||||
|
|
||||||
|
-------------------------------------------------------- |
||||||
|
$ example2 '{"class":"SKY","satellites": |
||||||
|
[{"PRN":10,"el":45,"az":196,"used":true}, |
||||||
|
{"PRN":29,"el":67,"az":310,"used":true}]}' |
||||||
|
2 satellites: |
||||||
|
PRN = 10, elevation = 45, azimuth = 196 |
||||||
|
PRN = 29, elevation = 67, azimuth = 310 |
||||||
|
-------------------------------------------------------- |
||||||
|
|
||||||
|
In this case, the parser needs to be told where to find a template |
||||||
|
array describing how to parse the element objects. The target addresses |
||||||
|
in this structure will point to the base addressees of parallel arrays. |
||||||
|
The arrays are filled in until the parser runs out of conforming JSON |
||||||
|
sub-objects to parse or would exceed the +maxlen+ count of elements. |
||||||
|
|
||||||
|
More formally: parallel 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" fixed size to use as an offset multiplier. |
||||||
|
|
||||||
|
The default of array elements is always zero (false for booleans, NULL |
||||||
|
for strings). |
||||||
|
|
||||||
|
==== Structure arrays ==== |
||||||
|
|
||||||
|
There's a different way to parse arrays that can unpack an |
||||||
|
array of JSON objects directly into an array of C structs. |
||||||
|
|
||||||
|
.Example 3: |
||||||
|
-------------------------------------------------------- |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <getopt.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <stddef.h> |
||||||
|
#include <limits.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
#define MAXUSERDEVS 4 |
||||||
|
|
||||||
|
struct devconfig_t { |
||||||
|
char path[PATH_MAX]; |
||||||
|
double activated; |
||||||
|
}; |
||||||
|
|
||||||
|
struct devlist_t { |
||||||
|
int ndevices; |
||||||
|
struct devconfig_t list[MAXUSERDEVS]; |
||||||
|
}; |
||||||
|
|
||||||
|
static struct devlist_t devicelist; |
||||||
|
|
||||||
|
static int json_devicelist_read(const char *buf) |
||||||
|
{ |
||||||
|
const struct json_attr_t json_attrs_subdevice[] = { |
||||||
|
{"path", t_string, STRUCTOBJECT(struct devconfig_t, path), |
||||||
|
.len = sizeof(devicelist.list[0].path)}, |
||||||
|
{"activated", t_real, STRUCTOBJECT(struct devconfig_t, activated)}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
const struct json_attr_t json_attrs_devices[] = { |
||||||
|
{"class", t_check,.dflt.check = "DEVICES"}, |
||||||
|
{"devices", t_array, STRUCTARRAY(devicelist.list, |
||||||
|
json_attrs_subdevice, |
||||||
|
&devicelist.ndevices)}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
int status; |
||||||
|
|
||||||
|
memset(&devicelist, '\0', sizeof(devicelist)); |
||||||
|
status = json_read_object(buf, json_attrs_devices, NULL); |
||||||
|
if (status != 0) { |
||||||
|
return status; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int i, status = 0; |
||||||
|
|
||||||
|
status = json_devicelist_read(argv[1]); |
||||||
|
printf("%d devices:\n", devicelist.ndevices); |
||||||
|
for (i = 0; i < devicelist.ndevices; i++) |
||||||
|
printf("%s @ %f\n", |
||||||
|
devicelist.list[i].path, devicelist.list[i].activated); |
||||||
|
|
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
} |
||||||
|
-------------------------------------------------------- |
||||||
|
|
||||||
|
Here is an example: |
||||||
|
|
||||||
|
-------------------------------------------------------- |
||||||
|
$ example3 '{"devices":[{"path":"/dev/ttyUSB0", |
||||||
|
"activated":1411468340}]}' |
||||||
|
1 devices: |
||||||
|
/dev/ttyUSB0 @ 1411468340.000000 |
||||||
|
-------------------------------------------------------- |
||||||
|
|
||||||
|
In this case, the STRUCTARRAY and STRUCTOBJECT macros are clues to |
||||||
|
what is going on. STRUCTOBJECT is a thin wrapper around offsetof(); |
||||||
|
STRUCTARRAY sets up the parser to walk through the array of |
||||||
|
structures, filling each element as it goes. |
||||||
|
|
||||||
|
More formally: structobject 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. |
||||||
|
|
||||||
|
== Parsing Concatenated Objects == |
||||||
|
|
||||||
|
The +end+ param of +json_read_object()+ can be re-used as the +cp+ param |
||||||
|
to the same. As a result, a simple loop can be used to parse streamed or |
||||||
|
concatenated root level JSON objects. |
||||||
|
|
||||||
|
.Example 4 |
||||||
|
-------------------------------------------------------- |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
#define ARR1_LENGTH 8 |
||||||
|
|
||||||
|
static bool flag1; |
||||||
|
static int arr1[ARR1_LENGTH]; |
||||||
|
static int arr1_count; |
||||||
|
|
||||||
|
const struct json_attr_t json_attrs_example4[] = { |
||||||
|
{"flag1", t_boolean, .addr.boolean = &flag1}, |
||||||
|
{"arr1", t_array, .addr.array.element_type = t_integer, |
||||||
|
.addr.array.arr.integers = arr1, |
||||||
|
.addr.array.maxlen = ARR1_LENGTH, |
||||||
|
.addr.array.count = &arr1_count}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int i, status = 0; |
||||||
|
|
||||||
|
const char* end = (const char*) argv[1] + strlen((const char*) argv[1]); |
||||||
|
const char* cur = (const char*) argv[1]; |
||||||
|
|
||||||
|
while (cur < end) { |
||||||
|
status = json_read_object(cur, json_attrs_example4, &cur); |
||||||
|
printf("status: %d, flag1: %d\n", status, flag1); |
||||||
|
for (i = 0; i < arr1_count; i++) |
||||||
|
printf("arr1 = %d\n", arr1[i]); |
||||||
|
if (status != 0) |
||||||
|
puts(json_error_string(status)); |
||||||
|
arr1_count = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
-------------------------------------------------------- |
||||||
|
|
||||||
|
Here is an example: |
||||||
|
|
||||||
|
-------------------------------------------------------- |
||||||
|
$ ./example4 '{"flag1":true} {"flag1":0,"arr1":[10,20]} |
||||||
|
{"flag1":1} {"flag1":7, "arr1":[30,40,50]' |
||||||
|
status: 0, flag1: 1 |
||||||
|
status: 0, flag1: 0 |
||||||
|
arr1 = 10 |
||||||
|
arr1 = 20 |
||||||
|
status: 0, flag1: 1 |
||||||
|
status: 0, flag1: 1 |
||||||
|
arr1 = 30 |
||||||
|
arr1 = 40 |
||||||
|
arr1 = 50 |
||||||
|
-------------------------------------------------------- |
||||||
|
|
||||||
|
(Test case 18 also illustrates how to use this feature.) |
||||||
|
|
||||||
|
== Some Grubby Details == |
||||||
|
|
||||||
|
You have to specify the shape of the JSON you expect to parse in advance. |
||||||
|
|
||||||
|
The "shape" of a JSON object is 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. (There's an instance |
||||||
|
of this in Example 3.) |
||||||
|
|
||||||
|
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, t_character may not be an array element |
||||||
|
(this restriction could be lifted, and might be in a future release). |
||||||
|
Third, both attribute names and string values have hard limits; these |
||||||
|
can be tweaked by modifying the header file. |
||||||
|
|
||||||
|
There are separate entry points for beginning a parse of either a JSON |
||||||
|
object or a JSON array. |
||||||
|
|
||||||
|
JSON "float" quantities are actually stored as doubles. Note that |
||||||
|
float parsing uses +atof(3)+ and is thus locale-sensitive - this |
||||||
|
affects whether period or comma is used as a decimal point. If in any |
||||||
|
doubt, set the C numeric locale explicitly to match your data source. |
||||||
|
|
||||||
|
You should not assume that the numeric values of error codes are |
||||||
|
stable. Use the JSON_ERR_* names, not the numbers. |
||||||
|
|
||||||
|
We have been informed that it is possible to core-dump this code by |
||||||
|
passing NULL or bogus pointers to json_read_object(), so don't do |
||||||
|
that. There's no sanity check against bad arguments in order to |
||||||
|
keep the library small and light. |
||||||
|
|
||||||
|
== Advanced Usage == |
||||||
|
|
||||||
|
This code is designed to be stripped down still further; do not be |
||||||
|
afraid to copy mjson.c and drop out the parts you don't need (but |
||||||
|
please leave in my name somewhere as original author). |
||||||
|
|
||||||
|
It is a good idea, when possible, to generate your parse-template |
||||||
|
structures programmatically from a higher-level description of the |
||||||
|
JSON. GPSD uses this technique extensively. |
||||||
|
|
||||||
|
// end |
||||||
@ -0,0 +1,75 @@ |
|||||||
|
= mjson(3) = |
||||||
|
:doctype: manpage |
||||||
|
|
||||||
|
== NAME == |
||||||
|
mjson - fast parse of JSON to fixed-extent C structures |
||||||
|
|
||||||
|
== FUNCTIONS == |
||||||
|
|
||||||
|
---------------------------------------------------- |
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
int json_read_object(const char *, const struct json_attr_t *, const char **); |
||||||
|
|
||||||
|
int json_read_array(const char *, const struct json_array_t *, const char **); |
||||||
|
|
||||||
|
const char *json_error_string(int); |
||||||
|
|
||||||
|
void json_enable_debug(int, FILE *); |
||||||
|
---------------------------------------------------- |
||||||
|
|
||||||
|
== DESCRIPTION == |
||||||
|
+json_read_object()+ attempts to parse a whole JSON object from the |
||||||
|
buffer pointed at by the first argument. The second argument points |
||||||
|
at an array of template structures describing the expected shape of |
||||||
|
the JSON object (that is, the set of attribute names and their |
||||||
|
expected value types. order-independent) and specifying for each |
||||||
|
attribute a C address where the parsed value should ve deivered. |
||||||
|
The third argument, if non-null, is where a copy of a pointer |
||||||
|
to just past the parsed object is placed. |
||||||
|
|
||||||
|
+json_read_object()+ attempts to parse a whole JSON object from the |
||||||
|
buffer pointed at by the first argument. The second argument points |
||||||
|
at a template structure describing the array's element type and where |
||||||
|
successive elements are to be placed. The third argument, if non-null, |
||||||
|
is where a copy of a pointer to just past the parsed object is placed. |
||||||
|
|
||||||
|
Objects may contain objects or arrays as attribute values, and an |
||||||
|
array may be composed of JSON objects. These functions mutually |
||||||
|
recurse as required. (Arrays within arrays are currently not |
||||||
|
supported; this may change in a future release.) |
||||||
|
|
||||||
|
+void json_enable_debug(int, FILE *)+ enables the generation of trace |
||||||
|
messages to the indicated file pointer while parsing. |
||||||
|
|
||||||
|
For details on how to build template structures, consult the document |
||||||
|
_Building Static JSON Parsers With Microjson_ shipped with the |
||||||
|
distribution. |
||||||
|
|
||||||
|
== RETURN VALUE == |
||||||
|
The two main functions return 0 for success, a nonzero error code for |
||||||
|
failure. The function +json_error_string()+ maps error codes to |
||||||
|
explanatory messages. |
||||||
|
|
||||||
|
When an error is returned and the end pointer (third) argument is |
||||||
|
non-null, it is filled with the value of the buffer pointer at the |
||||||
|
time the error was thrown. |
||||||
|
|
||||||
|
== BUGS == |
||||||
|
A backslash escape of "\0000" in a string value will drop a NUL in the |
||||||
|
string that is likely to confuse C code looking at the storage. In |
||||||
|
general, the high byte of a \uxxxxx escape will be ignored and the |
||||||
|
ASCII high-half byte named by the last two hex digits will be |
||||||
|
inserted in the string. |
||||||
|
|
||||||
|
There is no way to make a parser accept the typeless special JSON |
||||||
|
value "null", because there is nothing in C's type ontology to map it |
||||||
|
to. |
||||||
|
|
||||||
|
JSON arrays are restricted to having a single homogenous element type. |
||||||
|
However, the type may itself be a JSON object. |
||||||
|
|
||||||
|
== REPORTING BUGS == |
||||||
|
Report bugs to Eric S. Raymond <esr@thyrsus.com>. The project page is |
||||||
|
at http://catb.org/~esr/microjson |
||||||
|
|
||||||
@ -0,0 +1,879 @@ |
|||||||
|
/****************************************************************************
|
||||||
|
|
||||||
|
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 |
||||||
|
SPDX-License-Identifier: BSD-2-Clause |
||||||
|
|
||||||
|
***************************************************************************/ |
||||||
|
/* 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 str_starts_with(s, p) (strncmp(s, p, strlen(p)) == 0) |
||||||
|
|
||||||
|
#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) do { } while (0) |
||||||
|
#endif /* DEBUG_ENABLE */ |
||||||
|
|
||||||
|
static char *json_target_address(const struct json_attr_t *cursor, |
||||||
|
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_short: |
||||||
|
targetaddr = (char *)&cursor->addr.shortint[offset]; |
||||||
|
break; |
||||||
|
case t_ushort: |
||||||
|
targetaddr = (char *)&cursor->addr.ushortint[offset]; |
||||||
|
break; |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
case t_time: |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
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(char *isotime) |
||||||
|
/* ISO8601 UTC to Unix UTC */ |
||||||
|
{ |
||||||
|
double usec; |
||||||
|
struct tm tm; |
||||||
|
|
||||||
|
char *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 */ |
||||||
|
|
||||||
|
static int json_internal_read_object(const char *cp, |
||||||
|
const struct json_attr_t *attrs, |
||||||
|
const struct json_array_t *parent, |
||||||
|
int offset, |
||||||
|
const char **end) |
||||||
|
{ |
||||||
|
enum |
||||||
|
{ init, await_attr, in_attr, await_value, in_val_string, |
||||||
|
in_escape, in_val_token, post_val, post_element |
||||||
|
} 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_element", |
||||||
|
}; |
||||||
|
#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; |
||||||
|
|
||||||
|
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_short: |
||||||
|
memcpy(lptr, &cursor->dflt.shortint, sizeof(short)); |
||||||
|
break; |
||||||
|
case t_ushort: |
||||||
|
memcpy(lptr, &cursor->dflt.ushortint, |
||||||
|
sizeof(unsigned short)); |
||||||
|
break; |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
case t_time: |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
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 (strcmp(cursor->attribute, "") == 0 && |
||||||
|
cursor->type == t_ignore) { |
||||||
|
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); |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
else if (cursor->type == t_time) |
||||||
|
maxlen = JSON_VAL_MAX; |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
else if (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_element; |
||||||
|
} 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 == '{') { |
||||||
|
if (cursor->type != t_object) { |
||||||
|
json_debug_trace((1, |
||||||
|
"Saw { when not expecting object.\n")); |
||||||
|
if (end != NULL) |
||||||
|
*end = cp; |
||||||
|
return JSON_ERR_NOARRAY; |
||||||
|
} |
||||||
|
substatus = json_read_object(cp, cursor->addr.attrs, &cp); |
||||||
|
if (substatus != 0) |
||||||
|
return substatus; |
||||||
|
--cp; // last } will be re-consumed by cp++ at end of loop
|
||||||
|
state = post_element; |
||||||
|
} else if (cursor->type == t_object) { |
||||||
|
json_debug_trace((1, |
||||||
|
"Object element was specified, but no {.\n")); |
||||||
|
if (end != NULL) |
||||||
|
*end = cp; |
||||||
|
return JSON_ERR_NOCURLY; |
||||||
|
} 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; |
||||||
|
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; /* */ |
||||||
|
} |
||||||
|
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': |
||||||
|
cp++; /* skip the 'u' */ |
||||||
|
for (n = 0; n < 4 && isxdigit(*cp); n++) |
||||||
|
uescape[n] = *cp++; |
||||||
|
uescape[n] = '\0'; /* terminate */ |
||||||
|
--cp; |
||||||
|
/* ECMA-404 says JSON \u must have 4 hex digits */ |
||||||
|
if ((4 != n) || (1 != sscanf(uescape, "%4x", &u))) { |
||||||
|
return JSON_ERR_BADSTRING; |
||||||
|
} |
||||||
|
*pval++ = (unsigned char)u; /* 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: |
||||||
|
// Ignore whitespace after either string or token values.
|
||||||
|
if (isspace(*cp)) { |
||||||
|
while (*cp != '\0' && isspace((unsigned char) *cp)) { |
||||||
|
++cp; |
||||||
|
} |
||||||
|
json_debug_trace((1, "Skipped trailing whitespace: value \"%s\"\n", valbuf)); |
||||||
|
} |
||||||
|
/*
|
||||||
|
* 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)) |
||||||
|
break; |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
if(value_quoted && (cursor->type == t_time)) |
||||||
|
break; |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0 |
||||||
|
|| isdigit((unsigned char) valbuf[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_ignore && cursor->map == 0)) { |
||||||
|
json_debug_trace((1, "Saw quoted value when expecting" |
||||||
|
" non-string.\n")); |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
if(value_quoted && cursor->type != t_time) |
||||||
|
json_debug_trace((1, "Saw quoted value when expecting" |
||||||
|
" non-string.\n")); |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
return JSON_ERR_QNONSTRING; |
||||||
|
} |
||||||
|
if (!value_quoted |
||||||
|
&& (cursor->type == t_string || cursor->type == t_check || cursor->map != 0)) { |
||||||
|
json_debug_trace((1, "Didn't see quoted value when expecting" |
||||||
|
" string.\n")); |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
if(!value_quoted && cursor->type == t_time) |
||||||
|
json_debug_trace((1, "Didn't see quoted value when expecting" |
||||||
|
" string.\n")); |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
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_short: |
||||||
|
{ |
||||||
|
short tmp = atoi(valbuf); |
||||||
|
memcpy(lptr, &tmp, sizeof(short)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case t_ushort: |
||||||
|
{ |
||||||
|
unsigned short tmp = (unsigned int)atoi(valbuf); |
||||||
|
memcpy(lptr, &tmp, sizeof(unsigned short)); |
||||||
|
} |
||||||
|
break; |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
case t_time: |
||||||
|
{ |
||||||
|
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; |
||||||
|
else { |
||||||
|
size_t vl = strlen(valbuf), cl = cursor->len-1; |
||||||
|
memset(lptr, '\0', cl); |
||||||
|
memcpy(lptr, valbuf, vl < cl ? vl : cl); |
||||||
|
} |
||||||
|
break; |
||||||
|
case t_boolean: |
||||||
|
{ |
||||||
|
bool tmp = (strcmp(valbuf, "true") == 0 || strtol(valbuf, NULL, 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; |
||||||
|
} |
||||||
|
__attribute__ ((fallthrough)); |
||||||
|
case post_element: |
||||||
|
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 (*cp != '\0' && isspace((unsigned char) *cp)) |
||||||
|
++cp; |
||||||
|
if (end != NULL) |
||||||
|
*end = cp; |
||||||
|
json_debug_trace((1, "JSON parse ends.\n")); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int json_read_array(const char *cp, const struct json_array_t *arr, |
||||||
|
const char **end) |
||||||
|
{ |
||||||
|
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; |
||||||
|
case t_short: |
||||||
|
arr->arr.shorts.store[offset] = (short)strtol(cp, &ep, 0); |
||||||
|
if (ep == cp) |
||||||
|
return JSON_ERR_BADNUM; |
||||||
|
else |
||||||
|
cp = ep; |
||||||
|
break; |
||||||
|
case t_ushort: |
||||||
|
arr->arr.ushorts.store[offset] = (unsigned short)strtol(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 (str_starts_with(cp, "true")) { |
||||||
|
arr->arr.booleans.store[offset] = true; |
||||||
|
cp += 4; |
||||||
|
} |
||||||
|
else if (str_starts_with(cp, "false")) { |
||||||
|
arr->arr.booleans.store[offset] = false; |
||||||
|
cp += 5; |
||||||
|
} else { |
||||||
|
int val = strtol(cp, &ep, 0); |
||||||
|
if (ep == cp) |
||||||
|
return JSON_ERR_BADNUM; |
||||||
|
else { |
||||||
|
arr->arr.booleans.store[offset] = (bool) val; |
||||||
|
cp = ep; |
||||||
|
} |
||||||
|
} |
||||||
|
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; |
||||||
|
json_debug_trace((1, "leaving json_read_array() with %d elements\n", |
||||||
|
arrcount)); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int json_read_object(const char *cp, const struct json_attr_t *attrs, |
||||||
|
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 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", |
||||||
|
"object element specified, but no {", |
||||||
|
}; |
||||||
|
|
||||||
|
if (err <= 0 || err >= (int)(sizeof(errors) / sizeof(errors[0]))) |
||||||
|
return errors[0]; |
||||||
|
else |
||||||
|
return errors[err]; |
||||||
|
} |
||||||
|
|
||||||
|
/* end */ |
||||||
|
|
||||||
@ -0,0 +1,164 @@ |
|||||||
|
/* Structures for JSON parsing using only fixed-extent memory
|
||||||
|
* |
||||||
|
* This file is Copyright (c) 2010 by the GPSD project |
||||||
|
* SPDX-License-Identifier: BSD-2-clause |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef MICROJSON_H_ |
||||||
|
#define MICROJSON_H_ |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <ctype.h> |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
#include <time.h> |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
|
||||||
|
typedef enum {t_integer, t_uinteger, t_real, |
||||||
|
t_string, t_boolean, t_character, |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
t_time, |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
t_object, t_structobject, t_array, |
||||||
|
t_check, t_ignore, |
||||||
|
t_short, t_ushort} |
||||||
|
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 { |
||||||
|
short *store; |
||||||
|
} shorts; |
||||||
|
struct { |
||||||
|
unsigned short *store; |
||||||
|
} ushorts; |
||||||
|
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; |
||||||
|
short *shortint; |
||||||
|
unsigned short *ushortint; |
||||||
|
double *real; |
||||||
|
char *string; |
||||||
|
bool *boolean; |
||||||
|
char *character; |
||||||
|
const struct json_attr_t *attrs; |
||||||
|
const struct json_array_t array; |
||||||
|
size_t offset; |
||||||
|
} addr; |
||||||
|
union { |
||||||
|
int integer; |
||||||
|
unsigned int uinteger; |
||||||
|
short shortint; |
||||||
|
unsigned short ushortint; |
||||||
|
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 *, |
||||||
|
const char **); |
||||||
|
int json_read_array(const char *, const struct json_array_t *, |
||||||
|
const char **); |
||||||
|
const char *json_error_string(int); |
||||||
|
|
||||||
|
#ifdef TIME_ENABLE |
||||||
|
extern time_t timegm(struct tm *tm); |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
|
||||||
|
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 */ |
||||||
|
#define JSON_ERR_NOCURLY 23 /* object element specified, but no { */ |
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 */ |
||||||
|
#endif /* MICROJSON_H_ */ |
||||||
@ -0,0 +1,978 @@ |
|||||||
|
/* test_mjson.c - unit test for JSON parsing into fixed-extent structures
|
||||||
|
* |
||||||
|
* This file is Copyright (c) 2014 by Eric S. Raymond |
||||||
|
* SPDX-License-Identifier: BSD-2-Clause |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <assert.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include <stddef.h> |
||||||
|
#include <getopt.h> |
||||||
|
#include <limits.h> |
||||||
|
#include <math.h> |
||||||
|
#include <ctype.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <math.h> |
||||||
|
#include <stddef.h> |
||||||
|
|
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
/*
|
||||||
|
* Many of these structures and examples were dissected out of the GPSD code. |
||||||
|
*/ |
||||||
|
|
||||||
|
#define MAXCHANNELS 20 |
||||||
|
#define MAXUSERDEVS 4 |
||||||
|
#define JSON_DATE_MAX 24 /* ISO8601 timestamp with 2 decimal places */ |
||||||
|
|
||||||
|
/* these values don't matter in themselves, they just have to be out-of-band */ |
||||||
|
#define DEVDEFAULT_BPS 0 |
||||||
|
#define DEVDEFAULT_PARITY 'X' |
||||||
|
#define DEVDEFAULT_STOPBITS 3 |
||||||
|
#define DEVDEFAULT_NATIVE -1 |
||||||
|
|
||||||
|
typedef double timestamp_t; /* Unix time in seconds with fractional part */ |
||||||
|
|
||||||
|
struct dop_t { |
||||||
|
/* Dilution of precision factors */ |
||||||
|
double xdop, ydop, pdop, hdop, vdop, tdop, gdop; |
||||||
|
}; |
||||||
|
|
||||||
|
struct version_t { |
||||||
|
char release[64]; /* external version */ |
||||||
|
char rev[64]; /* internal revision ID */ |
||||||
|
int proto_major, proto_minor; /* API major and minor versions */ |
||||||
|
char remote[PATH_MAX]; /* could be from a remote device */ |
||||||
|
}; |
||||||
|
|
||||||
|
struct devconfig_t { |
||||||
|
char path[PATH_MAX]; |
||||||
|
int flags; |
||||||
|
#define SEEN_GPS 0x01 |
||||||
|
#define SEEN_RTCM2 0x02 |
||||||
|
#define SEEN_RTCM3 0x04 |
||||||
|
#define SEEN_AIS 0x08 |
||||||
|
char driver[64]; |
||||||
|
char subtype[64]; |
||||||
|
double activated; |
||||||
|
unsigned int baudrate, stopbits; /* RS232 link parameters */ |
||||||
|
char parity; /* 'N', 'O', or 'E' */ |
||||||
|
double cycle, mincycle; /* refresh cycle time in seconds */ |
||||||
|
int driver_mode; /* is driver in native mode or not? */ |
||||||
|
}; |
||||||
|
struct gps_fix_t { |
||||||
|
timestamp_t time; /* Time of update */ |
||||||
|
int mode; /* Mode of fix */ |
||||||
|
#define MODE_NOT_SEEN 0 /* mode update not seen yet */ |
||||||
|
#define MODE_NO_FIX 1 /* none */ |
||||||
|
#define MODE_2D 2 /* good for latitude/longitude */ |
||||||
|
#define MODE_3D 3 /* good for altitude/climb too */ |
||||||
|
double ept; /* Expected time uncertainty */ |
||||||
|
double latitude; /* Latitude in degrees (valid if mode >= 2) */ |
||||||
|
double epy; /* Latitude position uncertainty, meters */ |
||||||
|
double longitude; /* Longitude in degrees (valid if mode >= 2) */ |
||||||
|
double epx; /* Longitude position uncertainty, meters */ |
||||||
|
double altitude; /* Altitude in meters (valid if mode == 3) */ |
||||||
|
double epv; /* Vertical position uncertainty, meters */ |
||||||
|
double track; /* Course made good (relative to true north) */ |
||||||
|
double epd; /* Track uncertainty, degrees */ |
||||||
|
double speed; /* Speed over ground, meters/sec */ |
||||||
|
double eps; /* Speed uncertainty, meters/sec */ |
||||||
|
double climb; /* Vertical speed, meters/sec */ |
||||||
|
double epc; /* Vertical speed uncertainty */ |
||||||
|
}; |
||||||
|
|
||||||
|
struct gps_data_t { |
||||||
|
struct gps_fix_t fix; /* accumulated PVT data */ |
||||||
|
|
||||||
|
/* this should move to the per-driver structure */ |
||||||
|
double separation; /* Geoidal separation, MSL - WGS84 (Meters) */ |
||||||
|
|
||||||
|
/* GPS status -- always valid */ |
||||||
|
int status; /* Do we have a fix? */ |
||||||
|
#define STATUS_NO_FIX 0 /* no */ |
||||||
|
#define STATUS_FIX 1 /* yes, without DGPS */ |
||||||
|
#define STATUS_DGPS_FIX 2 /* yes, with DGPS */ |
||||||
|
|
||||||
|
/* precision of fix -- valid if satellites_used > 0 */ |
||||||
|
int satellites_used; /* Number of satellites used in solution */ |
||||||
|
int used[MAXCHANNELS]; /* PRNs of satellites used in solution */ |
||||||
|
struct dop_t dop; |
||||||
|
|
||||||
|
/* redundant with the estimate elements in the fix structure */ |
||||||
|
double epe; /* spherical position error, 95% confidence (meters) */ |
||||||
|
|
||||||
|
/* satellite status -- valid when satellites_visible > 0 */ |
||||||
|
timestamp_t skyview_time; /* skyview timestamp */ |
||||||
|
int satellites_visible; /* # of satellites in view */ |
||||||
|
int PRN[MAXCHANNELS]; /* PRNs of satellite */ |
||||||
|
int elevation[MAXCHANNELS]; /* elevation of satellite */ |
||||||
|
int azimuth[MAXCHANNELS]; /* azimuth */ |
||||||
|
double ss[MAXCHANNELS]; /* signal-to-noise ratio (dB) */ |
||||||
|
|
||||||
|
struct devconfig_t dev; /* device that shipped last update */ |
||||||
|
|
||||||
|
struct { |
||||||
|
timestamp_t time; |
||||||
|
int ndevices; |
||||||
|
struct devconfig_t list[MAXUSERDEVS]; |
||||||
|
} devices; |
||||||
|
|
||||||
|
struct version_t version; |
||||||
|
}; |
||||||
|
|
||||||
|
static struct gps_data_t gpsdata; |
||||||
|
|
||||||
|
static int json_tpv_read(const char *buf, struct gps_data_t *gpsdata, |
||||||
|
const char **endptr) |
||||||
|
{ |
||||||
|
const struct json_attr_t json_attrs_1[] = { |
||||||
|
/* *INDENT-OFF* */ |
||||||
|
{"class", t_check, .dflt.check = "TPV"}, |
||||||
|
{"device", t_string, .addr.string = gpsdata->dev.path, |
||||||
|
.len = sizeof(gpsdata->dev.path)}, |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
{"time", t_time, .addr.real = &gpsdata->fix.time, |
||||||
|
.dflt.real = NAN}, |
||||||
|
#else |
||||||
|
{"time", t_ignore}, |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
{"ept", t_real, .addr.real = &gpsdata->fix.ept, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"lon", t_real, .addr.real = &gpsdata->fix.longitude, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"lat", t_real, .addr.real = &gpsdata->fix.latitude, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"alt", t_real, .addr.real = &gpsdata->fix.altitude, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"epx", t_real, .addr.real = &gpsdata->fix.epx, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"epy", t_real, .addr.real = &gpsdata->fix.epy, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"epv", t_real, .addr.real = &gpsdata->fix.epv, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"track", t_real, .addr.real = &gpsdata->fix.track, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"speed", t_real, .addr.real = &gpsdata->fix.speed, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"climb", t_real, .addr.real = &gpsdata->fix.climb, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"epd", t_real, .addr.real = &gpsdata->fix.epd, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"eps", t_real, .addr.real = &gpsdata->fix.eps, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"epc", t_real, .addr.real = &gpsdata->fix.epc, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"mode", t_integer, .addr.integer = &gpsdata->fix.mode, |
||||||
|
.dflt.integer = MODE_NOT_SEEN}, |
||||||
|
{NULL}, |
||||||
|
/* *INDENT-ON* */ |
||||||
|
}; |
||||||
|
|
||||||
|
return json_read_object(buf, json_attrs_1, endptr); |
||||||
|
} |
||||||
|
|
||||||
|
static int json_sky_read(const char *buf, struct gps_data_t *gpsdata, |
||||||
|
const char **endptr) |
||||||
|
{ |
||||||
|
bool usedflags[MAXCHANNELS]; |
||||||
|
const struct json_attr_t json_attrs_2_1[] = { |
||||||
|
/* *INDENT-OFF* */ |
||||||
|
{"PRN", t_integer, .addr.integer = gpsdata->PRN}, |
||||||
|
{"el", t_integer, .addr.integer = gpsdata->elevation}, |
||||||
|
{"az", t_integer, .addr.integer = gpsdata->azimuth}, |
||||||
|
{"ss", t_real, .addr.real = gpsdata->ss}, |
||||||
|
{"used", t_boolean, .addr.boolean = usedflags}, |
||||||
|
/* *INDENT-ON* */ |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
const struct json_attr_t json_attrs_2[] = { |
||||||
|
/* *INDENT-OFF* */ |
||||||
|
{"class", t_check, .dflt.check = "SKY"}, |
||||||
|
{"device", t_string, .addr.string = gpsdata->dev.path, |
||||||
|
.len = sizeof(gpsdata->dev.path)}, |
||||||
|
{"hdop", t_real, .addr.real = &gpsdata->dop.hdop, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"xdop", t_real, .addr.real = &gpsdata->dop.xdop, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"ydop", t_real, .addr.real = &gpsdata->dop.ydop, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"vdop", t_real, .addr.real = &gpsdata->dop.vdop, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"tdop", t_real, .addr.real = &gpsdata->dop.tdop, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"pdop", t_real, .addr.real = &gpsdata->dop.pdop, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"gdop", t_real, .addr.real = &gpsdata->dop.gdop, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"satellites", t_array, .addr.array.element_type = t_object, |
||||||
|
.addr.array.arr.objects.subtype=json_attrs_2_1, |
||||||
|
.addr.array.maxlen = MAXCHANNELS, |
||||||
|
.addr.array.count = &gpsdata->satellites_visible}, |
||||||
|
{NULL}, |
||||||
|
/* *INDENT-ON* */ |
||||||
|
}; |
||||||
|
int status, i, j; |
||||||
|
|
||||||
|
for (i = 0; i < MAXCHANNELS; i++) { |
||||||
|
gpsdata->PRN[i] = 0; |
||||||
|
usedflags[i] = false; |
||||||
|
} |
||||||
|
|
||||||
|
status = json_read_object(buf, json_attrs_2, endptr); |
||||||
|
if (status != 0) |
||||||
|
return status; |
||||||
|
|
||||||
|
gpsdata->satellites_used = 0; |
||||||
|
gpsdata->satellites_visible = 0; |
||||||
|
(void)memset(gpsdata->used, '\0', sizeof(gpsdata->used)); |
||||||
|
for (i = j = 0; i < MAXCHANNELS; i++) { |
||||||
|
if(gpsdata->PRN[i] > 0) |
||||||
|
gpsdata->satellites_visible++; |
||||||
|
if (usedflags[i]) { |
||||||
|
gpsdata->used[j++] = gpsdata->PRN[i]; |
||||||
|
gpsdata->satellites_used++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int json_devicelist_read(const char *buf, struct gps_data_t *gpsdata, |
||||||
|
const char **endptr) |
||||||
|
{ |
||||||
|
const struct json_attr_t json_attrs_subdevices[] = { |
||||||
|
/* *INDENT-OFF* */ |
||||||
|
{"class", t_check, .dflt.check = "DEVICE"}, |
||||||
|
{"path", t_string, STRUCTOBJECT(struct devconfig_t, path), |
||||||
|
.len = sizeof(gpsdata->devices.list[0].path)}, |
||||||
|
{"activated", t_real, STRUCTOBJECT(struct devconfig_t, activated)}, |
||||||
|
{"flags", t_integer, STRUCTOBJECT(struct devconfig_t, flags)}, |
||||||
|
{"driver", t_string, STRUCTOBJECT(struct devconfig_t, driver), |
||||||
|
.len = sizeof(gpsdata->devices.list[0].driver)}, |
||||||
|
{"subtype", t_string, STRUCTOBJECT(struct devconfig_t, subtype), |
||||||
|
.len = sizeof(gpsdata->devices.list[0].subtype)}, |
||||||
|
{"native", t_integer, STRUCTOBJECT(struct devconfig_t, driver_mode), |
||||||
|
.dflt.integer = -1}, |
||||||
|
{"bps", t_uinteger, STRUCTOBJECT(struct devconfig_t, baudrate), |
||||||
|
.dflt.uinteger = DEVDEFAULT_BPS}, |
||||||
|
{"parity", t_character, STRUCTOBJECT(struct devconfig_t, parity), |
||||||
|
.dflt.character = DEVDEFAULT_PARITY}, |
||||||
|
{"stopbits", t_uinteger, STRUCTOBJECT(struct devconfig_t, stopbits), |
||||||
|
.dflt.integer = DEVDEFAULT_STOPBITS}, |
||||||
|
{"cycle", t_real, STRUCTOBJECT(struct devconfig_t, cycle), |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"mincycle", t_real, STRUCTOBJECT(struct devconfig_t, mincycle), |
||||||
|
.dflt.real = NAN}, |
||||||
|
{NULL}, |
||||||
|
/* *INDENT-ON* */ |
||||||
|
}; |
||||||
|
const struct json_attr_t json_attrs_devices[] = { |
||||||
|
{"class", t_check,.dflt.check = "DEVICES"}, |
||||||
|
{"devices", t_array, STRUCTARRAY(gpsdata->devices.list, |
||||||
|
json_attrs_subdevices, |
||||||
|
&gpsdata->devices.ndevices)}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
int status; |
||||||
|
|
||||||
|
memset(&gpsdata->devices, '\0', sizeof(gpsdata->devices)); |
||||||
|
status = json_read_object(buf, json_attrs_devices, endptr); |
||||||
|
if (status != 0) { |
||||||
|
return status; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int json_device_read(const char *buf, |
||||||
|
struct devconfig_t *dev, |
||||||
|
const char **endptr) |
||||||
|
{ |
||||||
|
char tbuf[JSON_DATE_MAX+1]; |
||||||
|
const struct json_attr_t json_attrs_device[] = { |
||||||
|
{"class", t_check, .dflt.check = "DEVICE"}, |
||||||
|
|
||||||
|
{"path", t_string, .addr.string = dev->path, |
||||||
|
.len = sizeof(dev->path)}, |
||||||
|
{"activated", t_string, .addr.string = tbuf, |
||||||
|
.len = sizeof(tbuf)}, |
||||||
|
{"activated", t_real, .addr.real = &dev->activated}, |
||||||
|
{"flags", t_integer, .addr.integer = &dev->flags}, |
||||||
|
{"driver", t_string, .addr.string = dev->driver, |
||||||
|
.len = sizeof(dev->driver)}, |
||||||
|
{"subtype", t_string, .addr.string = dev->subtype, |
||||||
|
.len = sizeof(dev->subtype)}, |
||||||
|
{"native", t_integer, .addr.integer = &dev->driver_mode, |
||||||
|
.dflt.integer = DEVDEFAULT_NATIVE}, |
||||||
|
{"bps", t_uinteger, .addr.uinteger = &dev->baudrate, |
||||||
|
.dflt.uinteger = DEVDEFAULT_BPS}, |
||||||
|
{"parity", t_character, .addr.character = &dev->parity, |
||||||
|
.dflt.character = DEVDEFAULT_PARITY}, |
||||||
|
{"stopbits", t_uinteger, .addr.uinteger = &dev->stopbits, |
||||||
|
.dflt.uinteger = DEVDEFAULT_STOPBITS}, |
||||||
|
{"cycle", t_real, .addr.real = &dev->cycle, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{"mincycle", t_real, .addr.real = &dev->mincycle, |
||||||
|
.dflt.real = NAN}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
/* *INDENT-ON* */ |
||||||
|
int status; |
||||||
|
|
||||||
|
tbuf[0] = '\0'; |
||||||
|
status = json_read_object(buf, json_attrs_device, endptr); |
||||||
|
if (status != 0) |
||||||
|
return status; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int json_version_read(const char *buf, struct gps_data_t *gpsdata, |
||||||
|
const char **endptr) |
||||||
|
{ |
||||||
|
const struct json_attr_t json_attrs_version[] = { |
||||||
|
/* *INDENT-OFF* */ |
||||||
|
{"class", t_check, .dflt.check = "VERSION"}, |
||||||
|
{"release", t_string, .addr.string = gpsdata->version.release, |
||||||
|
.len = sizeof(gpsdata->version.release)}, |
||||||
|
{"rev", t_string, .addr.string = gpsdata->version.rev, |
||||||
|
.len = sizeof(gpsdata->version.rev)}, |
||||||
|
{"proto_major", t_integer, .addr.integer = &gpsdata->version.proto_major}, |
||||||
|
{"proto_minor", t_integer, .addr.integer = &gpsdata->version.proto_minor}, |
||||||
|
{"remote", t_string, .addr.string = gpsdata->version.remote, |
||||||
|
.len = sizeof(gpsdata->version.remote)}, |
||||||
|
{NULL}, |
||||||
|
/* *INDENT-ON* */ |
||||||
|
}; |
||||||
|
int status; |
||||||
|
|
||||||
|
memset(&gpsdata->version, '\0', sizeof(gpsdata->version)); |
||||||
|
status = json_read_object(buf, json_attrs_version, endptr); |
||||||
|
|
||||||
|
return status; |
||||||
|
} |
||||||
|
|
||||||
|
static int libgps_json_unpack(const char *buf, |
||||||
|
struct gps_data_t *gpsdata, const char **end) |
||||||
|
/* the only entry point - unpack a JSON object into gpsdata_t substructures */ |
||||||
|
{ |
||||||
|
int status; |
||||||
|
char *classtag = strstr(buf, "\"class\":"); |
||||||
|
|
||||||
|
if (classtag == NULL) |
||||||
|
return -1; |
||||||
|
#define STARTSWITH(str, prefix) strncmp(str, prefix, sizeof(prefix)-1)==0 |
||||||
|
if (STARTSWITH(classtag, "\"class\":\"TPV\"")) { |
||||||
|
status = json_tpv_read(buf, gpsdata, end); |
||||||
|
gpsdata->status = STATUS_FIX; |
||||||
|
return status; |
||||||
|
} else if (STARTSWITH(classtag, "\"class\":\"SKY\"")) { |
||||||
|
status = json_sky_read(buf, gpsdata, end); |
||||||
|
return status; |
||||||
|
} else if (STARTSWITH(classtag, "\"class\":\"DEVICE\"")) { |
||||||
|
status = json_device_read(buf, &gpsdata->dev, end); |
||||||
|
return status; |
||||||
|
} else if (STARTSWITH(classtag, "\"class\":\"DEVICES\"")) { |
||||||
|
status = json_devicelist_read(buf, gpsdata, end); |
||||||
|
return status; |
||||||
|
} else if (STARTSWITH(classtag, "\"class\":\"VERSION\"")) { |
||||||
|
status = json_version_read(buf, gpsdata, end); |
||||||
|
return status; |
||||||
|
} |
||||||
|
#undef STARTSWITH |
||||||
|
} |
||||||
|
|
||||||
|
static int assert_error_case(int num, int status, int error) |
||||||
|
{ |
||||||
|
if (status != error) { |
||||||
|
(void)fprintf(stderr, "case %d FAILED, status %d (%s).\n", num, |
||||||
|
status, json_error_string(status)); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
else |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void assert_case(int num, int status) |
||||||
|
{ |
||||||
|
assert_error_case(num, status, 0); |
||||||
|
} |
||||||
|
|
||||||
|
static void assert_string(char *attr, char *fld, char *check) |
||||||
|
{ |
||||||
|
if (strcmp(fld, check)) { |
||||||
|
(void)fprintf(stderr, |
||||||
|
"'%s' expecting string '%s', got '%s'.\n", |
||||||
|
attr, check, fld); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void assert_integer(char *attr, int fld, int check) |
||||||
|
{ |
||||||
|
if (fld != check) { |
||||||
|
(void)fprintf(stderr, |
||||||
|
"'%s' expecting integer %d, got %d.\n", |
||||||
|
attr, check, fld); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void assert_uinteger(char *attr, unsigned int fld, unsigned int check) |
||||||
|
{ |
||||||
|
if (fld != check) { |
||||||
|
(void)fprintf(stderr, |
||||||
|
"'%s' expecting uinteger %u, got %u.\n", |
||||||
|
attr, check, fld); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void assert_boolean(char *attr, bool fld, bool check) |
||||||
|
{ |
||||||
|
if (fld != check) { |
||||||
|
(void)fprintf(stderr, |
||||||
|
"'%s' expecting boolean %s, got %s.\n", |
||||||
|
attr,
|
||||||
|
check ? "true" : "false", |
||||||
|
fld ? "true" : "false"); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Floating point comparisons are iffy, but at least if any of these fail |
||||||
|
* the output will make it clear whether it was a precision issue |
||||||
|
*/ |
||||||
|
static void assert_real(char *attr, double fld, double check) |
||||||
|
{ |
||||||
|
if (fld != check) { |
||||||
|
(void)fprintf(stderr, |
||||||
|
"'%s' expecting real %f got %f.\n",
|
||||||
|
attr, check, fld); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* Case 1: TPV report */ |
||||||
|
|
||||||
|
/* *INDENT-OFF* */ |
||||||
|
static const char json_str1[] = "{\"class\":\"TPV\",\
|
||||||
|
\"device\":\"GPS#1\", \
|
||||||
|
\"time\":\"2005-06-19T12:12:42.03Z\", \
|
||||||
|
\"lon\":46.498203637,\"lat\":7.568074350, \
|
||||||
|
\"alt\":1327.780,\"epx\":21.000,\"epy\":23.000,\"epv\":124.484,\"mode\":3}"; |
||||||
|
|
||||||
|
/* Case 2: SKY report */ |
||||||
|
|
||||||
|
static const char *json_str2 = "{\"class\":\"SKY\",\
|
||||||
|
\"satellites\":[\
|
||||||
|
{\"PRN\":10,\"el\":45,\"az\":196,\"ss\":34,\"used\":true},\
|
||||||
|
{\"PRN\":29,\"el\":67,\"az\":310,\"ss\":40,\"used\":true},\
|
||||||
|
{\"PRN\":28,\"el\":59,\"az\":108,\"ss\":42,\"used\":true},\
|
||||||
|
{\"PRN\":26,\"el\":51,\"az\":304,\"ss\":43,\"used\":true},\
|
||||||
|
{\"PRN\":8,\"el\":44,\"az\":58,\"ss\":41,\"used\":true},\
|
||||||
|
{\"PRN\":27,\"el\":16,\"az\":66,\"ss\":39,\"used\":true},\
|
||||||
|
{\"PRN\":21,\"el\":10,\"az\":301,\"ss\":0,\"used\":false}]}"; |
||||||
|
|
||||||
|
/* Case 3: String list syntax */ |
||||||
|
|
||||||
|
static const char *json_str3 = "[\"foo\",\"bar\",\"baz\"]"; |
||||||
|
|
||||||
|
static char *stringptrs[3]; |
||||||
|
static char stringstore[256]; |
||||||
|
static int stringcount; |
||||||
|
|
||||||
|
static const struct json_array_t json_array_3 = { |
||||||
|
.element_type = t_string, |
||||||
|
.arr.strings.ptrs = stringptrs, |
||||||
|
.arr.strings.store = stringstore, |
||||||
|
.arr.strings.storelen = sizeof(stringstore), |
||||||
|
.count = &stringcount, |
||||||
|
.maxlen = sizeof(stringptrs)/sizeof(stringptrs[0]), |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 4: test defaulting of unspecified attributes */ |
||||||
|
|
||||||
|
static const char *json_str4 = "{\"flag1\":true,\"flag2\":false}"; |
||||||
|
|
||||||
|
static bool flag1, flag2; |
||||||
|
static double dftreal; |
||||||
|
static int dftinteger; |
||||||
|
static unsigned int dftuinteger; |
||||||
|
|
||||||
|
static const struct json_attr_t json_attrs_4[] = { |
||||||
|
{"dftint", t_integer, .addr.integer = &dftinteger, .dflt.integer = -5}, |
||||||
|
{"dftuint", t_integer, .addr.uinteger = &dftuinteger, .dflt.uinteger = 10}, |
||||||
|
{"dftreal", t_real, .addr.real = &dftreal, .dflt.real = 23.17}, |
||||||
|
{"flag1", t_boolean, .addr.boolean = &flag1,}, |
||||||
|
{"flag2", t_boolean, .addr.boolean = &flag2,}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 5: test DEVICE parsing */ |
||||||
|
|
||||||
|
static const char *json_str5 = "{\"class\":\"DEVICE\",\
|
||||||
|
\"path\":\"/dev/ttyUSB0\",\
|
||||||
|
\"flags\":5,\
|
||||||
|
\"driver\":\"Foonly\",\"subtype\":\"Foonly Frob\"\
|
||||||
|
}"; |
||||||
|
|
||||||
|
/* Case 6: test parsing of subobject list into array of structures */ |
||||||
|
|
||||||
|
static const char *json_str6 = "{\"parts\":[\
|
||||||
|
{\"name\":\"Urgle\", \"flag\":true, \"count\":3},\
|
||||||
|
{\"name\":\"Burgle\",\"flag\":false,\"count\":1},\
|
||||||
|
{\"name\":\"Witter\",\"flag\":true, \"count\":4},\
|
||||||
|
{\"name\":\"Thud\", \"flag\":false,\"count\":1}]}"; |
||||||
|
|
||||||
|
struct dumbstruct_t { |
||||||
|
char name[64]; |
||||||
|
bool flag; |
||||||
|
int count; |
||||||
|
}; |
||||||
|
static struct dumbstruct_t dumbstruck[5]; |
||||||
|
static int dumbcount; |
||||||
|
|
||||||
|
static const struct json_attr_t json_attrs_6_subtype[] = { |
||||||
|
{"name", t_string, .addr.offset = offsetof(struct dumbstruct_t, name), |
||||||
|
.len = 64}, |
||||||
|
{"flag", t_boolean, .addr.offset = offsetof(struct dumbstruct_t, flag),}, |
||||||
|
{"count", t_integer, .addr.offset = offsetof(struct dumbstruct_t, count),}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct json_attr_t json_attrs_6[] = { |
||||||
|
{"parts", t_array, .addr.array.element_type = t_structobject, |
||||||
|
.addr.array.arr.objects.base = (char*)&dumbstruck, |
||||||
|
.addr.array.arr.objects.stride = sizeof(struct dumbstruct_t), |
||||||
|
.addr.array.arr.objects.subtype = json_attrs_6_subtype, |
||||||
|
.addr.array.count = &dumbcount, |
||||||
|
.addr.array.maxlen = sizeof(dumbstruck)/sizeof(dumbstruck[0])}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 7: test parsing of version response */ |
||||||
|
|
||||||
|
static const char *json_str7 = "{\"class\":\"VERSION\",\
|
||||||
|
\"release\":\"2.40dev\",\"rev\":\"dummy-revision\",\
|
||||||
|
\"proto_major\":3,\"proto_minor\":1}"; |
||||||
|
|
||||||
|
/* Case 8: test parsing arrays of enumerated types */ |
||||||
|
|
||||||
|
static const char *json_str8 = "{\"fee\":\"FOO\",\"fie\":\"BAR\",\"foe\":\"BAZ\"}"; |
||||||
|
static const struct json_enum_t enum_table[] = { |
||||||
|
{"BAR", 6}, {"FOO", 3}, {"BAZ", 14}, {NULL} |
||||||
|
}; |
||||||
|
|
||||||
|
static int fee, fie, foe; |
||||||
|
static const struct json_attr_t json_attrs_8[] = { |
||||||
|
{"fee", t_integer, .addr.integer = &fee, .map=enum_table}, |
||||||
|
{"fie", t_integer, .addr.integer = &fie, .map=enum_table}, |
||||||
|
{"foe", t_integer, .addr.integer = &foe, .map=enum_table}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 9: Like case 6 but w/ an empty array */ |
||||||
|
|
||||||
|
static const char *json_str9 = "{\"parts\":[]}"; |
||||||
|
|
||||||
|
/* Case 10: test buffer overflow of short string destination */ |
||||||
|
|
||||||
|
// static char json_strErr2[7 * JSON_VAL_MAX]; /* dynamically built */
|
||||||
|
static char *json_strOver = "{\"name\":\"\\u0033\\u0034\\u0035\\u0036\"}"; |
||||||
|
|
||||||
|
char json_short_string_dst[1]; |
||||||
|
static const struct json_attr_t json_short_string[] = { |
||||||
|
{"name", t_string, |
||||||
|
.addr.string = json_short_string_dst, |
||||||
|
.len = sizeof(json_short_string_dst)}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 11: Read array of integers */ |
||||||
|
|
||||||
|
static const char *json_str11 = "[23,-17,5]"; |
||||||
|
static int intstore[4], intcount; |
||||||
|
|
||||||
|
static const struct json_array_t json_array_11 = { |
||||||
|
.element_type = t_integer, |
||||||
|
.arr.integers.store = intstore, |
||||||
|
.count = &intcount, |
||||||
|
.maxlen = sizeof(intstore)/sizeof(intstore[0]), |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 12: Read array of booleans */ |
||||||
|
|
||||||
|
static const char *json_str12 = "[true,false,true]"; |
||||||
|
static bool boolstore[4]; |
||||||
|
static int boolcount; |
||||||
|
|
||||||
|
static const struct json_array_t json_array_12 = { |
||||||
|
.element_type = t_boolean, |
||||||
|
.arr.booleans.store = boolstore, |
||||||
|
.count = &boolcount, |
||||||
|
.maxlen = sizeof(boolstore)/sizeof(boolstore[0]), |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 13: Read array of reals */ |
||||||
|
|
||||||
|
static const char *json_str13 = "[23.1,-17.2,5.3]"; |
||||||
|
static double realstore[4];
|
||||||
|
static int realcount; |
||||||
|
|
||||||
|
static const struct json_array_t json_array_13 = { |
||||||
|
.element_type = t_real, |
||||||
|
.arr.reals.store = realstore, |
||||||
|
.count = &realcount, |
||||||
|
.maxlen = sizeof(realstore)/sizeof(realstore[0]), |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 14: Read object within object. Test case not derived from GPSD */ |
||||||
|
|
||||||
|
static char json_inner_name_string_dst[JSON_VAL_MAX]; |
||||||
|
static int inner_value; |
||||||
|
static const char *json_str14 = "{\"name\":\"wobble\",\"value\":{\"inner\":23}}"; |
||||||
|
const struct json_attr_t json_inner_int_value[] = { |
||||||
|
{"inner", t_integer, |
||||||
|
.addr.integer = &inner_value}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
static const struct json_attr_t json_object_14[] = { |
||||||
|
{"name", t_string, |
||||||
|
.addr.string = json_inner_name_string_dst, |
||||||
|
.len = sizeof(json_inner_name_string_dst)}, |
||||||
|
{"value", t_object, |
||||||
|
.addr.attrs = json_inner_int_value}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 15: test parsing 0/1 integer values as bool */ |
||||||
|
|
||||||
|
static bool flag3; |
||||||
|
static bool flags4[3]; |
||||||
|
|
||||||
|
static const char *json_str15 = "{\"flag1\":1, \"flag2\":0, \"flag3\":7, \"flags4\":[1,0,7]}"; |
||||||
|
|
||||||
|
static const struct json_attr_t json_attrs_15[] = { |
||||||
|
{"flag1", t_boolean, .addr.boolean = &flag1,}, |
||||||
|
{"flag2", t_boolean, .addr.boolean = &flag2,}, |
||||||
|
{"flag3", t_boolean, .addr.boolean = &flag3,}, |
||||||
|
{"flags4", t_array, .addr.array.element_type = t_boolean, |
||||||
|
.addr.array.arr.booleans = flags4, |
||||||
|
.addr.array.maxlen = 3,}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 16: Read object within object within object.*/ |
||||||
|
|
||||||
|
char json_inner1_name_string_dst[JSON_VAL_MAX]; |
||||||
|
|
||||||
|
int inner1_value; |
||||||
|
int inner2_value; |
||||||
|
|
||||||
|
static const char *json_str16 = "{\"name\":\"wobble\",\"value\":{\"inner\":23, \"innerobject\":{\"innerinner\":123}}}\0READ PAST END OF BUFFER"; |
||||||
|
|
||||||
|
const struct json_attr_t json_inner2_int_value[] = { |
||||||
|
{"innerinner", t_integer, |
||||||
|
.addr.integer = &inner2_value}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
const struct json_attr_t json_inner1_int_value[] = { |
||||||
|
{"inner", t_integer, |
||||||
|
.addr.integer = &inner1_value}, |
||||||
|
{"innerobject", t_object, |
||||||
|
.addr.attrs = json_inner2_int_value}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
static const struct json_attr_t json_object_16[] = { |
||||||
|
{"name", t_string, |
||||||
|
.addr.string = json_inner1_name_string_dst, |
||||||
|
.len = sizeof(json_inner1_name_string_dst)}, |
||||||
|
{"value", t_object, |
||||||
|
.addr.attrs = json_inner1_int_value}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 17: Parent struct enclosing enum in sub-struct followed by value outside of sub-struct */ |
||||||
|
|
||||||
|
static int eval, ival; |
||||||
|
static const char *json_str17 = "{\"parentStruct\":{\"enumStruct\":{\"enumName\":\"NOT_SET\"\n},\"intName\":1}}"; |
||||||
|
static const struct json_enum_t enum_table16[] = { |
||||||
|
{"NOT_SET", 0}, {"SET", 1},{NULL} |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct json_attr_t json_enum_struct[] = { |
||||||
|
{"enumName", t_integer,.addr.integer = &eval,.map = enum_table16}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
static const struct json_attr_t json_sub_struct[] = { |
||||||
|
{"enumStruct", t_object,.addr.attrs = json_enum_struct}, |
||||||
|
{"intName", t_integer,.addr.integer = &ival}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
static const struct json_attr_t json_object_17[] = { |
||||||
|
{"parentStruct", t_object,.addr.attrs = json_sub_struct}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
|
||||||
|
/* Case 18: Call json_read_object() on concatenated JSON objects. */ |
||||||
|
|
||||||
|
static const char *json_cur18; |
||||||
|
static const char *json_end18; |
||||||
|
|
||||||
|
static const char *json_str18 = "{\"flag1\":1}{\"flag1\":0}{\"flag1\":7, \"flags4\":[1,0,7]} {\"flag2\":true, \"flags4\":[0,true,false]}"; |
||||||
|
|
||||||
|
/* Insert more test definitions here */ |
||||||
|
/* *INDENT-ON* */ |
||||||
|
|
||||||
|
static void jsontest(int i) |
||||||
|
{ |
||||||
|
int status = 0; |
||||||
|
|
||||||
|
switch (i)
|
||||||
|
{ |
||||||
|
case 1: |
||||||
|
status = libgps_json_unpack(json_str1, &gpsdata, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_string("device", gpsdata.dev.path, "GPS#1"); |
||||||
|
#ifdef TIME_ENABLE |
||||||
|
assert_real("time", gpsdata.fix.time, 1119183162.030000); |
||||||
|
#endif /* TIME_ENABLE */ |
||||||
|
assert_integer("mode", gpsdata.fix.mode, 3); |
||||||
|
assert_real("lon", gpsdata.fix.longitude, 46.498203637); |
||||||
|
assert_real("lat", gpsdata.fix.latitude, 7.568074350); |
||||||
|
break; |
||||||
|
|
||||||
|
case 2: |
||||||
|
status = libgps_json_unpack(json_str2, &gpsdata, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("used", gpsdata.satellites_used, 6); |
||||||
|
assert_integer("PRN[0]", gpsdata.PRN[0], 10); |
||||||
|
assert_integer("el[0]", gpsdata.elevation[0], 45); |
||||||
|
assert_integer("az[0]", gpsdata.azimuth[0], 196); |
||||||
|
assert_real("ss[0]", gpsdata.ss[0], 34); |
||||||
|
assert_integer("used[0]", gpsdata.used[0], 10); |
||||||
|
assert_integer("used[5]", gpsdata.used[5], 27); |
||||||
|
assert_integer("PRN[6]", gpsdata.PRN[6], 21); |
||||||
|
assert_integer("el[6]", gpsdata.elevation[6], 10); |
||||||
|
assert_integer("az[6]", gpsdata.azimuth[6], 301); |
||||||
|
assert_real("ss[6]", gpsdata.ss[6], 0); |
||||||
|
break; |
||||||
|
|
||||||
|
case 3: |
||||||
|
status = json_read_array(json_str3, &json_array_3, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert(stringcount == 3); |
||||||
|
assert(strcmp(stringptrs[0], "foo") == 0); |
||||||
|
assert(strcmp(stringptrs[1], "bar") == 0); |
||||||
|
assert(strcmp(stringptrs[2], "baz") == 0); |
||||||
|
break; |
||||||
|
|
||||||
|
case 4: |
||||||
|
status = json_read_object(json_str4, json_attrs_4, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("dftint", dftinteger, -5); /* did the default work? */ |
||||||
|
assert_uinteger("dftuint", dftuinteger, 10); /* did the default work? */ |
||||||
|
assert_real("dftreal", dftreal, 23.17); /* did the default work? */ |
||||||
|
assert_boolean("flag1", flag1, true); |
||||||
|
assert_boolean("flag2", flag2, false); |
||||||
|
break; |
||||||
|
|
||||||
|
case 5: |
||||||
|
status = libgps_json_unpack(json_str5, &gpsdata, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_string("path", gpsdata.dev.path, "/dev/ttyUSB0"); |
||||||
|
assert_integer("flags", gpsdata.dev.flags, 5); |
||||||
|
assert_string("driver", gpsdata.dev.driver, "Foonly"); |
||||||
|
break; |
||||||
|
|
||||||
|
case 6: |
||||||
|
status = json_read_object(json_str6, json_attrs_6, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("dumbcount", dumbcount, 4); |
||||||
|
assert_string("dumbstruck[0].name", dumbstruck[0].name, "Urgle"); |
||||||
|
assert_string("dumbstruck[1].name", dumbstruck[1].name, "Burgle"); |
||||||
|
assert_string("dumbstruck[2].name", dumbstruck[2].name, "Witter"); |
||||||
|
assert_string("dumbstruck[3].name", dumbstruck[3].name, "Thud"); |
||||||
|
assert_boolean("dumbstruck[0].flag", dumbstruck[0].flag, true); |
||||||
|
assert_boolean("dumbstruck[1].flag", dumbstruck[1].flag, false); |
||||||
|
assert_boolean("dumbstruck[2].flag", dumbstruck[2].flag, true); |
||||||
|
assert_boolean("dumbstruck[3].flag", dumbstruck[3].flag, false); |
||||||
|
assert_integer("dumbstruck[0].count", dumbstruck[0].count, 3); |
||||||
|
assert_integer("dumbstruck[1].count", dumbstruck[1].count, 1); |
||||||
|
assert_integer("dumbstruck[2].count", dumbstruck[2].count, 4); |
||||||
|
assert_integer("dumbstruck[3].count", dumbstruck[3].count, 1); |
||||||
|
break; |
||||||
|
|
||||||
|
case 7: |
||||||
|
status = libgps_json_unpack(json_str7, &gpsdata, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_string("release", gpsdata.version.release, "2.40dev"); |
||||||
|
assert_string("rev", gpsdata.version.rev, "dummy-revision"); |
||||||
|
assert_integer("proto_major", gpsdata.version.proto_major, 3); |
||||||
|
assert_integer("proto_minor", gpsdata.version.proto_minor, 1); |
||||||
|
break; |
||||||
|
|
||||||
|
case 8: |
||||||
|
status = json_read_object(json_str8, json_attrs_8, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("fee", fee, 3); |
||||||
|
assert_integer("fie", fie, 6); |
||||||
|
assert_integer("foe", foe, 14); |
||||||
|
break; |
||||||
|
|
||||||
|
case 9: |
||||||
|
/* yes, the '6' in the next line is correct */
|
||||||
|
status = json_read_object(json_str9, json_attrs_6, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("dumbcount", dumbcount, 0); |
||||||
|
break; |
||||||
|
|
||||||
|
case 10: |
||||||
|
status = json_read_object(json_strOver, json_short_string, NULL); |
||||||
|
status = assert_error_case(10, status, JSON_ERR_STRLONG); |
||||||
|
assert_string("name", json_short_string_dst, ""); |
||||||
|
break; |
||||||
|
|
||||||
|
case 11: |
||||||
|
status = json_read_array(json_str11, &json_array_11, NULL); |
||||||
|
assert_integer("count", intcount, 3); |
||||||
|
assert_integer("intstore[0]", intstore[0], 23); |
||||||
|
assert_integer("intstore[1]", intstore[1], -17); |
||||||
|
assert_integer("intstore[2]", intstore[2], 5); |
||||||
|
assert_integer("intstore[3]", intstore[3], 0); |
||||||
|
break; |
||||||
|
|
||||||
|
case 12: |
||||||
|
status = json_read_array(json_str12, &json_array_12, NULL); |
||||||
|
assert_integer("count", boolcount, 3); |
||||||
|
assert_boolean("boolstore[0]", boolstore[0], true); |
||||||
|
assert_boolean("boolstore[1]", boolstore[1], false); |
||||||
|
assert_boolean("boolstore[2]", boolstore[2], true); |
||||||
|
assert_boolean("boolstore[3]", boolstore[3], false); |
||||||
|
break; |
||||||
|
|
||||||
|
case 13: |
||||||
|
status = json_read_array(json_str13, &json_array_13, NULL); |
||||||
|
assert_integer("count", realcount, 3); |
||||||
|
assert_real("realstore[0]", realstore[0], 23.1); |
||||||
|
assert_real("realstore[1]", realstore[1], -17.2); |
||||||
|
assert_real("realstore[2]", realstore[2], 5.3); |
||||||
|
assert_real("realstore[3]", realstore[3], 0); |
||||||
|
break; |
||||||
|
|
||||||
|
case 14: |
||||||
|
status = json_read_object(json_str14, json_object_14, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("inner", inner_value, 23); |
||||||
|
assert_string("name", json_inner_name_string_dst, "wobble"); |
||||||
|
break; |
||||||
|
|
||||||
|
case 15: |
||||||
|
status = json_read_object(json_str15, json_attrs_15, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_boolean("flag1", flag1, true); |
||||||
|
assert_boolean("flag2", flag2, false); |
||||||
|
assert_boolean("flag3", flag3, true); |
||||||
|
assert_boolean("flags4[0]", flags4[0], true); |
||||||
|
assert_boolean("flags4[1]", flags4[1], false); |
||||||
|
assert_boolean("flags4[2]", flags4[2], true); |
||||||
|
break; |
||||||
|
|
||||||
|
case 16: |
||||||
|
status = json_read_object(json_str16, json_object_16, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("inner", inner1_value, 23); |
||||||
|
assert_integer("innerinner", inner2_value, 123); |
||||||
|
assert_string("name", json_inner1_name_string_dst, "wobble"); |
||||||
|
break; |
||||||
|
|
||||||
|
case 17: |
||||||
|
status = json_read_object(json_str17, json_object_17, NULL); |
||||||
|
assert_case(i, status); |
||||||
|
assert_integer("ival", ival, 1); |
||||||
|
assert_integer("eval", eval, 0); |
||||||
|
break; |
||||||
|
|
||||||
|
case 18: |
||||||
|
json_cur18 = json_str18; |
||||||
|
json_end18 = json_str18 + strlen(json_str18); |
||||||
|
/* first JSON message */ |
||||||
|
status = json_read_object(json_cur18, json_attrs_15, &json_cur18); |
||||||
|
assert_case(i, status); |
||||||
|
assert_boolean("flag1", flag1, true); |
||||||
|
/* second JSON message */ |
||||||
|
status = json_read_object(json_cur18, json_attrs_15, &json_cur18); |
||||||
|
assert_case(i, status); |
||||||
|
assert_boolean("flag1", flag1, false); |
||||||
|
/* third JSON message */ |
||||||
|
status = json_read_object(json_cur18, json_attrs_15, &json_cur18); |
||||||
|
assert_case(i, status); |
||||||
|
assert_boolean("flag1", flag1, true); |
||||||
|
assert_boolean("flags4[0]", flags4[0], true); |
||||||
|
assert_boolean("flags4[1]", flags4[1], false); |
||||||
|
assert_boolean("flags4[2]", flags4[2], true); |
||||||
|
/* fourth JSON message */ |
||||||
|
status = json_read_object(json_cur18, json_attrs_15, &json_cur18); |
||||||
|
assert_case(i, status); |
||||||
|
assert_boolean("flag2", flag2, true); |
||||||
|
assert_boolean("flags4[0]", flags4[0], false); |
||||||
|
assert_boolean("flags4[1]", flags4[1], true); |
||||||
|
assert_boolean("flags4[2]", flags4[2], false); |
||||||
|
break; |
||||||
|
|
||||||
|
#define MAXTEST 18 |
||||||
|
|
||||||
|
default: |
||||||
|
(void)fputs("Unknown test number\n", stderr); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (status > 0) |
||||||
|
printf("Parse failure!\n"); |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
int option; |
||||||
|
int individual = 0; |
||||||
|
|
||||||
|
while ((option = getopt(argc, argv, "hn:D:?")) != -1) { |
||||||
|
switch (option) { |
||||||
|
case 'D': |
||||||
|
#ifdef DEBUG_ENABLE |
||||||
|
json_enable_debug(atoi(optarg), stdout); |
||||||
|
#else |
||||||
|
fputs("Debug disabled in build\n", stderr); |
||||||
|
#endif |
||||||
|
break; |
||||||
|
case 'n': |
||||||
|
individual = atoi(optarg); |
||||||
|
break; |
||||||
|
case '?': |
||||||
|
case 'h': |
||||||
|
default: |
||||||
|
(void)fputs("usage: test_json [-D lvl]\n", stderr); |
||||||
|
exit(EXIT_FAILURE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
(void)fprintf(stderr, "microjson unit test "); |
||||||
|
|
||||||
|
if (individual) |
||||||
|
jsontest(individual); |
||||||
|
else { |
||||||
|
int i; |
||||||
|
for (i = 1; i <= MAXTEST; i++) |
||||||
|
jsontest(i); |
||||||
|
} |
||||||
|
|
||||||
|
(void)fprintf(stderr, "succeeded.\n"); |
||||||
|
|
||||||
|
exit(EXIT_SUCCESS); |
||||||
|
} |
||||||
|
|
||||||
|
/* end */ |
||||||
@ -0,0 +1,117 @@ |
|||||||
|
//#include <ctype.h>
|
||||||
|
#include <errno.h> |
||||||
|
#include <math.h> |
||||||
|
#include <stdint.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include "mjson.h" |
||||||
|
|
||||||
|
static int test_ver(void); |
||||||
|
static int test_watch(void); |
||||||
|
static int test_tpv(void); |
||||||
|
int main(void); |
||||||
|
|
||||||
|
static char *VER = |
||||||
|
"{\"class\":\"VERSION\",\"release\":\"3.19.1~dev\",\"rev\":\"release-3.19-" |
||||||
|
"655-gb4aded4c1\",\"proto_major\":3,\"proto_minor\":14}"; |
||||||
|
static char *WAT = |
||||||
|
"{\"class\":\"WATCH\",\"enable\":true,\"json\":true,\"nmea\":false,\"raw\":" |
||||||
|
"0,\"scaled\":false,\"timing\":false,\"split24\":false,\"pps\":false," |
||||||
|
"\"device\":\"/dev/ttyUSB0\"}"; |
||||||
|
static char *TPV = |
||||||
|
"{\"class\":\"TPV\",\"device\":\"/dev/" |
||||||
|
"ttyUSB0\",\"mode\":3,\"time\":\"2019-10-04T08:51:34.000Z\",\"ept\":0.005," |
||||||
|
"\"lat\":46.367303831,\"lon\":-116.963791235,\"altHAE\":460.834,\"altMSL\":" |
||||||
|
"476.140,\"epx\":7.842,\"epy\":12.231,\"epv\":30.607,\"track\":57.1020," |
||||||
|
"\"magtrack\":70.9299,\"magvar\":13.8,\"speed\":0.065,\"climb\":-0.206," |
||||||
|
"\"eps\":24.46,\"epc\":61.21,\"ecefx\":-1999242.00,\"ecefy\":-3929871.00," |
||||||
|
"\"ecefz\":4593848.00,\"ecefvx\":0.12,\"ecefvy\":0.12,\"ecefvz\":-0.12," |
||||||
|
"\"velN\":0.035,\"velE\":0.055,\"velD\":0.206,\"geoidSep\":-15.307,\"eph\":" |
||||||
|
"15.200,\"sep\":31.273}"; |
||||||
|
|
||||||
|
#define icmp(want, got) \ |
||||||
|
if (want != got) { \
|
||||||
|
tally++; \
|
||||||
|
printf("wanted %d got %d\n", want, got); \
|
||||||
|
} |
||||||
|
#define fcmp(want, got, tol) \ |
||||||
|
if (fabs(want - got) > tol) { \
|
||||||
|
tally++; \
|
||||||
|
printf("wanted %f got %f diff %f > %f\n", want, got, (want-got), tol); \
|
||||||
|
} |
||||||
|
#define scmp(want, got) \ |
||||||
|
if ((NULL == &got)||(0 != strcmp(want, got))) { \
|
||||||
|
tally++; \
|
||||||
|
printf("wanted '%s' got '%s'\n", want, got); \
|
||||||
|
} |
||||||
|
#define fin() icmp(0, errno) return tally; |
||||||
|
|
||||||
|
static int test_ver() { |
||||||
|
int tally = 0; |
||||||
|
char revision[50]; |
||||||
|
uint16_t pvhi, pvlo; |
||||||
|
const struct json_attr_t json_attrs_version[] = { |
||||||
|
{"class", t_check, .dflt.check = "VERSION"}, |
||||||
|
{"rev", t_string, .addr.string = (char *)&revision, .len = 50}, |
||||||
|
{"proto_major", t_ushort, .addr.ushortint = &pvhi}, |
||||||
|
{"proto_minor", t_ushort, .addr.ushortint = &pvlo}, |
||||||
|
{"", t_ignore}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
printf("."); |
||||||
|
errno = json_read_object(VER, json_attrs_version, NULL); |
||||||
|
icmp(3, pvhi); |
||||||
|
icmp(14, pvlo); |
||||||
|
fin(); |
||||||
|
} |
||||||
|
|
||||||
|
static int test_watch() { |
||||||
|
int tally = 0; |
||||||
|
bool enable, json; |
||||||
|
const struct json_attr_t json_attrs_watch[] = { |
||||||
|
{"class", t_check, .dflt.check = "WATCH"}, |
||||||
|
{"device", t_check, .dflt.check = "/dev/ttyUSB0"}, |
||||||
|
{"enable", t_boolean, .addr.boolean = &enable}, |
||||||
|
{"json", t_boolean, .addr.boolean = &json}, |
||||||
|
{"", t_ignore}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
printf("."); |
||||||
|
errno = json_read_object(WAT, json_attrs_watch, NULL); |
||||||
|
icmp(true, enable); |
||||||
|
icmp(true, json); |
||||||
|
fin(); |
||||||
|
} |
||||||
|
|
||||||
|
static int test_tpv() { |
||||||
|
int tally = 0; |
||||||
|
int gps_mode; |
||||||
|
double ept; |
||||||
|
char gps_time[50]; |
||||||
|
const struct json_attr_t json_attrs_tpv[] = { |
||||||
|
{"class", t_check, .dflt.check = "TPV"}, |
||||||
|
{"device", t_check, .dflt.check = "/dev/ttyUSB0"}, |
||||||
|
{"mode", t_integer, .addr.integer = &gps_mode, .dflt.integer = -1}, |
||||||
|
{"time", t_string, .addr.string = (char *)&gps_time, .len = 50}, |
||||||
|
{"ept", t_real, .addr.real = &ept, .dflt.real = NAN}, |
||||||
|
{"", t_ignore}, |
||||||
|
{NULL}, |
||||||
|
}; |
||||||
|
printf("."); |
||||||
|
errno = json_read_object(TPV, json_attrs_tpv, NULL); |
||||||
|
icmp(3, gps_mode) fcmp(0.005, ept, 0.001); |
||||||
|
scmp("2019-10-04T08:51:34.000Z", gps_time); |
||||||
|
fin(); |
||||||
|
} |
||||||
|
|
||||||
|
int main() { |
||||||
|
int count = 0; |
||||||
|
count += test_ver(); |
||||||
|
count += test_watch(); |
||||||
|
count += test_tpv(); |
||||||
|
if (count) { |
||||||
|
printf("OOPS: %d\n", count); |
||||||
|
} |
||||||
|
return count; |
||||||
|
} |
||||||
Loading…
Reference in new issue