aboutgitcodelistschat:MatrixIRC
path: root/src/cooker
diff options
context:
space:
mode:
Diffstat (limited to 'src/cooker')
-rw-r--r--src/cooker/Makefile20
-rw-r--r--src/cooker/calls.c18
-rw-r--r--src/cooker/calls.h23
-rw-r--r--src/cooker/calls/net.c174
-rw-r--r--src/cooker/calls/net.h11
-rw-r--r--src/cooker/cooker.h99
-rw-r--r--src/cooker/emit.c27
-rw-r--r--src/cooker/emit.h12
-rw-r--r--src/cooker/example.hjson67
-rw-r--r--src/cooker/example.hjson.license2
-rw-r--r--src/cooker/filter.c299
-rw-r--r--src/cooker/filter.h39
-rw-r--r--src/cooker/gluten.c44
-rw-r--r--src/cooker/gluten.h36
-rw-r--r--src/cooker/main.c28
-rw-r--r--src/cooker/parse.c237
-rw-r--r--src/cooker/parse.h11
-rw-r--r--src/cooker/parson.c2080
-rw-r--r--src/cooker/parson.h240
-rw-r--r--src/cooker/util.c29
-rw-r--r--src/cooker/util.h22
21 files changed, 3518 insertions, 0 deletions
diff --git a/src/cooker/Makefile b/src/cooker/Makefile
new file mode 100644
index 0000000..8741879
--- /dev/null
+++ b/src/cooker/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# seitan - Syscall Expressive Interpreter, Transformer and Notifier
+#
+# cooker/Makefile - Makefile for seitan-cooker
+#
+# Copyright 2023 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+SRCS := calls.c emit.c gluten.c main.c parse.c parson.c util.c calls/net.c
+HEADERS := calls.h cooker.h emit.h gluten.h parse.h parson.h util.h calls/net.h
+BIN := $(OUTDIR)cooker
+
+cooker: $(SRCS) $(HEADERS)
+ $(CC) -O0 -g -Wall -Wextra -pedantic -std=c99 -o $(BIN) $(SRCS)
+
+all: cooker
+
+clean:
+ rm -f cooker
diff --git a/src/cooker/calls.c b/src/cooker/calls.c
new file mode 100644
index 0000000..74b5a06
--- /dev/null
+++ b/src/cooker/calls.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/calls.c - Known syscall sets
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "calls.h"
+
+#include "calls/net.h"
+
+struct call *call_sets[] = {
+ syscalls_net, NULL,
+};
diff --git a/src/cooker/calls.h b/src/cooker/calls.h
new file mode 100644
index 0000000..5d46e14
--- /dev/null
+++ b/src/cooker/calls.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef CALLS_H
+#define CALLS_H
+
+/**
+ * struct call - Description of one known system call
+ * @number: Number from __NR_ macros, architecture dependent
+ * @name: Name for use in recipes
+ * @args: NULL-terminated array of argument descriptions
+ */
+struct call {
+ long number;
+ const char *name;
+ struct arg *args;
+};
+
+extern struct call *call_sets[];
+
+#endif /* CALLS_H */
diff --git a/src/cooker/calls/net.c b/src/cooker/calls/net.c
new file mode 100644
index 0000000..c0949cc
--- /dev/null
+++ b/src/cooker/calls/net.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/calls/net.c - Description of known networking system calls
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+/*
+fd = socket(family, type stream/dgram/..., protocol)
+fd = connect(fd, addr, addrlen)
+fd = accept(fd, addr, addrlen)
+n = sendto(fd, buf, len, flags, dst addr, addrlen)
+n = recvfrom(fd, buf, len, flags, src addr, addrlen)
+n = sendmsg(fd, msg, flags)
+n = recvmsg(fd, msg, flags)
+e = shutdown(fd, rd/wr/rdwr)
+e = bind(fd, addr, addrlen)
+e = listen(fd, backlog)
+e = getsockname(fd, bound addr, addrlen)
+e = getpeername(fd, peer addr, addrlen)
+e = socketpair(family, type stream/dgram/..., sockets[2])
+e = setsockopt(fd, level, optname, *optval, optlen)
+e = getsockopt(fd, level, optname, *optval, *optlen)
+n = recvmmsg(fd, *msgvec, vlen, flags, *timeout)
+n = sendmmsg(fd, *msgvec, vlen, flags)
+*/
+
+#include <asm-generic/unistd.h>
+#include <sys/syscall.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <linux/un.h>
+#include <linux/netlink.h>
+
+#include "../cooker.h"
+#include "../calls.h"
+
+static struct arg_num af[] = {
+ { "unix", AF_UNIX },
+ { "ipv4", AF_INET },
+ { "ipv6", AF_INET6 },
+ { "netlink", AF_NETLINK },
+ { "packet", AF_PACKET },
+ { "vsock", AF_VSOCK },
+ { 0 },
+};
+
+static struct arg_num socket_types[] = {
+ { "stream", SOCK_STREAM },
+ { "dgram", SOCK_DGRAM },
+ { "seq", SOCK_SEQPACKET },
+ { "raw", SOCK_RAW },
+ { "packet", SOCK_PACKET },
+ { 0 },
+};
+
+static struct arg_num socket_flags[] = {
+ { "nonblock", SOCK_NONBLOCK },
+ { "cloexec", SOCK_CLOEXEC },
+ { 0 },
+};
+
+static struct arg_num protocols[] = {
+ { "ip", IPPROTO_IP },
+ { "icmp", IPPROTO_ICMP },
+ { "igmp", IPPROTO_IGMP },
+ { "tcp", IPPROTO_TCP },
+ { "udp", IPPROTO_UDP },
+ { "ipv6", IPPROTO_IPV6 },
+ { "gre", IPPROTO_GRE },
+ { "esp", IPPROTO_ESP },
+ { "ah", IPPROTO_AH },
+ { "sctp", IPPROTO_SCTP },
+ { "udplite", IPPROTO_UDPLITE },
+ { "mpls", IPPROTO_MPLS },
+ { "raw", IPPROTO_RAW },
+ { "mptcp", IPPROTO_MPTCP },
+ { 0 },
+};
+
+static struct arg socket_args[] = {
+ { 0, "family", ARG_INT, 0, { .d_num = af } },
+ { 1, "type", ARG_INTMASK, 0, { .d_num = socket_types } },
+ { 1, "flags", ARG_INTFLAGS, 0, { .d_num = socket_flags } },
+ { 2, "protocol", ARG_INT, 0, { .d_num = protocols } },
+ { 0 },
+};
+
+static struct arg_struct connect_addr_unix[] = {
+ { "path", ARG_STRING,
+ offsetof(struct sockaddr_un, sun_path),
+ UNIX_PATH_MAX, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_addr_ipv4[] = {
+ { "port", ARG_PORT,
+ offsetof(struct sockaddr_in, sin_port),
+ 0, { 0 }
+ },
+ { "addr", ARG_IPV4,
+ offsetof(struct sockaddr_in, sin_addr),
+ 0, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_addr_ipv6[] = {
+ { "port", ARG_PORT,
+ offsetof(struct sockaddr_in6, sin6_port),
+ 0, { 0 }
+ },
+ { "addr", ARG_IPV6,
+ offsetof(struct sockaddr_in6, sin6_addr),
+ 0, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_addr_nl[] = {
+ { "pid", ARG_PID,
+ offsetof(struct sockaddr_nl, nl_pid),
+ 0, { 0 }
+ },
+ { "groups", ARG_U32,
+ offsetof(struct sockaddr_in6, sin6_addr),
+ 0, { 0 }
+ },
+ { 0 },
+};
+
+static struct arg_struct connect_family = {
+ "family", ARG_INT,
+ offsetof(struct sockaddr, sa_family),
+ 0, { .d_num = af }
+};
+
+static struct arg_select_num connect_addr_select_family[] = {
+ { AF_UNIX, ARG_STRUCT, { .d_struct = connect_addr_unix } },
+ { AF_INET, ARG_STRUCT, { .d_struct = connect_addr_ipv4 } },
+ { AF_INET6, ARG_STRUCT, { .d_struct = connect_addr_ipv6 } },
+ { AF_NETLINK, ARG_STRUCT, { .d_struct = connect_addr_nl } },
+ { 0 },
+};
+
+static struct arg_select connect_addr_select = {
+ &connect_family, { .d_num = connect_addr_select_family }
+};
+
+static struct arg connect_args[] = {
+ { 0, "fd", ARG_INT, 0,
+ { 0 },
+ },
+ { 0, "path", ARG_FDPATH, 0,
+ { 0 },
+ },
+ { 1, "addr", ARG_SELECT, sizeof(struct sockaddr_storage),
+ { .d_select = &connect_addr_select },
+ },
+ { 2, "addrlen", ARG_LONG, 0,
+ { 0 },
+ },
+};
+
+struct call syscalls_net[] = {
+ { __NR_connect, "connect", connect_args },
+ { __NR_socket, "socket", socket_args },
+ { 0 },
+};
diff --git a/src/cooker/calls/net.h b/src/cooker/calls/net.h
new file mode 100644
index 0000000..105bf4a
--- /dev/null
+++ b/src/cooker/calls/net.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef CALLS_NET_H
+#define CALLS_NET_H
+
+extern struct call syscalls_net[];
+
+#endif /* CALLS_NET_H */
diff --git a/src/cooker/cooker.h b/src/cooker/cooker.h
new file mode 100644
index 0000000..53aa0db
--- /dev/null
+++ b/src/cooker/cooker.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef COOKER_H
+#define COOKER_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define REFS_MAX 256
+#define CALL_ARGS 6
+
+struct arg_num;
+struct arg_struct;
+struct arg_select;
+
+union arg_value {
+ struct arg_num *d_num;
+ struct arg_struct *d_struct;
+ struct arg_select *d_select;
+};
+
+enum arg_type {
+ ARG_INT,
+ ARG_INTMASK,
+ ARG_INTFLAGS,
+
+ ARG_U32,
+ ARG_U32MASK,
+ ARG_U32FLAGS,
+
+ ARG_LONG,
+ ARG_LONGMASK,
+ ARG_LONGFLAGS,
+
+ ARG_STRING,
+
+ ARG_STRUCT,
+ ARG_SELECT,
+
+ ARG_PID,
+
+ ARG_PORT,
+ ARG_IPV4,
+ ARG_IPV6,
+
+ ARG_FDPATH,
+
+ ARG_TYPE_END,
+};
+
+#define ARG_TYPE_COUNT (ARG_TYPE_END - 1)
+
+struct arg_num {
+ char *name;
+ long long value;
+};
+
+struct arg_struct {
+ char *name;
+ enum arg_type type;
+ size_t offset;
+
+ size_t strlen;
+
+ union arg_value desc;
+};
+
+struct arg_select_num {
+ long long value;
+
+ enum arg_type type;
+ union arg_value desc;
+};
+
+struct arg_select {
+ struct arg_struct *field;
+
+ union {
+ struct arg_select_num *d_num;
+ } desc;
+};
+
+struct arg {
+ int pos;
+ char *name;
+
+ enum arg_type type;
+ size_t size;
+
+ union arg_value desc;
+};
+
+#endif /* COOKER_H */
diff --git a/src/cooker/emit.c b/src/cooker/emit.c
new file mode 100644
index 0000000..a82529c
--- /dev/null
+++ b/src/cooker/emit.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/emit.c - Generate gluten (bytecode) instructions
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "gluten.h"
+#include "util.h"
+
+int emit_nr(struct gluten_ctx *g, long number)
+{
+ debug(" %i: OP_NR %li, < >", g->ip++, number);
+
+ return 0;
+}
+
+int emit_load(struct gluten_ctx *g, int offset, int index, size_t len)
+{
+ debug(" %i: OP_LOAD #%i < %i (%lu)", g->ip++, offset, index, len);
+
+ return 0;
+}
diff --git a/src/cooker/emit.h b/src/cooker/emit.h
new file mode 100644
index 0000000..74264b1
--- /dev/null
+++ b/src/cooker/emit.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef EMIT_H
+#define EMIT_H
+
+int emit_nr(struct gluten_ctx *g, long number);
+int emit_load(struct gluten_ctx *g, int offset, int index, size_t len);
+
+#endif /* EMIT_H */
diff --git a/src/cooker/example.hjson b/src/cooker/example.hjson
new file mode 100644
index 0000000..45ed339
--- /dev/null
+++ b/src/cooker/example.hjson
@@ -0,0 +1,67 @@
+[
+ {
+ "match": [ /* qemu-pr-helper and similar */
+ { "connect": { "addr": { "family": "unix", "path": "/var/run/pr-helper.sock" }, "fd": { "ref": "fd" } } }
+ ],
+ "call": { "connect": { "addr": { "family": "unix", "path": "/var/run/pr-helper.sock" }, "ret": "y" } },
+ "inject": { "what": "fd", "new": { "ref": "y" }, "old": { "ref": "fd" }, "return": 0 }
+ },
+ {
+ "match": [ /* qemu creates a tap interface */
+ { "ioctl": { "path": "/dev/net/tun", "request": "TUNSETIFF", "ifr": { "name": "tap0", "flags": "IFF_TUN" } } }
+ ],
+ "limit": { "scope": "process", "count": 1 },
+ "call": { "ioctl": { "request": "TUNSETIFF", "path": "/dev/net/tun", "ifr": { "name": "tap0", "flags": "IFF_TUN", "ret": "x" } } },
+ "return": { "ref": "x" }
+ },
+ {
+ "match": [ /* CVE-2022-0185-style */
+ { "unshare": { "flags": { "has": { "newuser": true, "newnet": false } } } }
+ ],
+ "block": { }
+ },
+ {
+ "match": [ /* passt */
+ { "unshare": { "flags": { "has": [ "ipc", "mount", "uts", "pid" ] } } }
+ ],
+ "block": { }
+ },
+ {
+ "match": [ /* Giuseppe's example */
+ { "mknod": { "path": { "ref": "path" }, "mode": "c", "major": 1, "minor": { "in": [ 3, 5, 7, 8, 9 ], "ref": "minor" } } }
+ ],
+ "context": { "userns": "init", "mountns": "caller" },
+ "call": { "mknod": { "path": { "ref": "path" }, "mode": "c", "major": 1, "minor": { "ref": "minor" }, "ret": "x" } },
+ "inject": { "what": "fd", "new": { "ref": "x" } },
+ "return": { "ref": "x" }
+ }
+]
+
+/*
+ * INTFLAGS, LONGFLAGS, U32FLAGS
+ *
+ * "field": { "in": [ "ipc", "mount", "uts" ] }
+ * flags & set
+ * !!(flags & (ipc | mount | ns))
+ *
+ * "field": { "all": [ "ipc", "mount", "uts" ] }
+ * flags & set == set
+ * flags & (ipc | mount | ns) == (ipc | mount | ns)
+ *
+ * "field": { "not": [ "ipc", "mount", "uts" ] }
+ * !(flags & set)
+ *
+ * "field": { "ipc": false, "mount": true, "uts": false }
+ * flags & set == set
+ * !(flags & ipc) && (flags & mount) && !(flags & utc)
+ *
+ * "field": { "ipc" }
+ * flags == ipc
+ *
+ * INTMASK
+ * value = (target value & known values)
+ *
+ * INT, LONG, U32
+ * "arg": { "in": [ 0, 1 ] }
+ * arg == 0 || arg == 1
+ */
diff --git a/src/cooker/example.hjson.license b/src/cooker/example.hjson.license
new file mode 100644
index 0000000..2e3bd69
--- /dev/null
+++ b/src/cooker/example.hjson.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2023 Red Hat GmbH <sbrivio@redhat.com>
+SPDX-License-Identifier: GPL-3.0-or-later
diff --git a/src/cooker/filter.c b/src/cooker/filter.c
new file mode 100644
index 0000000..dbda7ca
--- /dev/null
+++ b/src/cooker/filter.c
@@ -0,0 +1,299 @@
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "numbers.h"
+#include "filter.h"
+
+#define N_SYSCALL sizeof(numbers) / sizeof(numbers[0])
+
+static int compare_key(const void *key, const void *base)
+{
+ return strcmp((const char *)key,
+ ((struct syscall_numbers *)base)->name);
+}
+
+int compare_bpf_call_names(const void *a, const void *b)
+{
+ return strcmp(((struct bpf_call *)a)->name,
+ ((struct bpf_call *)b)->name);
+}
+
+static int compare_table_nr(const void *a, const void *b)
+{
+ return (((struct syscall_entry *)a)->nr -
+ ((struct syscall_entry *)b)->nr);
+}
+
+static unsigned int count_shift_right(unsigned int n)
+{
+ unsigned int i = 0;
+ for (; n > 0; i++) {
+ n = n >> 1;
+ }
+ return i;
+}
+
+static void insert_pair(int jumps[], int arr[], unsigned int level)
+{
+ unsigned int i_a, i;
+ for (i = 0; i < level; i++) {
+ i_a = 2 * i + 1;
+ if (arr[i_a] == EMPTY) {
+ jumps[i] = arr[i_a - 1];
+ } else {
+ jumps[i] = arr[i_a];
+ }
+ }
+}
+
+unsigned int left_child(unsigned int parent_index)
+{
+ unsigned int level = count_shift_right(parent_index + 1);
+ /* 2^(level) -1 gives the beginning of the next interval */
+ unsigned int next_interval = (1 << level) - 1;
+ /* 2^(level -1) -1 gives the beginning of the current interval */
+ unsigned begin = (1 << (level - 1)) - 1;
+ unsigned i = parent_index - begin;
+ return next_interval + 2 * i;
+}
+
+unsigned int right_child(unsigned int parent_index)
+{
+ return left_child(parent_index) + 1;
+}
+
+void create_lookup_nodes(int jumps[], unsigned int n)
+{
+ unsigned int i, index;
+ unsigned int old_interval, interval;
+
+ for (i = 0; i < MAX_JUMPS; i++)
+ jumps[i] = EMPTY;
+
+ if (n < 2) {
+ jumps[0] = 0;
+ return;
+ }
+ old_interval = 1 << count_shift_right(n - 1);
+ interval = old_interval >> 1;
+
+ /* first scan populate the last level of jumps */
+ for (i = interval - 1, index = 1; index < old_interval && index < n;
+ i++, index += 2) {
+ jumps[i] = index;
+ }
+ if (n % 2 == 1) {
+ jumps[i] = index - 1;
+ }
+ for (old_interval = interval, interval = interval / 2; interval > 0;
+ old_interval = interval, interval = interval / 2) {
+ insert_pair(&jumps[interval - 1], &jumps[old_interval - 1],
+ interval);
+ }
+}
+
+long resolve_syscall_nr(const char *name)
+{
+ struct syscall_numbers *p;
+ p = (struct syscall_numbers *)bsearch(
+ name, numbers, sizeof(numbers) / sizeof(numbers[0]),
+ sizeof(numbers[0]), compare_key);
+ if (p == NULL)
+ return -1;
+ return p->number;
+}
+
+/*
+ * Construct a syscall tables ordered by increasing syscall number
+ * @returns number of syscall entries in the table
+ */
+int construct_table(const struct bpf_call *entries, int n,
+ struct syscall_entry *table)
+{
+ long nr;
+ unsigned int tn;
+ int i;
+
+ tn = 0;
+ for (i = 0; i < n; i++) {
+ table[i].count = 0;
+ table[i].entry = NULL;
+ }
+
+ for (i = 0; i < n; i++) {
+ if (tn > N_SYSCALL - 1)
+ return -1;
+ if (i > 0) {
+ if (strcmp((entries[i]).name, (entries[i - 1]).name) ==
+ 0) {
+ table[tn - 1].count++;
+ continue;
+ }
+ }
+ nr = resolve_syscall_nr((entries[i]).name);
+ if (nr < 0) {
+ fprintf(stderr, "wrong syscall number for %s\n",
+ (entries[i]).name);
+ continue;
+ }
+ table[tn].entry = &entries[i];
+ table[tn].count++;
+ table[tn].nr = nr;
+ tn++;
+ }
+ qsort(table, tn, sizeof(struct syscall_entry), compare_table_nr);
+
+ return tn;
+}
+
+static unsigned get_n_args(const struct syscall_entry *table)
+{
+ unsigned i, k, n;
+ n = 0;
+ for (i = 0; i < table->count; i++)
+ for (k = 0; k < 6; k++)
+ if ((table->entry + i)->check_arg[k])
+ n++;
+ return n;
+}
+
+static unsigned int get_total_args(const struct syscall_entry table[],
+ unsigned int n_syscall)
+{
+ unsigned int i, n;
+ n = 0;
+ for (i = 0; i < n_syscall; i++) {
+ n += get_n_args(&table[i]);
+ }
+ return n;
+}
+
+unsigned int create_bpf_program_log(struct sock_filter filter[])
+{
+ filter[0] = (struct sock_filter)BPF_STMT(
+ BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, arch)));
+ filter[1] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,
+ SEITAN_AUDIT_ARCH, 0, 1);
+ filter[2] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_USER_NOTIF);
+ filter[3] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_ALLOW);
+ return 4;
+}
+
+unsigned int create_bfp_program(struct syscall_entry table[],
+ struct sock_filter filter[],
+ unsigned int n_syscall)
+{
+ unsigned int offset_left, offset_right;
+ unsigned int n_args, n_nodes;
+ unsigned int notify, accept;
+ unsigned int i, j, k, size;
+ unsigned int next_offset;
+ int nodes[MAX_JUMPS];
+
+ create_lookup_nodes(nodes, n_syscall);
+
+ size = 3;
+ /* No nodes if there is a single syscall */
+ n_nodes = (1 << count_shift_right(n_syscall - 1)) - 1;
+
+ n_args = get_total_args(table, n_syscall);
+
+ accept = 2 + n_nodes + 2 * n_syscall + n_args + 1;
+ notify = 2 + n_nodes + 2 * n_syscall + n_args + 2;
+
+ /* Pre */
+ /* cppcheck-suppress badBitmaskCheck */
+ filter[0] = (struct sock_filter)BPF_STMT(
+ BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, arch)));
+ filter[1] = (struct sock_filter)BPF_JUMP(
+ BPF_JMP | BPF_JEQ | BPF_K, SEITAN_AUDIT_ARCH, 0, accept - 2);
+ /* cppcheck-suppress badBitmaskCheck */
+ filter[2] = (struct sock_filter)BPF_STMT(
+ BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr)));
+
+ /* Insert nodes */
+ for (i = 0; i < n_nodes; i++) {
+ if (nodes[i] == EMPTY) {
+ filter[size++] =
+ (struct sock_filter)JUMPA(accept - size);
+ } else {
+ offset_left = left_child(i) - i - 1;
+ offset_right = right_child(i) - i - 1;
+ filter[size++] = (struct sock_filter)JGE(
+ table[nodes[i]].nr, offset_right, offset_left);
+ }
+ }
+
+ next_offset = n_syscall - 1;
+ /* Insert leaves */
+ for (i = 0; i < n_syscall; i++) {
+ filter[size++] = (struct sock_filter)EQ(
+ table[i].nr, next_offset, accept - size);
+ next_offset += get_n_args(&table[i]);
+ }
+
+ /*
+ * Insert args. Evaluate every args, if it doesn't match continue with
+ * the following, otherwise notify.
+ */
+ for (i = 0; i < n_syscall; i++) {
+ for (j = 0; j < (table[i]).count; j++) {
+ for (k = 0; k < 6; k++)
+ if ((table[i].entry + j)->check_arg[k]) {
+ filter[size++] = (struct sock_filter)EQ(
+ (table[i].entry + j)->args[k],
+ notify - size, 0);
+ }
+ }
+ filter[size++] = (struct sock_filter)JUMPA(accept - size);
+ }
+
+ /* Seccomp accept and notify instruction */
+ filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_ALLOW);
+ filter[size++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_USER_NOTIF);
+ return size;
+}
+
+static int compare_names(const void *a, const void *b)
+{
+ return strcmp(((struct syscall_numbers *)a)->name,
+ ((struct syscall_numbers *)b)->name);
+}
+
+int convert_bpf(char *file, struct bpf_call *entries, int n, bool log)
+{
+ int nt, fd, fsize;
+ struct syscall_entry table[N_SYSCALL];
+ struct sock_filter filter[MAX_FILTER];
+
+ qsort(numbers, sizeof(numbers) / sizeof(numbers[0]), sizeof(numbers[0]),
+ compare_names);
+
+ qsort(entries, n, sizeof(struct bpf_call), compare_bpf_call_names);
+ nt = construct_table(entries, n, table);
+
+ if (log)
+ fsize = create_bpf_program_log(filter);
+ else
+ fsize = create_bfp_program(table, filter, nt);
+
+ fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
+ S_IRUSR | S_IWUSR);
+ write(fd, filter, sizeof(struct sock_filter) * fsize);
+
+ close(fd);
+
+ return 0;
+}
diff --git a/src/cooker/filter.h b/src/cooker/filter.h
new file mode 100644
index 0000000..ee5ab12
--- /dev/null
+++ b/src/cooker/filter.h
@@ -0,0 +1,39 @@
+#ifndef FILTER_H_
+#define FILTER_H_
+
+#include <linux/filter.h>
+#include <linux/audit.h>
+#include <linux/seccomp.h>
+
+#define JGE(nr, right, left) \
+ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, (nr), (right), (left))
+#define JUMPA(jump) BPF_JUMP(BPF_JMP | BPF_JA, (jump), 0, 0)
+#define EQ(nr, a1, a2) BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (nr), (a1), (a2))
+
+#define MAX_FILTER 1024
+
+#define MAX_JUMPS 128
+#define EMPTY -1
+
+struct bpf_call {
+ char *name;
+ int args[6];
+ bool check_arg[6];
+};
+
+struct syscall_entry {
+ unsigned int count;
+ long nr;
+ const struct bpf_call *entry;
+};
+
+void create_lookup_nodes(int jumps[], unsigned int n);
+unsigned int left_child(unsigned int parent_index);
+unsigned int right_child(unsigned int parent_index);
+
+unsigned int create_bfp_program(struct syscall_entry table[],
+ struct sock_filter filter[],
+ unsigned int n_syscall);
+int convert_bpf(char *file, struct bpf_call *entries, int n, bool log);
+
+#endif
diff --git a/src/cooker/gluten.c b/src/cooker/gluten.c
new file mode 100644
index 0000000..1116e6b
--- /dev/null
+++ b/src/cooker/gluten.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/gluten.c - gluten (bytecode) file and layout functions
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "gluten.h"
+#include "util.h"
+
+#define GLUTEN_INST_SIZE BUFSIZ
+#define GLUTEN_DATA_SIZE BUFSIZ
+
+static char gluten[GLUTEN_INST_SIZE + GLUTEN_DATA_SIZE];
+
+static size_t gluten_arg_storage[ARG_TYPE_COUNT] = {
+ [ARG_INT] = sizeof(int),
+ [ARG_INTMASK] = sizeof(int),
+};
+
+int gluten_alloc(struct gluten_ctx *g, size_t size)
+{
+ debug(" allocating %lu at offset %i", size, g->sp);
+ if ((g->sp += size) >= GLUTEN_DATA_SIZE)
+ die("Temporary data size exceeded");
+
+ return g->sp - size;
+}
+
+int gluten_alloc_type(struct gluten_ctx *g, enum arg_type type)
+{
+ return gluten_alloc(g, gluten_arg_storage[type]);
+}
+
+int gluten_init(struct gluten_ctx *g)
+{
+ g->gluten = gluten;
+
+ return 0;
+}
diff --git a/src/cooker/gluten.h b/src/cooker/gluten.h
new file mode 100644
index 0000000..440029d
--- /dev/null
+++ b/src/cooker/gluten.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef GLUTEN_H
+#define GLUTEN_H
+
+struct gluten_arg_data {
+ int offset;
+ size_t len;
+};
+
+struct gluten_ref_data {
+ int name;
+ int offset;
+ size_t len;
+};
+
+struct gluten_ctx {
+ int ip;
+ int lr;
+ int sp;
+ char *gluten;
+
+ struct gluten_arg_data match_dst[CALL_ARGS];
+ struct gluten_arg_data call_src[CALL_ARGS];
+
+ struct gluten_ref_data refs[REFS_MAX];
+};
+
+int gluten_alloc(struct gluten_ctx *g, size_t size);
+int gluten_alloc_type(struct gluten_ctx *g, enum arg_type type);
+int gluten_init(struct gluten_ctx *g);
+
+#endif /* GLUTEN_H */
diff --git a/src/cooker/main.c b/src/cooker/main.c
new file mode 100644
index 0000000..9965cff
--- /dev/null
+++ b/src/cooker/main.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/main.c - Entry point of seitan-cooker, the gluten (bytecode) generator
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "cooker.h"
+#include "gluten.h"
+#include "parse.h"
+
+int main(int argc, char **argv)
+{
+ struct gluten_ctx g = { 0 };
+
+ /* TODO: Options and usage */
+ (void)argc;
+ (void)argv;
+
+ gluten_init(&g);
+
+ parse_file(&g, argv[1]);
+
+ return 0;
+}
diff --git a/src/cooker/parse.c b/src/cooker/parse.c
new file mode 100644
index 0000000..9d8a7be
--- /dev/null
+++ b/src/cooker/parse.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/parse.c - JSON recipe parsing
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include "parson.h"
+#include "calls.h"
+#include "cooker.h"
+#include "gluten.h"
+#include "emit.h"
+#include "util.h"
+
+#include "calls/net.h"
+
+struct rule_parser {
+ const char *type;
+ int (*fn)(struct gluten_ctx *g, JSON_Value *value);
+};
+
+static int parse_match_load(struct gluten_ctx *g, struct arg *a)
+{
+ if (!a->size || g->match_dst[a->pos].len)
+ return 0;
+
+ g->match_dst[a->pos].offset = gluten_alloc(g, a->size);
+ g->match_dst[a->pos].len = a->size;
+
+ emit_load(g, g->match_dst[a->pos].offset, a->pos, a->size);
+
+ return 0;
+}
+
+static long long parse_match_expr_num(struct arg_num *desc, JSON_Value *value)
+{
+ const char *s = NULL;
+ long long n;
+
+ if (desc) {
+ s = json_value_get_string(value);
+ for (; desc->name && s && strcmp(s, desc->name); desc++);
+ if (s && !desc->name)
+ die(" Invalid value %s", s);
+
+ n = desc->value;
+ }
+
+ if (!s) {
+ if (json_value_get_type(value) != JSONNumber)
+ die(" Invalid value type");
+
+ n = json_value_get_number(value);
+ }
+
+ return n;
+}
+
+static int parse_match_key(struct gluten_ctx *g, int index, enum arg_type type,
+ union arg_value desc, JSON_Value *value)
+{
+ JSON_Object *tmp;
+ const char *ref;
+
+ (void)index;
+
+ if (json_value_get_type(value) == JSONObject &&
+ (tmp = json_value_get_object(value)) &&
+ (ref = json_object_get_string(tmp, "ref"))) {
+ debug(" setting reference '%s'", ref);
+ gluten_alloc_type(g, type);
+ value = json_object_get_value(tmp, "value");
+ }
+
+ if (!value)
+ return 0;
+
+ switch (type) {
+ case ARG_INTFLAGS:
+ case ARG_LONGFLAGS:
+ case ARG_U32FLAGS:
+ /* fetch/combine expr algebra loop */
+ case ARG_INTMASK:
+ /* calculate mask first */
+ case ARG_INT:
+ case ARG_LONG:
+ case ARG_U32:
+ parse_match_expr_num(desc.d_num, value);
+ //emit_cmp(...);
+ default:
+ ;
+ }
+
+ return 0;
+}
+
+static int parse_match_arg(struct gluten_ctx *g, const char *name,
+ JSON_Value *value, struct arg *a)
+{
+ debug(" Parsing match argument %s", name);
+
+ parse_match_load(g, a);
+ parse_match_key(g, a->pos, a->type, a->desc, value);
+
+ return 0;
+}
+
+static int parse_match(struct gluten_ctx *g, JSON_Object *obj, struct arg *args)
+{
+ unsigned count = 0;
+ struct arg *a;
+
+ for (a = args; a->name; a++) {
+ JSON_Value *value;
+
+ if ((value = json_object_get_value(obj, a->name))) {
+ count++;
+ parse_match_arg(g, a->name, value, a);
+ }
+ }
+
+ if (count != json_object_get_count(obj))
+ die(" Stray elements in match");
+
+ return 0;
+}
+
+static int parse_matches(struct gluten_ctx *g, JSON_Value *value)
+{
+ JSON_Array *matches = json_value_get_array(value);
+ unsigned i;
+
+ for (i = 0; i < json_array_get_count(matches); i++) {
+ JSON_Object *match, *args;
+ struct call **set, *call;
+ const char *name;
+
+ g->lr = g->ip;
+ g->sp = 0;
+
+ match = json_array_get_object(matches, i);
+ name = json_object_get_name(match, 0);
+ args = json_object_get_object(match, name);
+ debug(" Parsing match %i: %s", i, name);
+
+ for (set = call_sets, call = set[0]; *set; call++) {
+ if (!call->name) {
+ set++;
+ continue;
+ }
+
+ if (!strcmp(name, call->name)) {
+ debug(" Found handler for %s", name);
+ emit_nr(g, call->number);
+
+ parse_match(g, args, call->args);
+ break;
+ }
+ }
+
+ if (!*set)
+ die(" Unknown system call: %s", name);
+ }
+
+ return 0;
+}
+
+static int parse_call(struct gluten_ctx *g, JSON_Value *value)
+{
+ (void)g;
+ (void)value;
+ return 0;
+}
+
+static int parse_inject(struct gluten_ctx *g, JSON_Value *value)
+{
+ (void)g;
+ (void)value;
+ return 0;
+}
+
+struct rule_parser parsers[] = {
+ { "match", parse_matches },
+ { "call", parse_call },
+ { "inject", parse_inject },
+ { NULL, NULL },
+};
+
+static int parse_block(struct gluten_ctx *g, JSON_Object *block)
+{
+ unsigned i;
+
+ for (i = 0; i < json_object_get_count(block); i++) {
+ struct rule_parser *parser;
+ JSON_Value *rule;
+ const char *type;
+
+ type = json_object_get_name(block, i);
+ rule = json_object_get_value(block, type);
+
+ for (parser = parsers; parser->type; parser++) {
+ if (!strcmp(type, parser->type)) {
+ parser->fn(g, rule);
+ break;
+ }
+ }
+
+ if (!parser->type)
+ die(" Invalid rule type: \"%s\"", type);
+ }
+
+ return 0;
+}
+
+int parse_file(struct gluten_ctx *g, const char *path)
+{
+ JSON_Array *blocks;
+ JSON_Value *root;
+ JSON_Object *obj;
+ unsigned i;
+
+ root = json_parse_file_with_comments(path);
+ if (json_value_get_type(root) != JSONArray)
+ die("Invalid input file %s", path);
+
+ blocks = json_value_get_array(root);
+ for (i = 0; i < json_array_get_count(blocks); i++) {
+ obj = json_array_get_object(blocks, i);
+ debug("Parsing block %i", i);
+ parse_block(g, obj);
+ }
+
+ return 0;
+}
diff --git a/src/cooker/parse.h b/src/cooker/parse.h
new file mode 100644
index 0000000..cb4a2f2
--- /dev/null
+++ b/src/cooker/parse.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef PARSE_H
+#define PARSE_H
+
+int parse_file(struct gluten_ctx *g, const char *path);
+
+#endif /* PARSE_H */
diff --git a/src/cooker/parson.c b/src/cooker/parson.c
new file mode 100644
index 0000000..a7a844a
--- /dev/null
+++ b/src/cooker/parson.c
@@ -0,0 +1,2080 @@
+/*
+ SPDX-License-Identifier: MIT
+
+ Parson ( http://kgabis.github.com/parson/ )
+ Copyright (c) 2012 - 2019 Krzysztof Gabis
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+#ifdef _MSC_VER
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif /* _CRT_SECURE_NO_WARNINGS */
+#endif /* _MSC_VER */
+
+#include "parson.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <errno.h>
+
+/* Apparently sscanf is not implemented in some "standard" libraries, so don't use it, if you
+ * don't have to. */
+#define sscanf THINK_TWICE_ABOUT_USING_SSCANF
+
+#define STARTING_CAPACITY 16
+#define MAX_NESTING 2048
+
+#define FLOAT_FORMAT "%1.17g" /* do not increase precision without incresing NUM_BUF_SIZE */
+#define NUM_BUF_SIZE 64 /* double printed with "%1.17g" shouldn't be longer than 25 bytes so let's be paranoid and use 64 */
+
+#define SIZEOF_TOKEN(a) (sizeof(a) - 1)
+#define SKIP_CHAR(str) ((*str)++)
+#define SKIP_WHITESPACES(str) while (isspace((unsigned char)(**str))) { SKIP_CHAR(str); }
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#undef malloc
+#undef free
+
+#if defined(isnan) && defined(isinf)
+#define IS_NUMBER_INVALID(x) (isnan((x)) || isinf((x)))
+#else
+#define IS_NUMBER_INVALID(x) (((x) * 0.0) != 0.0)
+#endif
+
+static JSON_Malloc_Function parson_malloc = malloc;
+static JSON_Free_Function parson_free = free;
+
+static int parson_escape_slashes = 1;
+
+#define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */
+
+/* Type definitions */
+typedef union json_value_value {
+ char *string;
+ double number;
+ JSON_Object *object;
+ JSON_Array *array;
+ int boolean;
+ int null;
+} JSON_Value_Value;
+
+struct json_value_t {
+ JSON_Value *parent;
+ JSON_Value_Type type;
+ JSON_Value_Value value;
+};
+
+struct json_object_t {
+ JSON_Value *wrapping_value;
+ char **names;
+ JSON_Value **values;
+ size_t count;
+ size_t capacity;
+};
+
+struct json_array_t {
+ JSON_Value *wrapping_value;
+ JSON_Value **items;
+ size_t count;
+ size_t capacity;
+};
+
+/* Various */
+static char * read_file(const char *filename);
+static void remove_comments(char *string, const char *start_token, const char *end_token);
+static char * parson_strndup(const char *string, size_t n);
+static char * parson_strdup(const char *string);
+static int hex_char_to_int(char c);
+static int parse_utf16_hex(const char *string, unsigned int *result);
+static int num_bytes_in_utf8_sequence(unsigned char c);
+static int verify_utf8_sequence(const unsigned char *string, int *len);
+static int is_valid_utf8(const char *string, size_t string_len);
+static int is_decimal(const char *string, size_t length);
+
+/* JSON Object */
+static JSON_Object * json_object_init(JSON_Value *wrapping_value);
+static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value);
+static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value);
+static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity);
+static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len);
+static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value);
+static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value);
+static void json_object_free(JSON_Object *object);
+
+/* JSON Array */
+static JSON_Array * json_array_init(JSON_Value *wrapping_value);
+static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value);
+static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity);
+static void json_array_free(JSON_Array *array);
+
+/* JSON Value */
+static JSON_Value * json_value_init_string_no_copy(char *string);
+
+/* Parser */
+static JSON_Status skip_quotes(const char **string);
+static int parse_utf16(const char **unprocessed, char **processed);
+static char * process_string(const char *input, size_t len);
+static char * get_quoted_string(const char **string);
+static JSON_Value * parse_object_value(const char **string, size_t nesting);
+static JSON_Value * parse_array_value(const char **string, size_t nesting);
+static JSON_Value * parse_string_value(const char **string);
+static JSON_Value * parse_boolean_value(const char **string);
+static JSON_Value * parse_number_value(const char **string);
+static JSON_Value * parse_null_value(const char **string);
+static JSON_Value * parse_value(const char **string, size_t nesting);
+
+/* Serialization */
+static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf);
+static int json_serialize_string(const char *string, char *buf);
+static int append_indent(char *buf, int level);
+static int append_string(char *buf, const char *string);
+
+/* Various */
+static char * parson_strndup(const char *string, size_t n) {
+ char *output_string = (char*)parson_malloc(n + 1);
+ if (!output_string) {
+ return NULL;
+ }
+ output_string[n] = '\0';
+ strncpy(output_string, string, n);
+ return output_string;
+}
+
+static char * parson_strdup(const char *string) {
+ return parson_strndup(string, strlen(string));
+}
+
+static int hex_char_to_int(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 10;
+ }
+ return -1;
+}
+
+static int parse_utf16_hex(const char *s, unsigned int *result) {
+ int x1, x2, x3, x4;
+ if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0' || s[3] == '\0') {
+ return 0;
+ }
+ x1 = hex_char_to_int(s[0]);
+ x2 = hex_char_to_int(s[1]);
+ x3 = hex_char_to_int(s[2]);
+ x4 = hex_char_to_int(s[3]);
+ if (x1 == -1 || x2 == -1 || x3 == -1 || x4 == -1) {
+ return 0;
+ }
+ *result = (unsigned int)((x1 << 12) | (x2 << 8) | (x3 << 4) | x4);
+ return 1;
+}
+
+static int num_bytes_in_utf8_sequence(unsigned char c) {
+ if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) {
+ return 0;
+ } else if ((c & 0x80) == 0) { /* 0xxxxxxx */
+ return 1;
+ } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */
+ return 2;
+ } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */
+ return 3;
+ } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */
+ return 4;
+ }
+ return 0; /* won't happen */
+}
+
+static int verify_utf8_sequence(const unsigned char *string, int *len) {
+ unsigned int cp = 0;
+ *len = num_bytes_in_utf8_sequence(string[0]);
+
+ if (*len == 1) {
+ cp = string[0];
+ } else if (*len == 2 && IS_CONT(string[1])) {
+ cp = string[0] & 0x1F;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) {
+ cp = ((unsigned char)string[0]) & 0xF;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ cp = (cp << 6) | (string[2] & 0x3F);
+ } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) {
+ cp = string[0] & 0x7;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ cp = (cp << 6) | (string[2] & 0x3F);
+ cp = (cp << 6) | (string[3] & 0x3F);
+ } else {
+ return 0;
+ }
+
+ /* overlong encodings */
+ if ((cp < 0x80 && *len > 1) ||
+ (cp < 0x800 && *len > 2) ||
+ (cp < 0x10000 && *len > 3)) {
+ return 0;
+ }
+
+ /* invalid unicode */
+ if (cp > 0x10FFFF) {
+ return 0;
+ }
+
+ /* surrogate halves */
+ if (cp >= 0xD800 && cp <= 0xDFFF) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int is_valid_utf8(const char *string, size_t string_len) {
+ int len = 0;
+ const char *string_end = string + string_len;
+ while (string < string_end) {
+ if (!verify_utf8_sequence((const unsigned char*)string, &len)) {
+ return 0;
+ }
+ string += len;
+ }
+ return 1;
+}
+
+static int is_decimal(const char *string, size_t length) {
+ if (length > 1 && string[0] == '0' && string[1] != '.') {
+ return 0;
+ }
+ if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') {
+ return 0;
+ }
+ while (length--) {
+ if (strchr("xX", string[length])) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static char * read_file(const char * filename) {
+ FILE *fp = fopen(filename, "r");
+ size_t size_to_read = 0;
+ size_t size_read = 0;
+ long pos;
+ char *file_contents;
+ if (!fp) {
+ return NULL;
+ }
+ fseek(fp, 0L, SEEK_END);
+ pos = ftell(fp);
+ if (pos < 0) {
+ fclose(fp);
+ return NULL;
+ }
+ size_to_read = pos;
+ rewind(fp);
+ file_contents = (char*)parson_malloc(sizeof(char) * (size_to_read + 1));
+ if (!file_contents) {
+ fclose(fp);
+ return NULL;
+ }
+ size_read = fread(file_contents, 1, size_to_read, fp);
+ if (size_read == 0 || ferror(fp)) {
+ fclose(fp);
+ parson_free(file_contents);
+ return NULL;
+ }
+ fclose(fp);
+ file_contents[size_read] = '\0';
+ return file_contents;
+}
+
+static void remove_comments(char *string, const char *start_token, const char *end_token) {
+ int in_string = 0, escaped = 0;
+ size_t i;
+ char *ptr = NULL, current_char;
+ size_t start_token_len = strlen(start_token);
+ size_t end_token_len = strlen(end_token);
+ if (start_token_len == 0 || end_token_len == 0) {
+ return;
+ }
+ while ((current_char = *string) != '\0') {
+ if (current_char == '\\' && !escaped) {
+ escaped = 1;
+ string++;
+ continue;
+ } else if (current_char == '\"' && !escaped) {
+ in_string = !in_string;
+ } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) {
+ for(i = 0; i < start_token_len; i++) {
+ string[i] = ' ';
+ }
+ string = string + start_token_len;
+ ptr = strstr(string, end_token);
+ if (!ptr) {
+ return;
+ }
+ for (i = 0; i < (ptr - string) + end_token_len; i++) {
+ string[i] = ' ';
+ }
+ string = ptr + end_token_len - 1;
+ }
+ escaped = 0;
+ string++;
+ }
+}
+
+/* JSON Object */
+static JSON_Object * json_object_init(JSON_Value *wrapping_value) {
+ JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object));
+ if (new_obj == NULL) {
+ return NULL;
+ }
+ new_obj->wrapping_value = wrapping_value;
+ new_obj->names = (char**)NULL;
+ new_obj->values = (JSON_Value**)NULL;
+ new_obj->capacity = 0;
+ new_obj->count = 0;
+ return new_obj;
+}
+
+static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) {
+ if (name == NULL) {
+ return JSONFailure;
+ }
+ return json_object_addn(object, name, strlen(name), value);
+}
+
+static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value) {
+ size_t index = 0;
+ if (object == NULL || name == NULL || value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_getn_value(object, name, name_len) != NULL) {
+ return JSONFailure;
+ }
+ if (object->count >= object->capacity) {
+ size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY);
+ if (json_object_resize(object, new_capacity) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ index = object->count;
+ object->names[index] = parson_strndup(name, name_len);
+ if (object->names[index] == NULL) {
+ return JSONFailure;
+ }
+ value->parent = json_object_get_wrapping_value(object);
+ object->values[index] = value;
+ object->count++;
+ return JSONSuccess;
+}
+
+static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) {
+ char **temp_names = NULL;
+ JSON_Value **temp_values = NULL;
+
+ if ((object->names == NULL && object->values != NULL) ||
+ (object->names != NULL && object->values == NULL) ||
+ new_capacity == 0) {
+ return JSONFailure; /* Shouldn't happen */
+ }
+ temp_names = (char**)parson_malloc(new_capacity * sizeof(char*));
+ if (temp_names == NULL) {
+ return JSONFailure;
+ }
+ temp_values = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*));
+ if (temp_values == NULL) {
+ parson_free(temp_names);
+ return JSONFailure;
+ }
+ if (object->names != NULL && object->values != NULL && object->count > 0) {
+ memcpy(temp_names, object->names, object->count * sizeof(char*));
+ memcpy(temp_values, object->values, object->count * sizeof(JSON_Value*));
+ }
+ parson_free(object->names);
+ parson_free(object->values);
+ object->names = temp_names;
+ object->values = temp_values;
+ object->capacity = new_capacity;
+ return JSONSuccess;
+}
+
+static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len) {
+ size_t i, name_length;
+ for (i = 0; i < json_object_get_count(object); i++) {
+ name_length = strlen(object->names[i]);
+ if (name_length != name_len) {
+ continue;
+ }
+ if (strncmp(object->names[i], name, name_len) == 0) {
+ return object->values[i];
+ }
+ }
+ return NULL;
+}
+
+static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value) {
+ size_t i = 0, last_item_index = 0;
+ if (object == NULL || json_object_get_value(object, name) == NULL) {
+ return JSONFailure;
+ }
+ last_item_index = json_object_get_count(object) - 1;
+ for (i = 0; i < json_object_get_count(object); i++) {
+ if (strcmp(object->names[i], name) == 0) {
+ parson_free(object->names[i]);
+ if (free_value) {
+ json_value_free(object->values[i]);
+ }
+ if (i != last_item_index) { /* Replace key value pair with one from the end */
+ object->names[i] = object->names[last_item_index];
+ object->values[i] = object->values[last_item_index];
+ }
+ object->count -= 1;
+ return JSONSuccess;
+ }
+ }
+ return JSONFailure; /* No execution path should end here */
+}
+
+static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value) {
+ JSON_Value *temp_value = NULL;
+ JSON_Object *temp_object = NULL;
+ const char *dot_pos = strchr(name, '.');
+ if (dot_pos == NULL) {
+ return json_object_remove_internal(object, name, free_value);
+ }
+ temp_value = json_object_getn_value(object, name, dot_pos - name);
+ if (json_value_get_type(temp_value) != JSONObject) {
+ return JSONFailure;
+ }
+ temp_object = json_value_get_object(temp_value);
+ return json_object_dotremove_internal(temp_object, dot_pos + 1, free_value);
+}
+
+static void json_object_free(JSON_Object *object) {
+ size_t i;
+ for (i = 0; i < object->count; i++) {
+ parson_free(object->names[i]);
+ json_value_free(object->values[i]);
+ }
+ parson_free(object->names);
+ parson_free(object->values);
+ parson_free(object);
+}
+
+/* JSON Array */
+static JSON_Array * json_array_init(JSON_Value *wrapping_value) {
+ JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array));
+ if (new_array == NULL) {
+ return NULL;
+ }
+ new_array->wrapping_value = wrapping_value;
+ new_array->items = (JSON_Value**)NULL;
+ new_array->capacity = 0;
+ new_array->count = 0;
+ return new_array;
+}
+
+static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) {
+ if (array->count >= array->capacity) {
+ size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY);
+ if (json_array_resize(array, new_capacity) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ value->parent = json_array_get_wrapping_value(array);
+ array->items[array->count] = value;
+ array->count++;
+ return JSONSuccess;
+}
+
+static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) {
+ JSON_Value **new_items = NULL;
+ if (new_capacity == 0) {
+ return JSONFailure;
+ }
+ new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*));
+ if (new_items == NULL) {
+ return JSONFailure;
+ }
+ if (array->items != NULL && array->count > 0) {
+ memcpy(new_items, array->items, array->count * sizeof(JSON_Value*));
+ }
+ parson_free(array->items);
+ array->items = new_items;
+ array->capacity = new_capacity;
+ return JSONSuccess;
+}
+
+static void json_array_free(JSON_Array *array) {
+ size_t i;
+ for (i = 0; i < array->count; i++) {
+ json_value_free(array->items[i]);
+ }
+ parson_free(array->items);
+ parson_free(array);
+}
+
+/* JSON Value */
+static JSON_Value * json_value_init_string_no_copy(char *string) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONString;
+ new_value->value.string = string;
+ return new_value;
+}
+
+/* Parser */
+static JSON_Status skip_quotes(const char **string) {
+ if (**string != '\"') {
+ return JSONFailure;
+ }
+ SKIP_CHAR(string);
+ while (**string != '\"') {
+ if (**string == '\0') {
+ return JSONFailure;
+ } else if (**string == '\\') {
+ SKIP_CHAR(string);
+ if (**string == '\0') {
+ return JSONFailure;
+ }
+ }
+ SKIP_CHAR(string);
+ }
+ SKIP_CHAR(string);
+ return JSONSuccess;
+}
+
+static int parse_utf16(const char **unprocessed, char **processed) {
+ unsigned int cp, lead, trail;
+ int parse_succeeded = 0;
+ char *processed_ptr = *processed;
+ const char *unprocessed_ptr = *unprocessed;
+ unprocessed_ptr++; /* skips u */
+ parse_succeeded = parse_utf16_hex(unprocessed_ptr, &cp);
+ if (!parse_succeeded) {
+ return JSONFailure;
+ }
+ if (cp < 0x80) {
+ processed_ptr[0] = (char)cp; /* 0xxxxxxx */
+ } else if (cp < 0x800) {
+ processed_ptr[0] = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */
+ processed_ptr[1] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */
+ processed_ptr += 1;
+ } else if (cp < 0xD800 || cp > 0xDFFF) {
+ processed_ptr[0] = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */
+ processed_ptr[1] = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */
+ processed_ptr[2] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */
+ processed_ptr += 2;
+ } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */
+ lead = cp;
+ unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */
+ if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u') {
+ return JSONFailure;
+ }
+ parse_succeeded = parse_utf16_hex(unprocessed_ptr, &trail);
+ if (!parse_succeeded || trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */
+ return JSONFailure;
+ }
+ cp = ((((lead - 0xD800) & 0x3FF) << 10) | ((trail - 0xDC00) & 0x3FF)) + 0x010000;
+ processed_ptr[0] = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */
+ processed_ptr[1] = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */
+ processed_ptr[2] = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */
+ processed_ptr[3] = (((cp) & 0x3F) | 0x80); /* 10xxxxxx */
+ processed_ptr += 3;
+ } else { /* trail surrogate before lead surrogate */
+ return JSONFailure;
+ }
+ unprocessed_ptr += 3;
+ *processed = processed_ptr;
+ *unprocessed = unprocessed_ptr;
+ return JSONSuccess;
+}
+
+
+/* Copies and processes passed string up to supplied length.
+Example: "\u006Corem ipsum" -> lorem ipsum */
+static char* process_string(const char *input, size_t len) {
+ const char *input_ptr = input;
+ size_t initial_size = (len + 1) * sizeof(char);
+ size_t final_size = 0;
+ char *output = NULL, *output_ptr = NULL, *resized_output = NULL;
+ output = (char*)parson_malloc(initial_size);
+ if (output == NULL) {
+ goto error;
+ }
+ output_ptr = output;
+ while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) {
+ if (*input_ptr == '\\') {
+ input_ptr++;
+ switch (*input_ptr) {
+ case '\"': *output_ptr = '\"'; break;
+ case '\\': *output_ptr = '\\'; break;
+ case '/': *output_ptr = '/'; break;
+ case 'b': *output_ptr = '\b'; break;
+ case 'f': *output_ptr = '\f'; break;
+ case 'n': *output_ptr = '\n'; break;
+ case 'r': *output_ptr = '\r'; break;
+ case 't': *output_ptr = '\t'; break;
+ case 'u':
+ if (parse_utf16(&input_ptr, &output_ptr) == JSONFailure) {
+ goto error;
+ }
+ break;
+ default:
+ goto error;
+ }
+ } else if ((unsigned char)*input_ptr < 0x20) {
+ goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */
+ } else {
+ *output_ptr = *input_ptr;
+ }
+ output_ptr++;
+ input_ptr++;
+ }
+ *output_ptr = '\0';
+ /* resize to new length */
+ final_size = (size_t)(output_ptr-output) + 1;
+ /* todo: don't resize if final_size == initial_size */
+ resized_output = (char*)parson_malloc(final_size);
+ if (resized_output == NULL) {
+ goto error;
+ }
+ memcpy(resized_output, output, final_size);
+ parson_free(output);
+ return resized_output;
+error:
+ parson_free(output);
+ return NULL;
+}
+
+/* Return processed contents of a string between quotes and
+ skips passed argument to a matching quote. */
+static char * get_quoted_string(const char **string) {
+ const char *string_start = *string;
+ size_t string_len = 0;
+ JSON_Status status = skip_quotes(string);
+ if (status != JSONSuccess) {
+ return NULL;
+ }
+ string_len = *string - string_start - 2; /* length without quotes */
+ return process_string(string_start + 1, string_len);
+}
+
+static JSON_Value * parse_value(const char **string, size_t nesting) {
+ if (nesting > MAX_NESTING) {
+ return NULL;
+ }
+ SKIP_WHITESPACES(string);
+ switch (**string) {
+ case '{':
+ return parse_object_value(string, nesting + 1);
+ case '[':
+ return parse_array_value(string, nesting + 1);
+ case '\"':
+ return parse_string_value(string);
+ case 'f': case 't':
+ return parse_boolean_value(string);
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return parse_number_value(string);
+ case 'n':
+ return parse_null_value(string);
+ default:
+ return NULL;
+ }
+}
+
+static JSON_Value * parse_object_value(const char **string, size_t nesting) {
+ JSON_Value *output_value = NULL, *new_value = NULL;
+ JSON_Object *output_object = NULL;
+ char *new_key = NULL;
+ output_value = json_value_init_object();
+ if (output_value == NULL) {
+ return NULL;
+ }
+ if (**string != '{') {
+ json_value_free(output_value);
+ return NULL;
+ }
+ output_object = json_value_get_object(output_value);
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ if (**string == '}') { /* empty object */
+ SKIP_CHAR(string);
+ return output_value;
+ }
+ while (**string != '\0') {
+ new_key = get_quoted_string(string);
+ if (new_key == NULL) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ':') {
+ parson_free(new_key);
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ new_value = parse_value(string, nesting);
+ if (new_value == NULL) {
+ parson_free(new_key);
+ json_value_free(output_value);
+ return NULL;
+ }
+ if (json_object_add(output_object, new_key, new_value) == JSONFailure) {
+ parson_free(new_key);
+ json_value_free(new_value);
+ json_value_free(output_value);
+ return NULL;
+ }
+ parson_free(new_key);
+ SKIP_WHITESPACES(string);
+ if (**string != ',') {
+ break;
+ }
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != '}' || /* Trim object after parsing is over */
+ json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ return output_value;
+}
+
+static JSON_Value * parse_array_value(const char **string, size_t nesting) {
+ JSON_Value *output_value = NULL, *new_array_value = NULL;
+ JSON_Array *output_array = NULL;
+ output_value = json_value_init_array();
+ if (output_value == NULL) {
+ return NULL;
+ }
+ if (**string != '[') {
+ json_value_free(output_value);
+ return NULL;
+ }
+ output_array = json_value_get_array(output_value);
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ if (**string == ']') { /* empty array */
+ SKIP_CHAR(string);
+ return output_value;
+ }
+ while (**string != '\0') {
+ new_array_value = parse_value(string, nesting);
+ if (new_array_value == NULL) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ if (json_array_add(output_array, new_array_value) == JSONFailure) {
+ json_value_free(new_array_value);
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ',') {
+ break;
+ }
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ']' || /* Trim array after parsing is over */
+ json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ return output_value;
+}
+
+static JSON_Value * parse_string_value(const char **string) {
+ JSON_Value *value = NULL;
+ char *new_string = get_quoted_string(string);
+ if (new_string == NULL) {
+ return NULL;
+ }
+ value = json_value_init_string_no_copy(new_string);
+ if (value == NULL) {
+ parson_free(new_string);
+ return NULL;
+ }
+ return value;
+}
+
+static JSON_Value * parse_boolean_value(const char **string) {
+ size_t true_token_size = SIZEOF_TOKEN("true");
+ size_t false_token_size = SIZEOF_TOKEN("false");
+ if (strncmp("true", *string, true_token_size) == 0) {
+ *string += true_token_size;
+ return json_value_init_boolean(1);
+ } else if (strncmp("false", *string, false_token_size) == 0) {
+ *string += false_token_size;
+ return json_value_init_boolean(0);
+ }
+ return NULL;
+}
+
+static JSON_Value * parse_number_value(const char **string) {
+ char *end;
+ double number = 0;
+ errno = 0;
+ number = strtod(*string, &end);
+ if (errno || !is_decimal(*string, end - *string)) {
+ return NULL;
+ }
+ *string = end;
+ return json_value_init_number(number);
+}
+
+static JSON_Value * parse_null_value(const char **string) {
+ size_t token_size = SIZEOF_TOKEN("null");
+ if (strncmp("null", *string, token_size) == 0) {
+ *string += token_size;
+ return json_value_init_null();
+ }
+ return NULL;
+}
+
+/* Serialization */
+#define APPEND_STRING(str) do { written = append_string(buf, (str));\
+ if (written < 0) { return -1; }\
+ if (buf != NULL) { buf += written; }\
+ written_total += written; } while(0)
+
+#define APPEND_INDENT(level) do { written = append_indent(buf, (level));\
+ if (written < 0) { return -1; }\
+ if (buf != NULL) { buf += written; }\
+ written_total += written; } while(0)
+
+static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf)
+{
+ const char *key = NULL, *string = NULL;
+ JSON_Value *temp_value = NULL;
+ JSON_Array *array = NULL;
+ JSON_Object *object = NULL;
+ size_t i = 0, count = 0;
+ double num = 0.0;
+ int written = -1, written_total = 0;
+
+ switch (json_value_get_type(value)) {
+ case JSONArray:
+ array = json_value_get_array(value);
+ count = json_array_get_count(array);
+ APPEND_STRING("[");
+ if (count > 0 && is_pretty) {
+ APPEND_STRING("\n");
+ }
+ for (i = 0; i < count; i++) {
+ if (is_pretty) {
+ APPEND_INDENT(level+1);
+ }
+ temp_value = json_array_get_value(array, i);
+ written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ if (i < (count - 1)) {
+ APPEND_STRING(",");
+ }
+ if (is_pretty) {
+ APPEND_STRING("\n");
+ }
+ }
+ if (count > 0 && is_pretty) {
+ APPEND_INDENT(level);
+ }
+ APPEND_STRING("]");
+ return written_total;
+ case JSONObject:
+ object = json_value_get_object(value);
+ count = json_object_get_count(object);
+ APPEND_STRING("{");
+ if (count > 0 && is_pretty) {
+ APPEND_STRING("\n");
+ }
+ for (i = 0; i < count; i++) {
+ key = json_object_get_name(object, i);
+ if (key == NULL) {
+ return -1;
+ }
+ if (is_pretty) {
+ APPEND_INDENT(level+1);
+ }
+ written = json_serialize_string(key, buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ APPEND_STRING(":");
+ if (is_pretty) {
+ APPEND_STRING(" ");
+ }
+ temp_value = json_object_get_value(object, key);
+ written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ if (i < (count - 1)) {
+ APPEND_STRING(",");
+ }
+ if (is_pretty) {
+ APPEND_STRING("\n");
+ }
+ }
+ if (count > 0 && is_pretty) {
+ APPEND_INDENT(level);
+ }
+ APPEND_STRING("}");
+ return written_total;
+ case JSONString:
+ string = json_value_get_string(value);
+ if (string == NULL) {
+ return -1;
+ }
+ written = json_serialize_string(string, buf);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ return written_total;
+ case JSONBoolean:
+ if (json_value_get_boolean(value)) {
+ APPEND_STRING("true");
+ } else {
+ APPEND_STRING("false");
+ }
+ return written_total;
+ case JSONNumber:
+ num = json_value_get_number(value);
+ if (buf != NULL) {
+ num_buf = buf;
+ }
+ written = sprintf(num_buf, FLOAT_FORMAT, num);
+ if (written < 0) {
+ return -1;
+ }
+ if (buf != NULL) {
+ buf += written;
+ }
+ written_total += written;
+ return written_total;
+ case JSONNull:
+ APPEND_STRING("null");
+ return written_total;
+ case JSONError:
+ return -1;
+ default:
+ return -1;
+ }
+}
+
+static int json_serialize_string(const char *string, char *buf) {
+ size_t i = 0, len = strlen(string);
+ char c = '\0';
+ int written = -1, written_total = 0;
+ APPEND_STRING("\"");
+ for (i = 0; i < len; i++) {
+ c = string[i];
+ switch (c) {
+ case '\"': APPEND_STRING("\\\""); break;
+ case '\\': APPEND_STRING("\\\\"); break;
+ case '\b': APPEND_STRING("\\b"); break;
+ case '\f': APPEND_STRING("\\f"); break;
+ case '\n': APPEND_STRING("\\n"); break;
+ case '\r': APPEND_STRING("\\r"); break;
+ case '\t': APPEND_STRING("\\t"); break;
+ case '\x00': APPEND_STRING("\\u0000"); break;
+ case '\x01': APPEND_STRING("\\u0001"); break;
+ case '\x02': APPEND_STRING("\\u0002"); break;
+ case '\x03': APPEND_STRING("\\u0003"); break;
+ case '\x04': APPEND_STRING("\\u0004"); break;
+ case '\x05': APPEND_STRING("\\u0005"); break;
+ case '\x06': APPEND_STRING("\\u0006"); break;
+ case '\x07': APPEND_STRING("\\u0007"); break;
+ /* '\x08' duplicate: '\b' */
+ /* '\x09' duplicate: '\t' */
+ /* '\x0a' duplicate: '\n' */
+ case '\x0b': APPEND_STRING("\\u000b"); break;
+ /* '\x0c' duplicate: '\f' */
+ /* '\x0d' duplicate: '\r' */
+ case '\x0e': APPEND_STRING("\\u000e"); break;
+ case '\x0f': APPEND_STRING("\\u000f"); break;
+ case '\x10': APPEND_STRING("\\u0010"); break;
+ case '\x11': APPEND_STRING("\\u0011"); break;
+ case '\x12': APPEND_STRING("\\u0012"); break;
+ case '\x13': APPEND_STRING("\\u0013"); break;
+ case '\x14': APPEND_STRING("\\u0014"); break;
+ case '\x15': APPEND_STRING("\\u0015"); break;
+ case '\x16': APPEND_STRING("\\u0016"); break;
+ case '\x17': APPEND_STRING("\\u0017"); break;
+ case '\x18': APPEND_STRING("\\u0018"); break;
+ case '\x19': APPEND_STRING("\\u0019"); break;
+ case '\x1a': APPEND_STRING("\\u001a"); break;
+ case '\x1b': APPEND_STRING("\\u001b"); break;
+ case '\x1c': APPEND_STRING("\\u001c"); break;
+ case '\x1d': APPEND_STRING("\\u001d"); break;
+ case '\x1e': APPEND_STRING("\\u001e"); break;
+ case '\x1f': APPEND_STRING("\\u001f"); break;
+ case '/':
+ if (parson_escape_slashes) {
+ APPEND_STRING("\\/"); /* to make json embeddable in xml\/html */
+ } else {
+ APPEND_STRING("/");
+ }
+ break;
+ default:
+ if (buf != NULL) {
+ buf[0] = c;
+ buf += 1;
+ }
+ written_total += 1;
+ break;
+ }
+ }
+ APPEND_STRING("\"");
+ return written_total;
+}
+
+static int append_indent(char *buf, int level) {
+ int i;
+ int written = -1, written_total = 0;
+ for (i = 0; i < level; i++) {
+ APPEND_STRING(" ");
+ }
+ return written_total;
+}
+
+static int append_string(char *buf, const char *string) {
+ if (buf == NULL) {
+ return (int)strlen(string);
+ }
+ return sprintf(buf, "%s", string);
+}
+
+#undef APPEND_STRING
+#undef APPEND_INDENT
+
+/* Parser API */
+JSON_Value * json_parse_file(const char *filename) {
+ char *file_contents = read_file(filename);
+ JSON_Value *output_value = NULL;
+ if (file_contents == NULL) {
+ return NULL;
+ }
+ output_value = json_parse_string(file_contents);
+ parson_free(file_contents);
+ return output_value;
+}
+
+JSON_Value * json_parse_file_with_comments(const char *filename) {
+ char *file_contents = read_file(filename);
+ JSON_Value *output_value = NULL;
+ if (file_contents == NULL) {
+ return NULL;
+ }
+ output_value = json_parse_string_with_comments(file_contents);
+ parson_free(file_contents);
+ return output_value;
+}
+
+JSON_Value * json_parse_string(const char *string) {
+ if (string == NULL) {
+ return NULL;
+ }
+ if (string[0] == '\xEF' && string[1] == '\xBB' && string[2] == '\xBF') {
+ string = string + 3; /* Support for UTF-8 BOM */
+ }
+ return parse_value((const char**)&string, 0);
+}
+
+JSON_Value * json_parse_string_with_comments(const char *string) {
+ JSON_Value *result = NULL;
+ char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL;
+ string_mutable_copy = parson_strdup(string);
+ if (string_mutable_copy == NULL) {
+ return NULL;
+ }
+ remove_comments(string_mutable_copy, "/*", "*/");
+ remove_comments(string_mutable_copy, "//", "\n");
+ string_mutable_copy_ptr = string_mutable_copy;
+ result = parse_value((const char**)&string_mutable_copy_ptr, 0);
+ parson_free(string_mutable_copy);
+ return result;
+}
+
+/* JSON Object API */
+
+JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) {
+ if (object == NULL || name == NULL) {
+ return NULL;
+ }
+ return json_object_getn_value(object, name, strlen(name));
+}
+
+const char * json_object_get_string(const JSON_Object *object, const char *name) {
+ return json_value_get_string(json_object_get_value(object, name));
+}
+
+double json_object_get_number(const JSON_Object *object, const char *name) {
+ return json_value_get_number(json_object_get_value(object, name));
+}
+
+JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) {
+ return json_value_get_object(json_object_get_value(object, name));
+}
+
+JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) {
+ return json_value_get_array(json_object_get_value(object, name));
+}
+
+int json_object_get_boolean(const JSON_Object *object, const char *name) {
+ return json_value_get_boolean(json_object_get_value(object, name));
+}
+
+JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) {
+ const char *dot_position = strchr(name, '.');
+ if (!dot_position) {
+ return json_object_get_value(object, name);
+ }
+ object = json_value_get_object(json_object_getn_value(object, name, dot_position - name));
+ return json_object_dotget_value(object, dot_position + 1);
+}
+
+const char * json_object_dotget_string(const JSON_Object *object, const char *name) {
+ return json_value_get_string(json_object_dotget_value(object, name));
+}
+
+double json_object_dotget_number(const JSON_Object *object, const char *name) {
+ return json_value_get_number(json_object_dotget_value(object, name));
+}
+
+JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) {
+ return json_value_get_object(json_object_dotget_value(object, name));
+}
+
+JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) {
+ return json_value_get_array(json_object_dotget_value(object, name));
+}
+
+int json_object_dotget_boolean(const JSON_Object *object, const char *name) {
+ return json_value_get_boolean(json_object_dotget_value(object, name));
+}
+
+size_t json_object_get_count(const JSON_Object *object) {
+ return object ? object->count : 0;
+}
+
+const char * json_object_get_name(const JSON_Object *object, size_t index) {
+ if (object == NULL || index >= json_object_get_count(object)) {
+ return NULL;
+ }
+ return object->names[index];
+}
+
+JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index) {
+ if (object == NULL || index >= json_object_get_count(object)) {
+ return NULL;
+ }
+ return object->values[index];
+}
+
+JSON_Value *json_object_get_wrapping_value(const JSON_Object *object) {
+ return object->wrapping_value;
+}
+
+int json_object_has_value (const JSON_Object *object, const char *name) {
+ return json_object_get_value(object, name) != NULL;
+}
+
+int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) {
+ JSON_Value *val = json_object_get_value(object, name);
+ return val != NULL && json_value_get_type(val) == type;
+}
+
+int json_object_dothas_value (const JSON_Object *object, const char *name) {
+ return json_object_dotget_value(object, name) != NULL;
+}
+
+int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) {
+ JSON_Value *val = json_object_dotget_value(object, name);
+ return val != NULL && json_value_get_type(val) == type;
+}
+
+/* JSON Array API */
+JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) {
+ if (array == NULL || index >= json_array_get_count(array)) {
+ return NULL;
+ }
+ return array->items[index];
+}
+
+const char * json_array_get_string(const JSON_Array *array, size_t index) {
+ return json_value_get_string(json_array_get_value(array, index));
+}
+
+double json_array_get_number(const JSON_Array *array, size_t index) {
+ return json_value_get_number(json_array_get_value(array, index));
+}
+
+JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) {
+ return json_value_get_object(json_array_get_value(array, index));
+}
+
+JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) {
+ return json_value_get_array(json_array_get_value(array, index));
+}
+
+int json_array_get_boolean(const JSON_Array *array, size_t index) {
+ return json_value_get_boolean(json_array_get_value(array, index));
+}
+
+size_t json_array_get_count(const JSON_Array *array) {
+ return array ? array->count : 0;
+}
+
+JSON_Value * json_array_get_wrapping_value(const JSON_Array *array) {
+ return array->wrapping_value;
+}
+
+/* JSON Value API */
+JSON_Value_Type json_value_get_type(const JSON_Value *value) {
+ return value ? value->type : JSONError;
+}
+
+JSON_Object * json_value_get_object(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONObject ? value->value.object : NULL;
+}
+
+JSON_Array * json_value_get_array(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONArray ? value->value.array : NULL;
+}
+
+const char * json_value_get_string(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONString ? value->value.string : NULL;
+}
+
+double json_value_get_number(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONNumber ? value->value.number : 0;
+}
+
+int json_value_get_boolean(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1;
+}
+
+JSON_Value * json_value_get_parent (const JSON_Value *value) {
+ return value ? value->parent : NULL;
+}
+
+void json_value_free(JSON_Value *value) {
+ switch (json_value_get_type(value)) {
+ case JSONObject:
+ json_object_free(value->value.object);
+ break;
+ case JSONString:
+ parson_free(value->value.string);
+ break;
+ case JSONArray:
+ json_array_free(value->value.array);
+ break;
+ default:
+ break;
+ }
+ parson_free(value);
+}
+
+JSON_Value * json_value_init_object(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONObject;
+ new_value->value.object = json_object_init(new_value);
+ if (!new_value->value.object) {
+ parson_free(new_value);
+ return NULL;
+ }
+ return new_value;
+}
+
+JSON_Value * json_value_init_array(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONArray;
+ new_value->value.array = json_array_init(new_value);
+ if (!new_value->value.array) {
+ parson_free(new_value);
+ return NULL;
+ }
+ return new_value;
+}
+
+JSON_Value * json_value_init_string(const char *string) {
+ char *copy = NULL;
+ JSON_Value *value;
+ size_t string_len = 0;
+ if (string == NULL) {
+ return NULL;
+ }
+ string_len = strlen(string);
+ if (!is_valid_utf8(string, string_len)) {
+ return NULL;
+ }
+ copy = parson_strndup(string, string_len);
+ if (copy == NULL) {
+ return NULL;
+ }
+ value = json_value_init_string_no_copy(copy);
+ if (value == NULL) {
+ parson_free(copy);
+ }
+ return value;
+}
+
+JSON_Value * json_value_init_number(double number) {
+ JSON_Value *new_value = NULL;
+ if (IS_NUMBER_INVALID(number)) {
+ return NULL;
+ }
+ new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (new_value == NULL) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONNumber;
+ new_value->value.number = number;
+ return new_value;
+}
+
+JSON_Value * json_value_init_boolean(int boolean) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONBoolean;
+ new_value->value.boolean = boolean ? 1 : 0;
+ return new_value;
+}
+
+JSON_Value * json_value_init_null(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value) {
+ return NULL;
+ }
+ new_value->parent = NULL;
+ new_value->type = JSONNull;
+ return new_value;
+}
+
+JSON_Value * json_value_deep_copy(const JSON_Value *value) {
+ size_t i = 0;
+ JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL;
+ const char *temp_string = NULL, *temp_key = NULL;
+ char *temp_string_copy = NULL;
+ JSON_Array *temp_array = NULL, *temp_array_copy = NULL;
+ JSON_Object *temp_object = NULL, *temp_object_copy = NULL;
+
+ switch (json_value_get_type(value)) {
+ case JSONArray:
+ temp_array = json_value_get_array(value);
+ return_value = json_value_init_array();
+ if (return_value == NULL) {
+ return NULL;
+ }
+ temp_array_copy = json_value_get_array(return_value);
+ for (i = 0; i < json_array_get_count(temp_array); i++) {
+ temp_value = json_array_get_value(temp_array, i);
+ temp_value_copy = json_value_deep_copy(temp_value);
+ if (temp_value_copy == NULL) {
+ json_value_free(return_value);
+ return NULL;
+ }
+ if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) {
+ json_value_free(return_value);
+ json_value_free(temp_value_copy);
+ return NULL;
+ }
+ }
+ return return_value;
+ case JSONObject:
+ temp_object = json_value_get_object(value);
+ return_value = json_value_init_object();
+ if (return_value == NULL) {
+ return NULL;
+ }
+ temp_object_copy = json_value_get_object(return_value);
+ for (i = 0; i < json_object_get_count(temp_object); i++) {
+ temp_key = json_object_get_name(temp_object, i);
+ temp_value = json_object_get_value(temp_object, temp_key);
+ temp_value_copy = json_value_deep_copy(temp_value);
+ if (temp_value_copy == NULL) {
+ json_value_free(return_value);
+ return NULL;
+ }
+ if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) {
+ json_value_free(return_value);
+ json_value_free(temp_value_copy);
+ return NULL;
+ }
+ }
+ return return_value;
+ case JSONBoolean:
+ return json_value_init_boolean(json_value_get_boolean(value));
+ case JSONNumber:
+ return json_value_init_number(json_value_get_number(value));
+ case JSONString:
+ temp_string = json_value_get_string(value);
+ if (temp_string == NULL) {
+ return NULL;
+ }
+ temp_string_copy = parson_strdup(temp_string);
+ if (temp_string_copy == NULL) {
+ return NULL;
+ }
+ return_value = json_value_init_string_no_copy(temp_string_copy);
+ if (return_value == NULL) {
+ parson_free(temp_string_copy);
+ }
+ return return_value;
+ case JSONNull:
+ return json_value_init_null();
+ case JSONError:
+ return NULL;
+ default:
+ return NULL;
+ }
+}
+
+size_t json_serialization_size(const JSON_Value *value) {
+ char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
+ int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf);
+ return res < 0 ? 0 : (size_t)(res + 1);
+}
+
+JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
+ int written = -1;
+ size_t needed_size_in_bytes = json_serialization_size(value);
+ if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) {
+ return JSONFailure;
+ }
+ written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL);
+ if (written < 0) {
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) {
+ JSON_Status return_code = JSONSuccess;
+ FILE *fp = NULL;
+ char *serialized_string = json_serialize_to_string(value);
+ if (serialized_string == NULL) {
+ return JSONFailure;
+ }
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ json_free_serialized_string(serialized_string);
+ return JSONFailure;
+ }
+ if (fputs(serialized_string, fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ if (fclose(fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ json_free_serialized_string(serialized_string);
+ return return_code;
+}
+
+char * json_serialize_to_string(const JSON_Value *value) {
+ JSON_Status serialization_result = JSONFailure;
+ size_t buf_size_bytes = json_serialization_size(value);
+ char *buf = NULL;
+ if (buf_size_bytes == 0) {
+ return NULL;
+ }
+ buf = (char*)parson_malloc(buf_size_bytes);
+ if (buf == NULL) {
+ return NULL;
+ }
+ serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes);
+ if (serialization_result == JSONFailure) {
+ json_free_serialized_string(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+size_t json_serialization_size_pretty(const JSON_Value *value) {
+ char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
+ int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf);
+ return res < 0 ? 0 : (size_t)(res + 1);
+}
+
+JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
+ int written = -1;
+ size_t needed_size_in_bytes = json_serialization_size_pretty(value);
+ if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) {
+ return JSONFailure;
+ }
+ written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL);
+ if (written < 0) {
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) {
+ JSON_Status return_code = JSONSuccess;
+ FILE *fp = NULL;
+ char *serialized_string = json_serialize_to_string_pretty(value);
+ if (serialized_string == NULL) {
+ return JSONFailure;
+ }
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ json_free_serialized_string(serialized_string);
+ return JSONFailure;
+ }
+ if (fputs(serialized_string, fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ if (fclose(fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ json_free_serialized_string(serialized_string);
+ return return_code;
+}
+
+char * json_serialize_to_string_pretty(const JSON_Value *value) {
+ JSON_Status serialization_result = JSONFailure;
+ size_t buf_size_bytes = json_serialization_size_pretty(value);
+ char *buf = NULL;
+ if (buf_size_bytes == 0) {
+ return NULL;
+ }
+ buf = (char*)parson_malloc(buf_size_bytes);
+ if (buf == NULL) {
+ return NULL;
+ }
+ serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes);
+ if (serialization_result == JSONFailure) {
+ json_free_serialized_string(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+void json_free_serialized_string(char *string) {
+ parson_free(string);
+}
+
+JSON_Status json_array_remove(JSON_Array *array, size_t ix) {
+ size_t to_move_bytes = 0;
+ if (array == NULL || ix >= json_array_get_count(array)) {
+ return JSONFailure;
+ }
+ json_value_free(json_array_get_value(array, ix));
+ to_move_bytes = (json_array_get_count(array) - 1 - ix) * sizeof(JSON_Value*);
+ memmove(array->items + ix, array->items + ix + 1, to_move_bytes);
+ array->count -= 1;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) {
+ if (array == NULL || value == NULL || value->parent != NULL || ix >= json_array_get_count(array)) {
+ return JSONFailure;
+ }
+ json_value_free(json_array_get_value(array, ix));
+ value->parent = json_array_get_wrapping_value(array);
+ array->items[ix] = value;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_null(JSON_Array *array, size_t i) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_clear(JSON_Array *array) {
+ size_t i = 0;
+ if (array == NULL) {
+ return JSONFailure;
+ }
+ for (i = 0; i < json_array_get_count(array); i++) {
+ json_value_free(json_array_get_value(array, i));
+ }
+ array->count = 0;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) {
+ if (array == NULL || value == NULL || value->parent != NULL) {
+ return JSONFailure;
+ }
+ return json_array_add(array, value);
+}
+
+JSON_Status json_array_append_string(JSON_Array *array, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_number(JSON_Array *array, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_null(JSON_Array *array) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) {
+ size_t i = 0;
+ JSON_Value *old_value;
+ if (object == NULL || name == NULL || value == NULL || value->parent != NULL) {
+ return JSONFailure;
+ }
+ old_value = json_object_get_value(object, name);
+ if (old_value != NULL) { /* free and overwrite old value */
+ json_value_free(old_value);
+ for (i = 0; i < json_object_get_count(object); i++) {
+ if (strcmp(object->names[i], name) == 0) {
+ value->parent = json_object_get_wrapping_value(object);
+ object->values[i] = value;
+ return JSONSuccess;
+ }
+ }
+ }
+ /* add new key value pair */
+ return json_object_add(object, name, value);
+}
+
+JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_set_null(JSON_Object *object, const char *name) {
+ JSON_Value *value = json_value_init_null();
+ JSON_Status status = json_object_set_value(object, name, value);
+ if (status == JSONFailure) {
+ json_value_free(value);
+ }
+ return status;
+}
+
+JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) {
+ const char *dot_pos = NULL;
+ JSON_Value *temp_value = NULL, *new_value = NULL;
+ JSON_Object *temp_object = NULL, *new_object = NULL;
+ JSON_Status status = JSONFailure;
+ size_t name_len = 0;
+ if (object == NULL || name == NULL || value == NULL) {
+ return JSONFailure;
+ }
+ dot_pos = strchr(name, '.');
+ if (dot_pos == NULL) {
+ return json_object_set_value(object, name, value);
+ }
+ name_len = dot_pos - name;
+ temp_value = json_object_getn_value(object, name, name_len);
+ if (temp_value) {
+ /* Don't overwrite existing non-object (unlike json_object_set_value, but it shouldn't be changed at this point) */
+ if (json_value_get_type(temp_value) != JSONObject) {
+ return JSONFailure;
+ }
+ temp_object = json_value_get_object(temp_value);
+ return json_object_dotset_value(temp_object, dot_pos + 1, value);
+ }
+ new_value = json_value_init_object();
+ if (new_value == NULL) {
+ return JSONFailure;
+ }
+ new_object = json_value_get_object(new_value);
+ status = json_object_dotset_value(new_object, dot_pos + 1, value);
+ if (status != JSONSuccess) {
+ json_value_free(new_value);
+ return JSONFailure;
+ }
+ status = json_object_addn(object, name, name_len, new_value);
+ if (status != JSONSuccess) {
+ json_object_dotremove_internal(new_object, dot_pos + 1, 0);
+ json_value_free(new_value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL) {
+ return JSONFailure;
+ }
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_remove(JSON_Object *object, const char *name) {
+ return json_object_remove_internal(object, name, 1);
+}
+
+JSON_Status json_object_dotremove(JSON_Object *object, const char *name) {
+ return json_object_dotremove_internal(object, name, 1);
+}
+
+JSON_Status json_object_clear(JSON_Object *object) {
+ size_t i = 0;
+ if (object == NULL) {
+ return JSONFailure;
+ }
+ for (i = 0; i < json_object_get_count(object); i++) {
+ parson_free(object->names[i]);
+ json_value_free(object->values[i]);
+ }
+ object->count = 0;
+ return JSONSuccess;
+}
+
+JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) {
+ JSON_Value *temp_schema_value = NULL, *temp_value = NULL;
+ JSON_Array *schema_array = NULL, *value_array = NULL;
+ JSON_Object *schema_object = NULL, *value_object = NULL;
+ JSON_Value_Type schema_type = JSONError, value_type = JSONError;
+ const char *key = NULL;
+ size_t i = 0, count = 0;
+ if (schema == NULL || value == NULL) {
+ return JSONFailure;
+ }
+ schema_type = json_value_get_type(schema);
+ value_type = json_value_get_type(value);
+ if (schema_type != value_type && schema_type != JSONNull) { /* null represents all values */
+ return JSONFailure;
+ }
+ switch (schema_type) {
+ case JSONArray:
+ schema_array = json_value_get_array(schema);
+ value_array = json_value_get_array(value);
+ count = json_array_get_count(schema_array);
+ if (count == 0) {
+ return JSONSuccess; /* Empty array allows all types */
+ }
+ /* Get first value from array, rest is ignored */
+ temp_schema_value = json_array_get_value(schema_array, 0);
+ for (i = 0; i < json_array_get_count(value_array); i++) {
+ temp_value = json_array_get_value(value_array, i);
+ if (json_validate(temp_schema_value, temp_value) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ return JSONSuccess;
+ case JSONObject:
+ schema_object = json_value_get_object(schema);
+ value_object = json_value_get_object(value);
+ count = json_object_get_count(schema_object);
+ if (count == 0) {
+ return JSONSuccess; /* Empty object allows all objects */
+ } else if (json_object_get_count(value_object) < count) {
+ return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */
+ }
+ for (i = 0; i < count; i++) {
+ key = json_object_get_name(schema_object, i);
+ temp_schema_value = json_object_get_value(schema_object, key);
+ temp_value = json_object_get_value(value_object, key);
+ if (temp_value == NULL) {
+ return JSONFailure;
+ }
+ if (json_validate(temp_schema_value, temp_value) == JSONFailure) {
+ return JSONFailure;
+ }
+ }
+ return JSONSuccess;
+ case JSONString: case JSONNumber: case JSONBoolean: case JSONNull:
+ return JSONSuccess; /* equality already tested before switch */
+ case JSONError: default:
+ return JSONFailure;
+ }
+}
+
+int json_value_equals(const JSON_Value *a, const JSON_Value *b) {
+ JSON_Object *a_object = NULL, *b_object = NULL;
+ JSON_Array *a_array = NULL, *b_array = NULL;
+ const char *a_string = NULL, *b_string = NULL;
+ const char *key = NULL;
+ size_t a_count = 0, b_count = 0, i = 0;
+ JSON_Value_Type a_type, b_type;
+ a_type = json_value_get_type(a);
+ b_type = json_value_get_type(b);
+ if (a_type != b_type) {
+ return 0;
+ }
+ switch (a_type) {
+ case JSONArray:
+ a_array = json_value_get_array(a);
+ b_array = json_value_get_array(b);
+ a_count = json_array_get_count(a_array);
+ b_count = json_array_get_count(b_array);
+ if (a_count != b_count) {
+ return 0;
+ }
+ for (i = 0; i < a_count; i++) {
+ if (!json_value_equals(json_array_get_value(a_array, i),
+ json_array_get_value(b_array, i))) {
+ return 0;
+ }
+ }
+ return 1;
+ case JSONObject:
+ a_object = json_value_get_object(a);
+ b_object = json_value_get_object(b);
+ a_count = json_object_get_count(a_object);
+ b_count = json_object_get_count(b_object);
+ if (a_count != b_count) {
+ return 0;
+ }
+ for (i = 0; i < a_count; i++) {
+ key = json_object_get_name(a_object, i);
+ if (!json_value_equals(json_object_get_value(a_object, key),
+ json_object_get_value(b_object, key))) {
+ return 0;
+ }
+ }
+ return 1;
+ case JSONString:
+ a_string = json_value_get_string(a);
+ b_string = json_value_get_string(b);
+ if (a_string == NULL || b_string == NULL) {
+ return 0; /* shouldn't happen */
+ }
+ return strcmp(a_string, b_string) == 0;
+ case JSONBoolean:
+ return json_value_get_boolean(a) == json_value_get_boolean(b);
+ case JSONNumber:
+ return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */
+ case JSONError:
+ return 1;
+ case JSONNull:
+ return 1;
+ default:
+ return 1;
+ }
+}
+
+JSON_Value_Type json_type(const JSON_Value *value) {
+ return json_value_get_type(value);
+}
+
+JSON_Object * json_object (const JSON_Value *value) {
+ return json_value_get_object(value);
+}
+
+JSON_Array * json_array (const JSON_Value *value) {
+ return json_value_get_array(value);
+}
+
+const char * json_string (const JSON_Value *value) {
+ return json_value_get_string(value);
+}
+
+double json_number (const JSON_Value *value) {
+ return json_value_get_number(value);
+}
+
+int json_boolean(const JSON_Value *value) {
+ return json_value_get_boolean(value);
+}
+
+void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) {
+ parson_malloc = malloc_fun;
+ parson_free = free_fun;
+}
+
+void json_set_escape_slashes(int escape_slashes) {
+ parson_escape_slashes = escape_slashes;
+}
diff --git a/src/cooker/parson.h b/src/cooker/parson.h
new file mode 100644
index 0000000..186fcb0
--- /dev/null
+++ b/src/cooker/parson.h
@@ -0,0 +1,240 @@
+/*
+ SPDX-License-Identifier: MIT
+
+ Parson ( http://kgabis.github.com/parson/ )
+ Copyright (c) 2012 - 2019 Krzysztof Gabis
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#ifndef parson_parson_h
+#define parson_parson_h
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stddef.h> /* size_t */
+
+/* Types and enums */
+typedef struct json_object_t JSON_Object;
+typedef struct json_array_t JSON_Array;
+typedef struct json_value_t JSON_Value;
+
+enum json_value_type {
+ JSONError = -1,
+ JSONNull = 1,
+ JSONString = 2,
+ JSONNumber = 3,
+ JSONObject = 4,
+ JSONArray = 5,
+ JSONBoolean = 6
+};
+typedef int JSON_Value_Type;
+
+enum json_result_t {
+ JSONSuccess = 0,
+ JSONFailure = -1
+};
+typedef int JSON_Status;
+
+typedef void * (*JSON_Malloc_Function)(size_t);
+typedef void (*JSON_Free_Function)(void *);
+
+/* Call only once, before calling any other function from parson API. If not called, malloc and free
+ from stdlib will be used for all allocations */
+void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun);
+
+/* Sets if slashes should be escaped or not when serializing JSON. By default slashes are escaped.
+ This function sets a global setting and is not thread safe. */
+void json_set_escape_slashes(int escape_slashes);
+
+/* Parses first JSON value in a file, returns NULL in case of error */
+JSON_Value * json_parse_file(const char *filename);
+
+/* Parses first JSON value in a file and ignores comments (/ * * / and //),
+ returns NULL in case of error */
+JSON_Value * json_parse_file_with_comments(const char *filename);
+
+/* Parses first JSON value in a string, returns NULL in case of error */
+JSON_Value * json_parse_string(const char *string);
+
+/* Parses first JSON value in a string and ignores comments (/ * * / and //),
+ returns NULL in case of error */
+JSON_Value * json_parse_string_with_comments(const char *string);
+
+/* Serialization */
+size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */
+JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
+JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename);
+char * json_serialize_to_string(const JSON_Value *value);
+
+/* Pretty serialization */
+size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */
+JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
+JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename);
+char * json_serialize_to_string_pretty(const JSON_Value *value);
+
+void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */
+
+/* Comparing */
+int json_value_equals(const JSON_Value *a, const JSON_Value *b);
+
+/* Validation
+ This is *NOT* JSON Schema. It validates json by checking if object have identically
+ named fields with matching types.
+ For example schema {"name":"", "age":0} will validate
+ {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"},
+ but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}.
+ In case of arrays, only first value in schema is checked against all values in tested array.
+ Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays,
+ null validates values of every type.
+ */
+JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value);
+
+/*
+ * JSON Object
+ */
+JSON_Value * json_object_get_value (const JSON_Object *object, const char *name);
+const char * json_object_get_string (const JSON_Object *object, const char *name);
+JSON_Object * json_object_get_object (const JSON_Object *object, const char *name);
+JSON_Array * json_object_get_array (const JSON_Object *object, const char *name);
+double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */
+int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */
+
+/* dotget functions enable addressing values with dot notation in nested objects,
+ just like in structs or c++/java/c# objects (e.g. objectA.objectB.value).
+ Because valid names in JSON can contain dots, some values may be inaccessible
+ this way. */
+JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name);
+const char * json_object_dotget_string (const JSON_Object *object, const char *name);
+JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name);
+JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name);
+double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */
+int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */
+
+/* Functions to get available names */
+size_t json_object_get_count (const JSON_Object *object);
+const char * json_object_get_name (const JSON_Object *object, size_t index);
+JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index);
+JSON_Value * json_object_get_wrapping_value(const JSON_Object *object);
+
+/* Functions to check if object has a value with a specific name. Returned value is 1 if object has
+ * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */
+int json_object_has_value (const JSON_Object *object, const char *name);
+int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type);
+
+int json_object_dothas_value (const JSON_Object *object, const char *name);
+int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type);
+
+/* Creates new name-value pair or frees and replaces old value with a new one.
+ * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value);
+JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string);
+JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number);
+JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean);
+JSON_Status json_object_set_null(JSON_Object *object, const char *name);
+
+/* Works like dotget functions, but creates whole hierarchy if necessary.
+ * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value);
+JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string);
+JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number);
+JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean);
+JSON_Status json_object_dotset_null(JSON_Object *object, const char *name);
+
+/* Frees and removes name-value pair */
+JSON_Status json_object_remove(JSON_Object *object, const char *name);
+
+/* Works like dotget function, but removes name-value pair only on exact match. */
+JSON_Status json_object_dotremove(JSON_Object *object, const char *key);
+
+/* Removes all name-value pairs in object */
+JSON_Status json_object_clear(JSON_Object *object);
+
+/*
+ *JSON Array
+ */
+JSON_Value * json_array_get_value (const JSON_Array *array, size_t index);
+const char * json_array_get_string (const JSON_Array *array, size_t index);
+JSON_Object * json_array_get_object (const JSON_Array *array, size_t index);
+JSON_Array * json_array_get_array (const JSON_Array *array, size_t index);
+double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */
+int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */
+size_t json_array_get_count (const JSON_Array *array);
+JSON_Value * json_array_get_wrapping_value(const JSON_Array *array);
+
+/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist.
+ * Order of values in array may change during execution. */
+JSON_Status json_array_remove(JSON_Array *array, size_t i);
+
+/* Frees and removes from array value at given index and replaces it with given one.
+ * Does nothing and returns JSONFailure if index doesn't exist.
+ * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value);
+JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string);
+JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number);
+JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean);
+JSON_Status json_array_replace_null(JSON_Array *array, size_t i);
+
+/* Frees and removes all values from array */
+JSON_Status json_array_clear(JSON_Array *array);
+
+/* Appends new value at the end of array.
+ * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value);
+JSON_Status json_array_append_string(JSON_Array *array, const char *string);
+JSON_Status json_array_append_number(JSON_Array *array, double number);
+JSON_Status json_array_append_boolean(JSON_Array *array, int boolean);
+JSON_Status json_array_append_null(JSON_Array *array);
+
+/*
+ *JSON Value
+ */
+JSON_Value * json_value_init_object (void);
+JSON_Value * json_value_init_array (void);
+JSON_Value * json_value_init_string (const char *string); /* copies passed string */
+JSON_Value * json_value_init_number (double number);
+JSON_Value * json_value_init_boolean(int boolean);
+JSON_Value * json_value_init_null (void);
+JSON_Value * json_value_deep_copy (const JSON_Value *value);
+void json_value_free (JSON_Value *value);
+
+JSON_Value_Type json_value_get_type (const JSON_Value *value);
+JSON_Object * json_value_get_object (const JSON_Value *value);
+JSON_Array * json_value_get_array (const JSON_Value *value);
+const char * json_value_get_string (const JSON_Value *value);
+double json_value_get_number (const JSON_Value *value);
+int json_value_get_boolean(const JSON_Value *value);
+JSON_Value * json_value_get_parent (const JSON_Value *value);
+
+/* Same as above, but shorter */
+JSON_Value_Type json_type (const JSON_Value *value);
+JSON_Object * json_object (const JSON_Value *value);
+JSON_Array * json_array (const JSON_Value *value);
+const char * json_string (const JSON_Value *value);
+double json_number (const JSON_Value *value);
+int json_boolean(const JSON_Value *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/cooker/util.c b/src/cooker/util.c
new file mode 100644
index 0000000..a2ecce0
--- /dev/null
+++ b/src/cooker/util.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+/* seitan - Syscall Expressive Interpreter, Transformer and Notifier
+ *
+ * cooker/util.c - Convenience routines
+ *
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#define logfn(name) \
+void name(const char *format, ...) { \
+ va_list args; \
+ \
+ va_start(args, format); \
+ (void)vfprintf(stderr, format, args); \
+ va_end(args); \
+ if (format[strlen(format)] != '\n') \
+ fprintf(stderr, "\n"); \
+}
+
+logfn(err)
+logfn(info)
+logfn(debug)
+
diff --git a/src/cooker/util.h b/src/cooker/util.h
new file mode 100644
index 0000000..84dc3db
--- /dev/null
+++ b/src/cooker/util.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * Copyright 2023 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#define BIT(n) (1UL << (n))
+
+void err(const char *format, ...);
+void info(const char *format, ...);
+void debug(const char *format, ...);
+
+#define die(...) \
+ do { \
+ fprintf(stderr, "%s:%i: ", __FILE__, __LINE__); \
+ err(__VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+#endif /* UTIL_H */