summaryrefslogtreecommitdiffhomepage
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/digest.c372
-rw-r--r--lib/fs.c196
-rw-r--r--lib/math.c9
-rw-r--r--lib/nl80211.c55
-rw-r--r--lib/rtnl.c10
-rw-r--r--lib/socket.c148
-rw-r--r--lib/struct.c901
-rw-r--r--lib/ubus.c731
-rw-r--r--lib/uci.c215
-rw-r--r--lib/zlib.c751
10 files changed, 2962 insertions, 426 deletions
diff --git a/lib/digest.c b/lib/digest.c
new file mode 100644
index 0000000..c1287ff
--- /dev/null
+++ b/lib/digest.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2024 Sebastian Ertz <sebastian.ertz@gmx.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * # Digest Functions
+ *
+ * The `digest` module bundles various digest functions.
+ *
+ * @module digest
+ */
+
+#include <md5.h>
+#include <sha1.h>
+#include <sha2.h>
+
+#ifdef HAVE_DIGEST_EXTENDED
+#include <md2.h>
+#include <md4.h>
+#endif
+
+#include "ucode/module.h"
+
+
+static uc_value_t *
+uc_digest_calc_data(uc_value_t *str, char *(*fn)(const uint8_t *,size_t,char *))
+{
+ char buf[SHA512_DIGEST_STRING_LENGTH];
+
+ if( ucv_type(str) != UC_STRING )
+ return NULL;
+
+ if( fn((const uint8_t *)ucv_string_get(str), ucv_string_length(str), buf) )
+ return ucv_string_new(buf);
+
+ return NULL;
+}
+
+static uc_value_t *
+uc_digest_calc_file(uc_value_t *path, char *(fn)(const char *,char *))
+{
+ char buf[SHA512_DIGEST_STRING_LENGTH];
+
+ if( ucv_type(path) != UC_STRING )
+ return NULL;
+
+ if( fn(ucv_string_get(path), buf) )
+ return ucv_string_new(buf);
+
+ return NULL;
+}
+
+/**
+ * Calculates the MD5 hash of string and returns that hash.
+ *
+ * Returns `null` if a non-string argument is given.
+ *
+ * @function module:digest#md5
+ *
+ * @param {string} str
+ * The string to hash.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * md5("This is a test"); // Returns "ce114e4501d2f4e2dcea3e17b546f339"
+ * md5(123); // Returns null
+ */
+static uc_value_t *
+uc_digest_md5(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_data(uc_fn_arg(0), MD5Data);
+}
+
+/**
+ * Calculates the SHA1 hash of string and returns that hash.
+ *
+ * Returns `null` if a non-string argument is given.
+ *
+ * @function module:digest#sha1
+ *
+ * @param {string} str
+ * The string to hash.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * sha1("This is a test"); // Returns "a54d88e06612d820bc3be72877c74f257b561b19"
+ * sha1(123); // Returns null
+ */
+static uc_value_t *
+uc_digest_sha1(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_data(uc_fn_arg(0), SHA1Data);
+}
+
+/**
+ * Calculates the SHA256 hash of string and returns that hash.
+ *
+ * Returns `null` if a non-string argument is given.
+ *
+ * @function module:digest#sha256
+ *
+ * @param {string} str
+ * The string to hash.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * sha256("This is a test"); // Returns "c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e"
+ * sha256(123); // Returns null
+ */
+static uc_value_t *
+uc_digest_sha256(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_data(uc_fn_arg(0), SHA256Data);
+}
+
+#ifdef HAVE_DIGEST_EXTENDED
+/**
+ * Calculates the MD2 hash of string and returns that hash.
+ *
+ * Returns `null` if a non-string argument is given.
+ *
+ * @function module:digest#md2
+ *
+ * @param {string} str
+ * The string to hash.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * md2("This is a test"); // Returns "dc378580fd0722e56b82666a6994c718"
+ * md2(123); // Returns null
+ */
+static uc_value_t *
+uc_digest_md2(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_data(uc_fn_arg(0), MD2Data);
+}
+
+/**
+ * Calculates the MD4 hash of string and returns that hash.
+ *
+ * Returns `null` if a non-string argument is given.
+ *
+ * @function module:digest#md4
+ *
+ * @param {string} str
+ * The string to hash.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * md4("This is a test"); // Returns "3b487cf6856af7e330bc4b1b7d977ef8"
+ * md4(123); // Returns null
+ */
+static uc_value_t *
+uc_digest_md4(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_data(uc_fn_arg(0), MD4Data);
+}
+
+/**
+ * Calculates the SHA384 hash of string and returns that hash.
+ *
+ * Returns `null` if a non-string argument is given.
+ *
+ * @function module:digest#sha384
+ *
+ * @param {string} str
+ * The string to hash.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * sha384("This is a test"); // Returns "a27c7667e58200d4c0688ea136968404a0da366b1a9fc19bb38a0c7a609a1eef2bcc82837f4f4d92031a66051494b38c"
+ * sha384(123); // Returns null
+ */
+static uc_value_t *
+uc_digest_sha384(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_data(uc_fn_arg(0), SHA384Data);
+}
+
+/**
+ * Calculates the SHA384 hash of string and returns that hash.
+ *
+ * Returns `null` if a non-string argument is given.
+ *
+ * @function module:digest#sha384
+ *
+ * @param {string} str
+ * The string to hash.
+ *
+ * @returns {?string}
+ *
+ * @example
+ * sha512("This is a test"); // Returns "a028d4f74b602ba45eb0a93c9a4677240dcf281a1a9322f183bd32f0bed82ec72de9c3957b2f4c9a1ccf7ed14f85d73498df38017e703d47ebb9f0b3bf116f69"
+ * sha512(123); // Returns null
+ */
+static uc_value_t *
+uc_digest_sha512(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_data(uc_fn_arg(0), SHA512Data);
+}
+#endif
+
+/**
+ * Calculates the MD5 hash of a given file and returns that hash.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:digest#md5_file
+ *
+ * @param {string} path
+ * The path to the file.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_digest_md5_file(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_file(uc_fn_arg(0), MD5File);
+}
+
+/**
+ * Calculates the SHA1 hash of a given file and returns that hash.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:digest#sha1_file
+ *
+ * @param {string} path
+ * The path to the file.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_digest_sha1_file(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_file(uc_fn_arg(0), SHA1File);
+}
+
+/**
+ * Calculates the SHA256 hash of a given file and returns that hash.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:digest#sha256_file
+ *
+ * @param {string} path
+ * The path to the file.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_digest_sha256_file(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_file(uc_fn_arg(0), SHA256File);
+}
+
+#ifdef HAVE_DIGEST_EXTENDED
+/**
+ * Calculates the MD2 hash of a given file and returns that hash.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:digest#md2_file
+ *
+ * @param {string} path
+ * The path to the file.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_digest_md2_file(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_file(uc_fn_arg(0), MD2File);
+}
+
+/**
+ * Calculates the MD4 hash of a given file and returns that hash.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:digest#md4_file
+ *
+ * @param {string} path
+ * The path to the file.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_digest_md4_file(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_file(uc_fn_arg(0), MD4File);
+}
+
+/**
+ * Calculates the SHA384 hash of a given file and returns that hash.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:digest#sha384_file
+ *
+ * @param {string} path
+ * The path to the file.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_digest_sha384_file(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_file(uc_fn_arg(0), SHA384File);
+}
+
+/**
+ * Calculates the SHA512 hash of a given file and returns that hash.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:digest#sha512_file
+ *
+ * @param {string} path
+ * The path to the file.
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_digest_sha512_file(uc_vm_t *vm, size_t nargs)
+{
+ return uc_digest_calc_file(uc_fn_arg(0), SHA512File);
+}
+#endif
+
+
+static const uc_function_list_t global_fns[] = {
+ { "md5", uc_digest_md5 },
+ { "sha1", uc_digest_sha1 },
+ { "sha256", uc_digest_sha256 },
+ { "md5_file", uc_digest_md5_file },
+ { "sha1_file", uc_digest_sha1_file },
+ { "sha256_file", uc_digest_sha256_file },
+#ifdef HAVE_DIGEST_EXTENDED
+ { "md2", uc_digest_md2 },
+ { "md4", uc_digest_md4 },
+ { "sha384", uc_digest_sha384 },
+ { "sha512", uc_digest_sha512 },
+ { "md2_file", uc_digest_md2_file },
+ { "md4_file", uc_digest_md4_file },
+ { "sha384_file", uc_digest_sha384_file },
+ { "sha512_file", uc_digest_sha512_file },
+#endif
+};
+
+void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
+{
+ uc_function_list_register(scope, global_fns);
+}
diff --git a/lib/fs.c b/lib/fs.c
index d0d97ac..3b9f731 100644
--- a/lib/fs.c
+++ b/lib/fs.c
@@ -61,15 +61,28 @@
#include <limits.h>
#include <fcntl.h>
+#if defined(__linux__)
+#define HAS_IOCTL
+#endif
+
+#ifdef HAS_IOCTL
+#include <sys/ioctl.h>
+
+#define IOC_DIR_NONE (_IOC_NONE)
+#define IOC_DIR_READ (_IOC_READ)
+#define IOC_DIR_WRITE (_IOC_WRITE)
+#define IOC_DIR_RW (_IOC_READ | _IOC_WRITE)
+
+#endif
+
#include "ucode/module.h"
#include "ucode/platform.h"
-#define err_return(err) do { last_error = err; return NULL; } while(0)
-
-//static const uc_ops *ops;
-static uc_resource_type_t *file_type, *proc_type, *dir_type;
+#define err_return(err) do { \
+ uc_vm_registry_set(vm, "fs.last_error", ucv_int64_new(err)); \
+ return NULL; \
+} while(0)
-static int last_error = 0;
/**
* Query error information.
@@ -92,15 +105,14 @@ static int last_error = 0;
static uc_value_t *
uc_fs_error(uc_vm_t *vm, size_t nargs)
{
- uc_value_t *errmsg;
+ int last_error = ucv_int64_get(uc_vm_registry_get(vm, "fs.last_error"));
if (last_error == 0)
return NULL;
- errmsg = ucv_string_new(strerror(last_error));
- last_error = 0;
+ uc_vm_registry_set(vm, "fs.last_error", ucv_int64_new(0));
- return errmsg;
+ return ucv_string_new(strerror(last_error));
}
static uc_value_t *
@@ -126,8 +138,10 @@ uc_fs_read_common(uc_vm_t *vm, size_t nargs, const char *type)
if (llen == 4 && !strcmp(lstr, "line")) {
llen = getline(&p, &rlen, *fp);
- if (llen == -1)
+ if (llen == -1) {
+ free(p);
err_return(errno);
+ }
len = (size_t)llen;
}
@@ -154,8 +168,10 @@ uc_fs_read_common(uc_vm_t *vm, size_t nargs, const char *type)
else if (llen == 1) {
llen = getdelim(&p, &rlen, *lstr, *fp);
- if (llen == -1)
+ if (llen == -1) {
+ free(p);
err_return(errno);
+ }
len = (size_t)llen;
}
@@ -503,7 +519,7 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs)
if (!fp)
err_return(errno);
- return uc_resource_new(proc_type, fp);
+ return ucv_resource_create(vm, "fs.proc", fp);
}
@@ -929,6 +945,132 @@ uc_fs_fileno(uc_vm_t *vm, size_t nargs)
return uc_fs_fileno_common(vm, nargs, "fs.file");
}
+#ifdef HAS_IOCTL
+
+/**
+ * Performs an ioctl operation on the file.
+ *
+ * The direction parameter specifies who is reading and writing,
+ * from the user's point of view. It can be one of the following values:
+ *
+ * | Direction | Description |
+ * |----------------|-----------------------------------------------------------------------------------|
+ * | IOC_DIR_NONE | neither userspace nor kernel is writing, ioctl is executed without passing data. |
+ * | IOC_DIR_WRITE | userspace is writing and kernel is reading. |
+ * | IOC_DIR_READ | kernel is writing and userspace is reading. |
+ * | IOC_DIR_RW | userspace is writing and kernel is writing back into the data structure. |
+ *
+ * Returns the result of the ioctl operation; for `IOC_DIR_READ` and
+ * `IOC_DIR_RW` this is a string containing the data, otherwise a number as
+ * return code.
+ *
+ * In case of an error, null is returned and error details are available via
+ * {@link module:fs#error|error()}.
+ *
+ * @function module:fs.file#ioctl
+ *
+ * @param {number} direction
+ * The direction of the ioctl operation. Use constants IOC_DIR_*.
+ *
+ * @param {number} type
+ * The ioctl type (see https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html)
+ *
+ * @param {number} num
+ * The ioctl sequence number.
+ *
+ * @param {number|string} [value]
+ * The value to pass to the ioctl system call. For `IOC_DIR_NONE`, this argument
+ * is ignored. With `IOC_DIR_READ`, the value should be a positive integer
+ * specifying the number of bytes to expect from the kernel. For the other
+ * directions, `IOC_DIR_WRITE` and `IOC_DIR_RW`, that value parameter must be a
+ * string, serving as buffer for the data to send.
+ *
+ * @returns {?number|?string}
+ */
+static uc_value_t *
+uc_fs_ioctl(uc_vm_t *vm, size_t nargs)
+{
+ FILE *fp = uc_fn_thisval("fs.file");
+ uc_value_t *direction = uc_fn_arg(0);
+ uc_value_t *type = uc_fn_arg(1);
+ uc_value_t *num = uc_fn_arg(2);
+ uc_value_t *value = uc_fn_arg(3);
+ uc_value_t *mem = NULL;
+ char *buf = NULL;
+ unsigned long req = 0;
+ unsigned int dir, ty, nr;
+ size_t sz = 0;
+ int fd, ret;
+
+ if (!fp)
+ err_return(EBADF);
+
+ fd = fileno(fp);
+ if (fd == -1)
+ err_return(EBADF);
+
+ if (ucv_type(direction) != UC_INTEGER || ucv_type(type) != UC_INTEGER ||
+ ucv_type(num) != UC_INTEGER)
+ err_return(EINVAL);
+
+ dir = ucv_uint64_get(direction);
+ ty = ucv_uint64_get(type);
+ nr = ucv_uint64_get(num);
+
+ switch (dir) {
+ case IOC_DIR_NONE:
+ break;
+
+ case IOC_DIR_WRITE:
+ if (ucv_type(value) != UC_STRING)
+ err_return(EINVAL);
+
+ sz = ucv_string_length(value);
+ buf = ucv_string_get(value);
+ break;
+
+ case IOC_DIR_READ:
+ if (ucv_type(value) != UC_INTEGER)
+ err_return(EINVAL);
+
+ sz = ucv_to_unsigned(value);
+
+ if (errno != 0)
+ err_return(errno);
+
+ mem = xalloc(sizeof(uc_string_t) + sz + 1);
+ mem->type = UC_STRING;
+ mem->refcount = 1;
+ buf = ucv_string_get(mem);
+ ((uc_string_t *)mem)->length = sz;
+ break;
+
+ case IOC_DIR_RW:
+ if (ucv_type(value) != UC_STRING)
+ err_return(EINVAL);
+
+ sz = ucv_string_length(value);
+ mem = ucv_string_new_length(ucv_string_get(value), sz);
+ buf = ucv_string_get(mem);
+ break;
+
+ default:
+ err_return(EINVAL);
+ }
+
+ req = _IOC(dir, ty, nr, sz);
+ ret = ioctl(fd, req, buf);
+
+ if (ret < 0) {
+ ucv_put(mem);
+ err_return(errno);
+ }
+
+ return mem ? mem : ucv_uint64_new(ret);
+}
+
+#endif
+
/**
* Opens a file.
*
@@ -1045,7 +1187,7 @@ uc_fs_open(uc_vm_t *vm, size_t nargs)
err_return(i);
}
- return uc_resource_new(file_type, fp);
+ return ucv_resource_create(vm, "fs.file", fp);
}
/**
@@ -1104,7 +1246,7 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
if (!fp)
err_return(errno);
- return uc_resource_new(file_type, fp);
+ return ucv_resource_create(vm, "fs.file", fp);
}
@@ -1305,7 +1447,7 @@ uc_fs_opendir(uc_vm_t *vm, size_t nargs)
if (!dp)
err_return(errno);
- return uc_resource_new(dir_type, dp);
+ return ucv_resource_create(vm, "fs.dir", dp);
}
/**
@@ -2267,7 +2409,7 @@ uc_fs_mkstemp(uc_vm_t *vm, size_t nargs)
err_return(errno);
}
- return uc_resource_new(file_type, fp);
+ return ucv_resource_create(vm, "fs.file", fp);
}
/**
@@ -2637,8 +2779,8 @@ uc_fs_pipe(uc_vm_t *vm, size_t nargs)
rv = ucv_array_new_length(vm, 2);
- ucv_array_push(rv, uc_resource_new(file_type, rfp));
- ucv_array_push(rv, uc_resource_new(file_type, wfp));
+ ucv_array_push(rv, ucv_resource_create(vm, "fs.file", rfp));
+ ucv_array_push(rv, ucv_resource_create(vm, "fs.file", wfp));
return rv;
}
@@ -2665,6 +2807,9 @@ static const uc_function_list_t file_fns[] = {
{ "isatty", uc_fs_isatty },
{ "truncate", uc_fs_truncate },
{ "lock", uc_fs_lock },
+#if defined(__linux__)
+ { "ioctl", uc_fs_ioctl },
+#endif
};
static const uc_function_list_t dir_fns[] = {
@@ -2737,11 +2882,20 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
uc_function_list_register(scope, global_fns);
- proc_type = uc_type_declare(vm, "fs.proc", proc_fns, close_proc);
- file_type = uc_type_declare(vm, "fs.file", file_fns, close_file);
- dir_type = uc_type_declare(vm, "fs.dir", dir_fns, close_dir);
+ uc_type_declare(vm, "fs.proc", proc_fns, close_proc);
+ uc_type_declare(vm, "fs.dir", dir_fns, close_dir);
+
+ uc_resource_type_t *file_type = uc_type_declare(vm, "fs.file", file_fns, close_file);
ucv_object_add(scope, "stdin", uc_resource_new(file_type, stdin));
ucv_object_add(scope, "stdout", uc_resource_new(file_type, stdout));
ucv_object_add(scope, "stderr", uc_resource_new(file_type, stderr));
+
+#ifdef HAS_IOCTL
+#define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
+ ADD_CONST(IOC_DIR_NONE);
+ ADD_CONST(IOC_DIR_READ);
+ ADD_CONST(IOC_DIR_WRITE);
+ ADD_CONST(IOC_DIR_RW);
+#endif
}
diff --git a/lib/math.c b/lib/math.c
index 91c5c30..529c613 100644
--- a/lib/math.c
+++ b/lib/math.c
@@ -52,7 +52,6 @@
#include "ucode/module.h"
-static bool srand_called = false;
/**
* Returns the absolute value of the given numeric value.
@@ -398,11 +397,11 @@ uc_rand(uc_vm_t *vm, size_t nargs)
{
struct timeval tv;
- if (!srand_called) {
+ if (!ucv_boolean_get(uc_vm_registry_get(vm, "math.srand_called"))) {
gettimeofday(&tv, NULL);
srand((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
- srand_called = true;
+ uc_vm_registry_set(vm, "math.srand_called", ucv_boolean_new(true));
}
return ucv_int64_new(rand());
@@ -429,7 +428,7 @@ uc_srand(uc_vm_t *vm, size_t nargs)
int64_t n = ucv_to_integer(uc_fn_arg(0));
srand((unsigned int)n);
- srand_called = true;
+ uc_vm_registry_set(vm, "math.srand_called", ucv_boolean_new(true));
return NULL;
}
@@ -477,4 +476,6 @@ static const uc_function_list_t math_fns[] = {
void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
uc_function_list_register(scope, math_fns);
+
+ uc_vm_registry_set(vm, "math.srand_called", ucv_boolean_new(false));
}
diff --git a/lib/nl80211.c b/lib/nl80211.c
index feebaeb..34a3e92 100644
--- a/lib/nl80211.c
+++ b/lib/nl80211.c
@@ -330,7 +330,7 @@ static const uc_nl_nested_spec_t nl80211_mesh_setup_nla = {
static const uc_nl_nested_spec_t nl80211_mntr_flags_nla = {
.headsize = 0,
- .nattrs = 6,
+ .nattrs = 7,
.attrs = {
{ NL80211_MNTR_FLAG_FCSFAIL, "fcsfail", DT_FLAG, 0, NULL },
{ NL80211_MNTR_FLAG_PLCPFAIL, "plcpfail", DT_FLAG, 0, NULL },
@@ -338,6 +338,7 @@ static const uc_nl_nested_spec_t nl80211_mntr_flags_nla = {
{ NL80211_MNTR_FLAG_OTHER_BSS, "other_bss", DT_FLAG, 0, NULL },
{ NL80211_MNTR_FLAG_COOK_FRAMES, "cook_frames", DT_FLAG, 0, NULL },
{ NL80211_MNTR_FLAG_ACTIVE, "active", DT_FLAG, 0, NULL },
+ { NL80211_MNTR_FLAG_SKIP_TX, "skip_tx", DT_FLAG, 0, NULL },
}
};
@@ -588,7 +589,7 @@ static const uc_nl_nested_spec_t nl80211_wiphy_bands_rates_nla = {
static const uc_nl_nested_spec_t nl80211_wiphy_bands_iftype_data_nla = {
.headsize = 0,
- .nattrs = 7,
+ .nattrs = 9,
.attrs = {
{ NL80211_BAND_IFTYPE_ATTR_IFTYPES, "iftypes", DT_NESTED, 0, &nl80211_ifcomb_limit_types_nla },
{ NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC, "he_cap_mac", DT_U8, DF_ARRAY, NULL },
@@ -597,6 +598,8 @@ static const uc_nl_nested_spec_t nl80211_wiphy_bands_iftype_data_nla = {
{ NL80211_BAND_IFTYPE_ATTR_HE_CAP_PPE, "he_cap_ppe", DT_U8, DF_ARRAY, NULL },
{ NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA, "he_6ghz_capa", DT_U16, 0, NULL },
{ NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS, "vendor_elems", DT_STRING, DF_BINARY, NULL },
+ { NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MAC, "eht_cap_mac", DT_U8, DF_ARRAY, NULL },
+ { NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PHY, "eht_cap_phy", DT_U8, DF_ARRAY, NULL },
}
};
@@ -693,7 +696,7 @@ static const uc_nl_nested_spec_t nl80211_bss_nla = {
static const uc_nl_nested_spec_t nl80211_sta_info_bitrate_nla = {
.headsize = 0,
- .nattrs = 18,
+ .nattrs = 22,
.attrs = {
{ NL80211_RATE_INFO_BITRATE, "bitrate", DT_U16, 0, NULL },
{ NL80211_RATE_INFO_BITRATE32, "bitrate32", DT_U32, 0, NULL },
@@ -707,10 +710,14 @@ static const uc_nl_nested_spec_t nl80211_sta_info_bitrate_nla = {
{ NL80211_RATE_INFO_HE_GI, "he_gi", DT_U8, 0, NULL },
{ NL80211_RATE_INFO_HE_DCM, "he_dcm", DT_U8, 0, NULL },
{ NL80211_RATE_INFO_HE_RU_ALLOC, "he_ru_alloc", DT_U8, 0, NULL },
+ { NL80211_RATE_INFO_EHT_MCS, "eht_mcs", DT_U8, 0, NULL },
+ { NL80211_RATE_INFO_EHT_NSS, "eht_nss", DT_U8, 0, NULL },
+ { NL80211_RATE_INFO_EHT_GI, "eht_gi", DT_U8, 0, NULL },
{ NL80211_RATE_INFO_40_MHZ_WIDTH, "width_40", DT_FLAG, 0, NULL },
{ NL80211_RATE_INFO_80_MHZ_WIDTH, "width_80", DT_FLAG, 0, NULL },
{ NL80211_RATE_INFO_80P80_MHZ_WIDTH, "width_80p80", DT_FLAG, 0, NULL },
{ NL80211_RATE_INFO_160_MHZ_WIDTH, "width_160", DT_FLAG, 0, NULL },
+ { NL80211_RATE_INFO_320_MHZ_WIDTH, "width_320", DT_FLAG, 0, NULL },
{ NL80211_RATE_INFO_10_MHZ_WIDTH, "width_10", DT_FLAG, 0, NULL },
{ NL80211_RATE_INFO_5_MHZ_WIDTH, "width_5", DT_FLAG, 0, NULL },
}
@@ -844,17 +851,18 @@ static const uc_nl_nested_spec_t nl80211_radio_freq_range_nla = {
static const uc_nl_nested_spec_t nl80211_wiphy_radio_nla = {
.headsize = 0,
- .nattrs = 3,
+ .nattrs = 4,
.attrs = {
{ NL80211_WIPHY_RADIO_ATTR_INDEX, "index", DT_U32, 0, NULL },
{ NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE, "freq_ranges", DT_NESTED, DF_REPEATED, &nl80211_radio_freq_range_nla },
{ NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION, "interface_combinations", DT_NESTED, DF_REPEATED, &nl80211_ifcomb_nla },
+ { NL80211_WIPHY_RADIO_ATTR_ANTENNA_MASK, "antenna_mask", DT_U32, 0, NULL },
}
};
static const uc_nl_nested_spec_t nl80211_msg = {
.headsize = 0,
- .nattrs = 128,
+ .nattrs = 130,
.attrs = {
{ NL80211_ATTR_4ADDR, "4addr", DT_U8, 0, NULL },
{ NL80211_ATTR_AIRTIME_WEIGHT, "airtime_weight", DT_U16, 0, NULL },
@@ -881,6 +889,7 @@ static const uc_nl_nested_spec_t nl80211_msg = {
{ NL80211_ATTR_DFS_REGION, "dfs_region", DT_U8, 0, NULL },
{ NL80211_ATTR_DTIM_PERIOD, "dtim_period", DT_U32, 0, NULL },
{ NL80211_ATTR_DURATION, "duration", DT_U32, 0, NULL },
+ { NL80211_ATTR_EXT_FEATURES, "extended_features", DT_U8, DF_ARRAY, NULL },
{ NL80211_ATTR_FEATURE_FLAGS, "feature_flags", DT_U32, 0, NULL },
{ NL80211_ATTR_FRAME, "frame", DT_STRING, DF_BINARY, NULL },
{ NL80211_ATTR_FRAME_MATCH, "frame_match", DT_STRING, DF_BINARY, NULL },
@@ -984,6 +993,7 @@ static const uc_nl_nested_spec_t nl80211_msg = {
{ NL80211_ATTR_MAX_AP_ASSOC_STA, "max_ap_assoc", DT_U16, 0, NULL },
{ NL80211_ATTR_SURVEY_INFO, "survey_info", DT_NESTED, 0, &nl80211_survey_info_nla },
{ NL80211_ATTR_WIPHY_RADIOS, "radios", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_wiphy_radio_nla },
+ { NL80211_ATTR_VIF_RADIO_MASK, "vif_radio_mask", DT_U32, 0, NULL },
}
};
@@ -2014,7 +2024,8 @@ typedef struct {
reply_state_t state;
uc_vm_t *vm;
uc_value_t *res;
- bool merge;
+ bool merge_phy_info;
+ bool single_phy_info;
const uc_nl_nested_spec_t *spec;
} request_state_t;
@@ -2122,14 +2133,23 @@ cb_reply(struct nl_msg *msg, void *arg)
if (rv) {
if (hdr->nlmsg_flags & NLM_F_MULTI) {
- if (!s->res)
- s->res = ucv_array_new(s->vm);
-
- if (s->merge) {
+ if (s->merge_phy_info && s->single_phy_info) {
+ if (!s->res) {
+ s->res = o;
+ }
+ else {
+ deep_merge_object(s->res, o);
+ ucv_put(o);
+ }
+ }
+ else if (s->merge_phy_info) {
idx = ucv_object_get(o, "wiphy", NULL);
i = idx ? ucv_int64_get(idx) : -1;
if (i >= 0) {
+ if (!s->res)
+ s->res = ucv_array_new(s->vm);
+
idx = ucv_array_get(s->res, i);
if (idx) {
@@ -2142,6 +2162,9 @@ cb_reply(struct nl_msg *msg, void *arg)
}
}
else {
+ if (!s->res)
+ s->res = ucv_array_new(s->vm);
+
ucv_array_push(s->res, o);
}
}
@@ -2598,9 +2621,19 @@ uc_nl_request(uc_vm_t *vm, size_t nargs)
cid -= HWSIM_CMD_OFFSET;
st.spec = &hwsim_msg;
}
+ else if (cid == NL80211_CMD_GET_WIPHY) {
+ id = uc_nl_find_family_id("nl80211");
+ st.spec = &nl80211_msg;
+ st.merge_phy_info = true;
+
+ if (ucv_object_get(payload, "wiphy", NULL) != NULL)
+ st.single_phy_info = true;
+
+ if (ucv_is_truish(ucv_object_get(payload, "split_wiphy_dump", NULL)))
+ flagval |= NLM_F_DUMP;
+ }
else {
id = uc_nl_find_family_id("nl80211");
- st.merge = (cid == NL80211_CMD_GET_WIPHY);
st.spec = &nl80211_msg;
}
diff --git a/lib/rtnl.c b/lib/rtnl.c
index 6d2c819..f11a653 100644
--- a/lib/rtnl.c
+++ b/lib/rtnl.c
@@ -3612,6 +3612,10 @@ cb_listener_event(struct nl_msg *msg, void *arg)
if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE) {
uloop_end();
+ set_error(NLE_FAILURE, "Runtime exception in callback");
+
+ errno = EINVAL;
+
return NL_STOP;
}
@@ -3631,12 +3635,8 @@ uc_nl_listener_cb(struct uloop_fd *fd, unsigned int events)
nl_recvmsgs_default(nl_conn.evsock);
- if (errno != 0) {
- if (errno != EAGAIN && errno != EWOULDBLOCK)
- set_error(errno, NULL);
-
+ if (errno != 0)
break;
- }
}
}
diff --git a/lib/socket.c b/lib/socket.c
index 63ca24f..ebb7649 100644
--- a/lib/socket.c
+++ b/lib/socket.c
@@ -78,6 +78,7 @@
#if defined(__linux__)
# include <linux/if_packet.h>
+# include <linux/filter.h>
# ifndef SO_TIMESTAMP_OLD
# define SO_TIMESTAMP_OLD SO_TIMESTAMP
@@ -306,13 +307,16 @@ hwaddr_to_uv(uint8_t *addr, size_t alen)
char buf[sizeof("FF:FF:FF:FF:FF:FF:FF:FF")], *p = buf;
const char *hex = "0123456789ABCDEF";
- for (size_t i = 0; i < alen && i < 8; i++) {
+ if (alen > 8)
+ alen = 8;
+
+ for (size_t i = 0; i < alen; i++) {
if (i) *p++ = ':';
*p++ = hex[addr[i] / 16];
*p++ = hex[addr[i] % 16];
}
- return ucv_string_new(buf);
+ return ucv_string_new_length(buf, alen);
}
static bool
@@ -966,6 +970,90 @@ static struct_t st_timeval = {
};
#if defined(__linux__)
+static bool
+filter_to_c(void *st, uc_value_t *uv)
+{
+ struct sock_fprog **fpp = st;
+ struct sock_fprog *fp = *fpp;
+ size_t i, len;
+
+ if (ucv_type(uv) == UC_STRING) {
+ size_t len = ucv_string_length(uv);
+
+ if (len == 0 || (len % sizeof(struct sock_filter)) != 0)
+ err_return(EINVAL, "Filter program length not a multiple of %zu",
+ sizeof(struct sock_filter));
+
+ fp = *fpp = xrealloc(fp, sizeof(struct sock_fprog) + len);
+ fp->filter = memcpy((char *)fp + sizeof(struct sock_fprog), ucv_string_get(uv), len);
+
+ if (fp->len == 0)
+ fp->len = len / sizeof(struct sock_filter);
+ }
+ else if (ucv_type(uv) == UC_ARRAY) {
+ /* Opcode array of array. Each sub-array is a 4 element tuple */
+ if (ucv_type(ucv_array_get(uv, 0)) == UC_ARRAY) {
+ len = ucv_array_length(uv);
+
+ fp = *fpp = xrealloc(fp, sizeof(struct sock_fprog)
+ + (len * sizeof(struct sock_filter)));
+
+ fp->filter = (struct sock_filter *)((char *)fp + sizeof(struct sock_fprog));
+
+ for (i = 0; i < len; i++) {
+ uc_value_t *op = ucv_array_get(uv, i);
+
+ if (ucv_type(op) != UC_ARRAY)
+ continue;
+
+ fp->filter[i].code = ucv_to_unsigned(ucv_array_get(op, 0));
+ fp->filter[i].jt = ucv_to_unsigned(ucv_array_get(op, 1));
+ fp->filter[i].jf = ucv_to_unsigned(ucv_array_get(op, 2));
+ fp->filter[i].k = ucv_to_unsigned(ucv_array_get(op, 3));
+ }
+ }
+
+ /* Flat opcode array, must be a multiple of 4 */
+ else {
+ len = ucv_array_length(uv);
+
+ if (len % 4)
+ err_return(EINVAL, "Opcode array length not a multiple of 4");
+
+ len /= 4;
+
+ fp = *fpp = xrealloc(fp, sizeof(struct sock_fprog)
+ + (len * sizeof(struct sock_filter)));
+
+ fp->filter = (struct sock_filter *)((char *)fp + sizeof(struct sock_fprog));
+
+ for (i = 0; i < len; i++) {
+ fp->filter[i].code = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 0));
+ fp->filter[i].jt = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 1));
+ fp->filter[i].jf = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 2));
+ fp->filter[i].k = ucv_to_unsigned(ucv_array_get(uv, i * 4 + 3));
+ }
+ }
+
+ if (fp->len == 0)
+ fp->len = i;
+ }
+ else {
+ err_return(EINVAL, "Expecting either BPF bytecode string or array of opcodes");
+ }
+
+ return true;
+}
+
+static struct_t st_sock_fprog = {
+ .size = sizeof(struct sock_fprog),
+ .members = (member_t []){
+ STRUCT_MEMBER_NP(sock_fprog, len, DT_UNSIGNED),
+ STRUCT_MEMBER_CB(filter, filter_to_c, NULL),
+ { 0 }
+ }
+};
+
static struct_t st_ucred = {
.size = sizeof(struct ucred),
.members = (member_t []){
@@ -1048,7 +1136,7 @@ in6_ifindex_to_uv(void *st)
static bool
in6_ifindex_to_c(void *st, uc_value_t *uv)
{
- struct ipv6_mreq *mr = st;
+ struct ipv6_mreq *mr = *(struct ipv6_mreq **)st;
if (ucv_type(uv) == UC_STRING) {
mr->ipv6mr_interface = if_nametoindex(ucv_string_get(uv));
@@ -1186,7 +1274,9 @@ rcv_wscale_to_uv(void *st)
static bool
snd_wscale_to_c(void *st, uc_value_t *uv)
{
- ((struct tcp_info *)st)->tcpi_snd_wscale = ucv_to_unsigned(uv);
+ struct tcp_info *ti = *(struct tcp_info **)st;
+
+ ti->tcpi_snd_wscale = ucv_to_unsigned(uv);
if (errno)
err_return(errno, "Unable to convert field snd_wscale to unsigned");
@@ -1197,7 +1287,9 @@ snd_wscale_to_c(void *st, uc_value_t *uv)
static bool
rcv_wscale_to_c(void *st, uc_value_t *uv)
{
- ((struct tcp_info *)st)->tcpi_rcv_wscale = ucv_to_unsigned(uv);
+ struct tcp_info *ti = *(struct tcp_info **)st;
+
+ ti->tcpi_rcv_wscale = ucv_to_unsigned(uv);
if (errno)
err_return(errno, "Unable to convert field rcv_wscale to unsigned");
@@ -1311,7 +1403,7 @@ mr_ifindex_to_uv(void *st)
static bool
mr_ifindex_to_c(void *st, uc_value_t *uv)
{
- struct packet_mreq *mr = st;
+ struct packet_mreq *mr = *(struct packet_mreq **)st;
if (ucv_type(uv) == UC_STRING) {
mr->mr_ifindex = if_nametoindex(ucv_string_get(uv));
@@ -1341,7 +1433,7 @@ mr_address_to_uv(void *st)
static bool
mr_address_to_c(void *st, uc_value_t *uv)
{
- struct packet_mreq *mr = st;
+ struct packet_mreq *mr = *(struct packet_mreq **)st;
size_t len;
if (!uv_to_hwaddr(uv, mr->mr_address, &len))
@@ -1396,12 +1488,23 @@ static struct_t st_tpacket_auxdata = {
}
};
+struct fanout_args_local {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ uint16_t id;
+ uint16_t type_flags;
+#else
+ uint16_t type_flags;
+ uint16_t id;
+#endif
+ uint32_t max_num_members;
+};
+
static struct_t st_fanout_args = {
- .size = sizeof(struct fanout_args),
+ .size = sizeof(struct fanout_args_local),
.members = (member_t []){
- STRUCT_MEMBER_NP(fanout_args, id, DT_UNSIGNED),
- STRUCT_MEMBER_NP(fanout_args, type_flags, DT_UNSIGNED),
- STRUCT_MEMBER_NP(fanout_args, max_num_members, DT_UNSIGNED),
+ STRUCT_MEMBER_NP(fanout_args_local, id, DT_UNSIGNED),
+ STRUCT_MEMBER_NP(fanout_args_local, type_flags, DT_UNSIGNED),
+ STRUCT_MEMBER_NP(fanout_args_local, max_num_members, DT_UNSIGNED),
{ 0 }
}
};
@@ -1493,7 +1596,7 @@ static sockopt_t sockopts[] = {
{ SOL_SOCKET, SO_TIMESTAMP, SV_BOOL },
{ SOL_SOCKET, SO_TYPE, SV_INT },
#if defined(__linux__)
- { SOL_SOCKET, SO_ATTACH_FILTER, SV_STRING },
+ { SOL_SOCKET, SO_ATTACH_FILTER, &st_sock_fprog },
{ SOL_SOCKET, SO_ATTACH_BPF, SV_INT },
{ SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SV_STRING },
{ SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, SV_INT },
@@ -1778,7 +1881,7 @@ uv_to_struct(uc_value_t *uv, struct_t *spec)
break;
case DT_CALLBACK:
- if (m->u1.to_c && !m->u1.to_c(st, fv)) {
+ if (m->u1.to_c && !m->u1.to_c(&st, fv)) {
free(st);
return NULL;
}
@@ -2846,6 +2949,9 @@ out:
* @param {number} [backlog=128]
* The maximum length of the queue of pending connections.
*
+ * @param {boolean} [reuseaddr]
+ * Whether to set the SO_REUSEADDR option before calling bind().
+ *
* @returns {module:socket.socket}
*
* @example
@@ -2863,7 +2969,7 @@ uc_socket_listen(uc_vm_t *vm, size_t nargs)
{
int ret, fd, curr_weight, prev_weight, socktype = 0, protocol = 0;
struct addrinfo *ai_results, *ai_hints, *ai;
- uc_value_t *host, *serv, *hints, *backlog;
+ uc_value_t *host, *serv, *hints, *backlog, *reuseaddr;
struct sockaddr_storage ss = { 0 };
bool v6, lo, ll;
socklen_t slen;
@@ -2872,7 +2978,8 @@ uc_socket_listen(uc_vm_t *vm, size_t nargs)
"host", UC_NULL, true, &host,
"service", UC_NULL, true, &serv,
"hints", UC_OBJECT, true, &hints,
- "backlog", UC_INTEGER, true, &backlog);
+ "backlog", UC_INTEGER, true, &backlog,
+ "reuseaddr", UC_BOOLEAN, true, &reuseaddr);
ai_hints = hints
? (struct addrinfo *)uv_to_struct(hints, &st_addrinfo) : NULL;
@@ -2960,6 +3067,13 @@ uc_socket_listen(uc_vm_t *vm, size_t nargs)
if (fd == -1)
err_return(errno, "socket()");
+ if (ucv_is_truish(reuseaddr)) {
+ ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));
+
+ if (ret == -1)
+ err_return(errno, "setsockopt()");
+ }
+
ret = bind(fd, (struct sockaddr *)&ss, slen);
if (ret == -1) {
@@ -3909,10 +4023,6 @@ uc_socket_inst_recvmsg(uc_vm_t *vm, size_t nargs)
flagval = flags ? ucv_int64_get(flags) : 0;
-#if defined(__linux__)
- flagval |= MSG_CMSG_CLOEXEC;
-#endif
-
/* prepare ancillary data buffer */
if (anclength) {
size_t sz = ucv_to_unsigned(anclength);
diff --git a/lib/struct.c b/lib/struct.c
index ad0bc20..7393347 100644
--- a/lib/struct.c
+++ b/lib/struct.c
@@ -371,8 +371,6 @@
#include "ucode/module.h"
#include "ucode/vallist.h"
-static uc_resource_type_t *struct_type;
-
typedef struct formatdef {
char format;
ssize_t size;
@@ -395,6 +393,13 @@ typedef struct {
formatcode_t codes[];
} formatstate_t;
+typedef struct {
+ uc_resource_t resource;
+ size_t length;
+ size_t capacity;
+ size_t position;
+} formatbuffer_t;
+
/* Define various structs to figure out the alignments of types */
@@ -2474,12 +2479,59 @@ overflow:
return NULL;
}
-static uc_value_t *
-uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
+static bool
+grow_buffer(uc_vm_t *vm, void **buf, size_t *bufsz, size_t length)
+{
+ const size_t overhead = sizeof(uc_string_t) + 1;
+
+ if (length > *bufsz) {
+ size_t old_size = *bufsz;
+ size_t new_size = (length + 7u) & ~7u;
+
+ if (*buf != NULL) {
+ new_size = *bufsz;
+
+ while (length > new_size) {
+ if (new_size > SIZE_MAX - (new_size >> 1)) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
+ "Overflow reallocating buffer from %zu to %zu bytes",
+ *bufsz, length);
+
+ return false;
+ }
+
+ new_size += ((new_size >> 1) + 7u) & ~7u;
+ }
+ }
+
+ char *tmp = realloc(*buf, new_size + overhead);
+
+ if (!tmp) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
+ "Error reallocating buffer to %zu+%zu bytes: %m",
+ new_size, overhead);
+
+ return false;
+ }
+
+ if (*buf)
+ memset(tmp + overhead + old_size - 1, 0, new_size - old_size + 1);
+ else
+ memset(tmp, 0, new_size + overhead);
+
+ *buf = tmp;
+ *bufsz = new_size;
+ }
+
+ return true;
+}
+
+static bool
+uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff,
+ void **buf, size_t *pos, size_t *capacity)
{
- size_t ncode, arg, off;
+ size_t ncode, arg, off, new_pos;
formatcode_t *code;
- uc_string_t *buf;
ssize_t size, n;
const void *p;
@@ -2504,16 +2556,16 @@ uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
}
}
- buf = xalloc(sizeof(*buf) + state->size + off + 1);
- buf->header.type = UC_STRING;
- buf->header.refcount = 1;
- buf->length = state->size + off;
+ new_pos = *pos + state->size + off;
+
+ if (!grow_buffer(vm, buf, capacity, new_pos))
+ return NULL;
for (ncode = 0, code = &state->codes[0], off = 0;
ncode < state->ncodes;
code = &state->codes[++ncode]) {
const formatdef_t *e = code->fmtdef;
- char *res = buf->str + code->offset + off;
+ char *res = *buf + sizeof(uc_string_t) + *pos + code->offset + off;
ssize_t j = code->repeat;
while (j--) {
@@ -2526,7 +2578,7 @@ uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Argument for '*' must be a string");
- goto err;
+ return false;
}
n = ucv_string_length(v);
@@ -2547,7 +2599,7 @@ uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Argument for 's' must be a string");
- goto err;
+ return false;
}
n = ucv_string_length(v);
@@ -2564,7 +2616,7 @@ uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Argument for 'p' must be a string");
- goto err;
+ return false;
}
n = ucv_string_length(v);
@@ -2583,69 +2635,43 @@ uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
}
else {
if (!e->pack(vm, res, v, e))
- goto err;
+ return false;
}
res += size;
}
}
- return &buf->header;
-
-err:
- free(buf);
+ *pos = new_pos;
- return NULL;
+ return true;
}
static uc_value_t *
-uc_unpack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
+uc_unpack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state,
+ const char *buf, long long pos, size_t *rem, bool single)
{
- uc_value_t *bufval = uc_fn_arg(argoff);
- uc_value_t *offset = uc_fn_arg(argoff + 1);
- const char *startfrom = NULL;
- ssize_t bufrem, size, n;
uc_value_t *result;
formatcode_t *code;
size_t ncode, off;
+ ssize_t size, n;
- if (ucv_type(bufval) != UC_STRING) {
- uc_vm_raise_exception(vm, EXCEPTION_TYPE,
- "Buffer value not a string");
+ if (pos < 0)
+ pos += *rem;
+ if (pos < 0 || (size_t)pos >= *rem)
return NULL;
- }
- startfrom = ucv_string_get(bufval);
- bufrem = ucv_string_length(bufval);
+ buf += pos;
+ *rem -= pos;
- if (offset) {
- if (ucv_type(offset) != UC_INTEGER) {
- uc_vm_raise_exception(vm, EXCEPTION_TYPE,
- "Offset value not an integer");
-
- return NULL;
- }
-
- n = (ssize_t)ucv_int64_get(offset);
-
- if (n < 0)
- n += bufrem;
-
- if (n < 0 || n >= bufrem)
- return NULL;
-
- startfrom += n;
- bufrem -= n;
- }
-
- result = ucv_array_new(vm);
+ result = single ? NULL : ucv_array_new(vm);
for (ncode = 0, code = &state->codes[0], off = 0;
ncode < state->ncodes;
code = &state->codes[++ncode]) {
const formatdef_t *e = code->fmtdef;
- const char *res = startfrom + code->offset + off;
+ const char *res = buf + code->offset + off;
ssize_t j = code->repeat;
while (j--) {
@@ -2654,12 +2680,12 @@ uc_unpack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
size = code->size;
if (e->format == '*') {
- if (size == -1 || size > bufrem)
- size = bufrem;
+ if (size == -1 || (size_t)size > *rem)
+ size = *rem;
off += size;
}
- else if (size > bufrem) {
+ else if (size >= 0 && (size_t)size > *rem) {
goto fail;
}
@@ -2681,10 +2707,13 @@ uc_unpack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff)
if (v == NULL)
goto fail;
- ucv_array_push(result, v);
-
res += size;
- bufrem -= size;
+ *rem -= size;
+
+ if (single)
+ return v;
+
+ ucv_array_push(result, v);
}
}
@@ -2728,7 +2757,8 @@ static uc_value_t *
uc_pack(uc_vm_t *vm, size_t nargs)
{
uc_value_t *fmtval = uc_fn_arg(0);
- uc_value_t *res = NULL;
+ size_t pos = 0, capacity = 0;
+ uc_string_t *us = NULL;
formatstate_t *state;
state = parse_format(vm, fmtval);
@@ -2736,11 +2766,20 @@ uc_pack(uc_vm_t *vm, size_t nargs)
if (!state)
return NULL;
- res = uc_pack_common(vm, nargs, state, 1);
+ if (!uc_pack_common(vm, nargs, state, 1, (void **)&us, &pos, &capacity)) {
+ free(state);
+ free(us);
+
+ return NULL;
+ }
free(state);
- return res;
+ us->header.type = UC_STRING;
+ us->header.refcount = 1;
+ us->length = pos;
+
+ return &us->header;
}
/**
@@ -2780,15 +2819,32 @@ static uc_value_t *
uc_unpack(uc_vm_t *vm, size_t nargs)
{
uc_value_t *fmtval = uc_fn_arg(0);
+ uc_value_t *bufval = uc_fn_arg(1);
+ uc_value_t *offset = uc_fn_arg(2);
uc_value_t *res = NULL;
formatstate_t *state;
+ long long pos = 0;
+ size_t rem;
+ char *buf;
+
+ if (ucv_type(bufval) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE,
+ "Buffer value not a string");
+
+ return NULL;
+ }
+
+ if (offset && !ucv_as_longlong(vm, offset, &pos))
+ return NULL;
state = parse_format(vm, fmtval);
if (!state)
return NULL;
- res = uc_unpack_common(vm, nargs, state, 1);
+ buf = ucv_string_get(bufval);
+ rem = ucv_string_length(bufval);
+ res = uc_unpack_common(vm, nargs, state, buf, pos, &rem, false);
free(state);
@@ -2848,15 +2904,7 @@ uc_struct_new(uc_vm_t *vm, size_t nargs)
if (!state)
return NULL;
- return uc_resource_new(struct_type, state);
-}
-
-static void
-uc_struct_gc(void *ud)
-{
- formatstate_t *state = ud;
-
- free(state);
+ return ucv_resource_create(vm, "struct.format", state);
}
/**
@@ -2884,12 +2932,24 @@ uc_struct_gc(void *ud)
static uc_value_t *
uc_struct_pack(uc_vm_t *vm, size_t nargs)
{
- formatstate_t **state = uc_fn_this("struct");
+ formatstate_t **state = uc_fn_this("struct.format");
+ size_t pos = 0, capacity = 0;
+ uc_string_t *us = NULL;
if (!state || !*state)
return NULL;
- return uc_pack_common(vm, nargs, *state, 0);
+ if (!uc_pack_common(vm, nargs, *state, 0, (void **)&us, &pos, &capacity)) {
+ free(us);
+
+ return NULL;
+ }
+
+ us->header.type = UC_STRING;
+ us->header.refcount = 1;
+ us->length = pos;
+
+ return &us->header;
}
/**
@@ -2923,12 +2983,682 @@ uc_struct_pack(uc_vm_t *vm, size_t nargs)
static uc_value_t *
uc_struct_unpack(uc_vm_t *vm, size_t nargs)
{
- formatstate_t **state = uc_fn_this("struct");
+ formatstate_t **state = uc_fn_this("struct.format");
+ uc_value_t *bufval = uc_fn_arg(0);
+ uc_value_t *offset = uc_fn_arg(1);
+ long long pos = 0;
+ size_t rem;
+ char *buf;
if (!state || !*state)
return NULL;
- return uc_unpack_common(vm, nargs, *state, 0);
+ if (ucv_type(bufval) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE,
+ "Buffer value not a string");
+
+ return NULL;
+ }
+
+ if (offset && !ucv_as_longlong(vm, offset, &pos))
+ return NULL;
+
+ buf = ucv_string_get(bufval);
+ rem = ucv_string_length(bufval);
+
+ return uc_unpack_common(vm, nargs, *state, buf, pos, &rem, false);
+}
+
+
+/**
+ * Represents a struct buffer instance created by `buffer()`.
+ *
+ * @class module:struct.buffer
+ * @hideconstructor
+ *
+ * @see {@link module:struct#buffer|buffer()}
+ *
+ * @example
+ *
+ * const buf = struct.buffer();
+ *
+ * buf.put('I', 12345);
+ *
+ * const value = buf.get('I');
+ */
+
+/**
+ * Creates a new struct buffer instance.
+ *
+ * The `buffer()` function creates a new struct buffer object that can be used
+ * for incremental packing and unpacking of binary data. If an initial data
+ * string is provided, the buffer is initialized with this content.
+ *
+ * Note that even when initial data is provided, the buffer position is always
+ * set to zero. This design assumes that the primary intent when initializing
+ * a buffer with data is to read (unpack) from the beginning. If you want to
+ * append data to a pre-initialized buffer, you need to explicitly move the
+ * position to the end, either by calling `end()` or by setting the position
+ * manually with `pos()`.
+ *
+ * Returns a new struct buffer instance.
+ *
+ * @function module:struct#buffer
+ *
+ * @param {string} [initialData]
+ * Optional initial data to populate the buffer with.
+ *
+ * @returns {module:struct.buffer}
+ *
+ * @example
+ * // Create an empty buffer
+ * const emptyBuf = struct.buffer();
+ *
+ * // Create a buffer with initial data
+ * const dataBuf = struct.buffer("\x01\x02\x03\x04");
+ *
+ * // Read from the beginning of the initialized buffer
+ * const value = dataBuf.get('I');
+ *
+ * // Append data to the initialized buffer
+ * dataBuf.end().put('I', 5678);
+ *
+ * // Alternative chained syntax for initializing and appending
+ * const buf = struct.buffer("\x01\x02\x03\x04").end().put('I', 5678);
+ */
+static uc_value_t *
+uc_fmtbuf_new(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = xalloc(sizeof(*buffer));
+ uc_value_t *init_data = uc_fn_arg(0);
+
+ buffer->resource.header.type = UC_RESOURCE;
+ buffer->resource.header.refcount = 1;
+ buffer->resource.type = ucv_resource_type_lookup(vm, "struct.buffer");
+
+ if (ucv_type(init_data) == UC_STRING) {
+ char *buf = ucv_string_get(init_data);
+ size_t len = ucv_string_length(init_data);
+
+ if (!grow_buffer(vm, &buffer->resource.data, &buffer->capacity, len)) {
+ free(buffer);
+
+ return NULL;
+ }
+
+ buffer->length = len;
+ memcpy((char *)buffer->resource.data + sizeof(uc_string_t), buf, len);
+ }
+
+ return &buffer->resource.header;
+}
+
+static formatbuffer_t *
+formatbuffer_ctx(uc_vm_t *vm)
+{
+ uc_value_t *ctx = vm->callframes.entries[vm->callframes.count - 1].ctx;
+
+ if (ucv_type(ctx) != UC_RESOURCE)
+ return NULL;
+
+ uc_resource_t *res = (uc_resource_t *)ctx;
+
+ if (!res->type || strcmp(res->type->name, "struct.buffer") != 0)
+ return NULL;
+
+ return (formatbuffer_t *)res;
+}
+
+/**
+ * Get or set the current position in the buffer.
+ *
+ * If called without arguments, returns the current position.
+ * If called with a position argument, sets the current position to that value.
+ *
+ * @function module:struct.buffer#pos
+ *
+ * @param {number} [position]
+ * The position to set. If omitted, the current position is returned.
+ *
+ * @returns {number|module:struct.buffer}
+ * If called without arguments, returns the current position.
+ * If called with a position argument, returns the buffer instance for chaining.
+ *
+ * @example
+ * const currentPos = buf.pos();
+ * buf.pos(10); // Set position to 10
+ */
+static uc_value_t *
+uc_fmtbuf_pos(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+ uc_value_t *new_pos = uc_fn_arg(0);
+
+ if (!buffer)
+ return NULL;
+
+ if (new_pos) {
+ long long pos;
+
+ if (!ucv_as_longlong(vm, new_pos, &pos))
+ return NULL;
+
+ if (pos < 0) pos += buffer->length;
+ if (pos < 0) pos = 0;
+
+ if (!grow_buffer(vm, &buffer->resource.data, &buffer->capacity, pos))
+ return NULL;
+
+ buffer->position = pos;
+
+ if (buffer->position > buffer->length)
+ buffer->length = buffer->position;
+
+ return ucv_get(&buffer->resource.header);
+ }
+
+ return ucv_uint64_new(buffer->position);
+}
+
+/**
+ * Get or set the current buffer length.
+ *
+ * If called without arguments, returns the current length of the buffer.
+ * If called with a length argument, sets the buffer length to that value,
+ * padding the data with trailing zero bytes or truncating it depending on
+ * whether the updated length is larger or smaller than the current length
+ * respectively.
+ *
+ * In case the updated length is smaller than the current buffer offset, the
+ * position is updated accordingly, so that it points to the new end of the
+ * truncated buffer data.
+ *
+ * @function module:struct.buffer#length
+ *
+ * @param {number} [length]
+ * The length to set. If omitted, the current length is returned.
+ *
+ * @returns {number|module:struct.buffer}
+ * If called without arguments, returns the current length.
+ * If called with a length argument, returns the buffer instance for chaining.
+ *
+ * @example
+ * const buf = struct.buffer("abc"); // Initialize buffer with three bytes
+ * const currentLen = buf.length(); // Returns 3
+ *
+ * buf.length(6); // Extend to 6 bytes
+ * buf.slice(); // Trailing null bytes: "abc\x00\x00\x00"
+ *
+ * buf.length(2); // Truncate to 2 bytes
+ * buf.slice(); // Truncated data: "ab"
+ */
+static uc_value_t *
+uc_fmtbuf_length(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+ uc_value_t *new_len = uc_fn_arg(0);
+
+ if (!buffer)
+ return NULL;
+
+ if (new_len) {
+ size_t len;
+
+ if (!ucv_as_size_t(vm, new_len, &len))
+ return NULL;
+
+ if (len > buffer->length) {
+ if (!grow_buffer(vm, &buffer->resource.data, &buffer->capacity, len))
+ return NULL;
+
+ buffer->length = len;
+ }
+ else if (len < buffer->length) {
+ memset((char *)buffer->resource.data + sizeof(uc_string_t) + len,
+ 0, buffer->length - len);
+
+ buffer->length = len;
+
+ if (len < buffer->position)
+ buffer->position = len;
+ }
+
+ return ucv_get(&buffer->resource.header);
+ }
+
+ return ucv_uint64_new(buffer->length);
+}
+
+/**
+ * Set the buffer position to the start (0).
+ *
+ * @function module:struct.buffer#start
+ *
+ * @returns {module:struct.buffer}
+ * The buffer instance.
+ *
+ * @example
+ * buf.start();
+ */
+static uc_value_t *
+uc_fmtbuf_start(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+
+ if (!buffer)
+ return NULL;
+
+ buffer->position = 0;
+
+ return ucv_get(&buffer->resource.header);
+}
+
+/**
+ * Set the buffer position to the end.
+ *
+ * @function module:struct.buffer#end
+ *
+ * @returns {module:struct.buffer}
+ * The buffer instance.
+ *
+ * @example
+ * buf.end();
+ */
+static uc_value_t *
+uc_fmtbuf_end(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+
+ if (!buffer)
+ return NULL;
+
+ buffer->position = buffer->length;
+
+ return ucv_get(&buffer->resource.header);
+}
+
+/**
+ * Pack data into the buffer at the current position.
+ *
+ * The `put()` function packs the given values into the buffer according to
+ * the specified format string, starting at the current buffer position.
+ * The format string follows the same syntax as used in `struct.pack()`.
+ *
+ * For a detailed explanation of the format string syntax, refer to the
+ * ["Format Strings" section]{@link module:struct} in the module
+ * documentation.
+ *
+ * @function module:struct.buffer#put
+ *
+ * @param {string} format
+ * The format string specifying how to pack the data.
+ *
+ * @param {...*} values
+ * The values to pack into the buffer.
+ *
+ * @returns {module:struct.buffer}
+ * The buffer instance.
+ *
+ * @see {@link module:struct#pack|struct.pack()}
+ *
+ * @example
+ * buf.put('II', 1234, 5678);
+ */
+static uc_value_t *
+uc_fmtbuf_put(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+ uc_value_t *fmt = uc_fn_arg(0);
+ formatstate_t *state;
+ bool res;
+
+ if (!buffer)
+ return NULL;
+
+ state = parse_format(vm, fmt);
+
+ if (!state)
+ return NULL;
+
+ res = uc_pack_common(vm, nargs, state, 1,
+ &buffer->resource.data, &buffer->position, &buffer->capacity);
+
+ free(state);
+
+ if (!res)
+ return NULL;
+
+ if (buffer->position > buffer->length)
+ buffer->length = buffer->position;
+
+ return ucv_get(&buffer->resource.header);
+}
+
+static uc_value_t *
+fmtbuf_get_common(uc_vm_t *vm, size_t nargs, bool single)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+ uc_value_t *fmt = uc_fn_arg(0);
+ formatstate_t *state;
+ uc_value_t *result;
+ size_t rem;
+ char *buf;
+
+ if (!buffer)
+ return NULL;
+
+ if (single && ucv_type(fmt) == UC_INTEGER) {
+ int64_t len = ucv_int64_get(fmt);
+
+ if (errno != 0)
+ goto ebounds;
+
+ size_t spos, epos;
+
+ if (len < 0) {
+ if (len == INT64_MIN)
+ goto ebounds;
+
+ if ((uint64_t)-len > buffer->position)
+ return NULL;
+
+ spos = buffer->position + len;
+ epos = buffer->position;
+ }
+ else {
+ if ((uint64_t)len > (SIZE_MAX - buffer->position))
+ goto ebounds;
+
+ if (buffer->position + len > buffer->length)
+ return NULL;
+
+ spos = buffer->position;
+ epos = buffer->position + len;
+
+ buffer->position = epos;
+ }
+
+ return ucv_string_new_length(
+ (char *)buffer->resource.data + sizeof(uc_string_t) + spos,
+ epos - spos);
+
+ebounds:
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
+ "Length value out of bounds");
+
+ return NULL;
+ }
+
+ state = parse_format(vm, fmt);
+
+ if (!state)
+ return NULL;
+
+ if (single && (state->ncodes != 1 || state->codes[0].repeat != 1)) {
+ free(state);
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE,
+ "get() expects a format string for a single value. "
+ "Use read() for multiple values.");
+
+ return NULL;
+ }
+
+ rem = buffer->length;
+ buf = (char *)buffer->resource.data + sizeof(uc_string_t);
+
+ result = uc_unpack_common(vm, nargs, state,
+ buf, buffer->position, &rem, single);
+
+ if (result)
+ buffer->position = buffer->length - rem;
+
+ free(state);
+
+ return result;
+}
+
+/**
+ * Unpack a single value from the buffer at the current position.
+ *
+ * The `get()` function unpacks a single value from the buffer according to the
+ * specified format string, starting at the current buffer position.
+ * The format string follows the same syntax as used in `struct.unpack()`.
+ *
+ * For a detailed explanation of the format string syntax, refer to the
+ * ["Format Strings" section]{@link module:struct} in the module documentation.
+ *
+ * Alternatively, `get()` accepts a postive or negative integer as format, which
+ * specifies the length of a string to unpack before or after the current
+ * position. Negative values extract that many bytes before the current offset
+ * while postive ones extracts that many bytes after.
+ *
+ * @function module:struct.buffer#get
+ *
+ * @param {string|number} format
+ * The format string specifying how to unpack the data.
+ *
+ * @returns {*}
+ * The unpacked value.
+ *
+ * @see {@link module:struct#unpack|struct.unpack()}
+ *
+ * @example
+ * const val = buf.get('I');
+ * const str = buf.get(5); // equivalent to buf.get('5s')
+ * const str = buf.get(-3); // equivalent to buf.pos(buf.pos() - 3).get('3s')
+ */
+static uc_value_t *
+uc_fmtbuf_get(uc_vm_t *vm, size_t nargs)
+{
+ return fmtbuf_get_common(vm, nargs, true);
+}
+
+/**
+ * Unpack multiple values from the buffer at the current position.
+ *
+ * The `read()` function unpacks multiple values from the buffer according to
+ * the specified format string, starting at the current buffer position.
+ * The format string follows the same syntax as used in `struct.unpack()`.
+ *
+ * For a detailed explanation of the format string syntax, refer to the
+ * ["Format Strings" section]{@link module:struct} in the module documentation.
+ *
+ * @function module:struct.buffer#get
+ *
+ * @param {string} format
+ * The format string specifying how to unpack the data.
+ *
+ * @returns {array}
+ * An array containing the unpacked values.
+ *
+ * @see {@link module:struct#unpack|struct.unpack()}
+ *
+ * @example
+ * const values = buf.get('II');
+ */
+static uc_value_t *
+uc_fmtbuf_read(uc_vm_t *vm, size_t nargs)
+{
+ return fmtbuf_get_common(vm, nargs, false);
+}
+
+/**
+ * Extract a slice of the buffer content.
+ *
+ * The `slice()` function returns a substring of the buffer content
+ * between the specified start and end positions.
+ *
+ * Both the start and end position values may be negative, in which case they're
+ * relative to the end of the buffer, e.g. `slice(-3)` will extract the last
+ * three bytes of data.
+ *
+ * @function module:struct.buffer#slice
+ *
+ * @param {number} [start=0]
+ * The starting position of the slice.
+ *
+ * @param {number} [end=buffer.length()]
+ * The ending position of the slice (exclusive).
+ *
+ * @returns {string}
+ * A string containing the specified slice of the buffer content.
+ *
+ * @example
+ * const slice = buf.slice(4, 8);
+ */
+static uc_value_t *
+uc_fmtbuf_slice(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+ uc_value_t *from = uc_fn_arg(0);
+ uc_value_t *to = uc_fn_arg(1);
+ long long spos, epos;
+ char *buf;
+
+ if (!buffer)
+ return NULL;
+
+ spos = 0;
+ epos = buffer->length;
+
+ if (from && !ucv_as_longlong(vm, from, &spos))
+ return NULL;
+
+ if (to && !ucv_as_longlong(vm, to, &epos))
+ return NULL;
+
+ if (spos < 0) spos += buffer->length;
+ if (spos < 0) spos = 0;
+ if ((unsigned long long)spos > buffer->length) spos = buffer->length;
+
+ if (epos < 0) epos += buffer->length;
+ if (epos < spos) epos = spos;
+ if ((unsigned long long)epos > buffer->length) epos = buffer->length;
+
+ buf = (char *)buffer->resource.data + sizeof(uc_string_t) + spos;
+
+ return ucv_string_new_length(buf, epos - spos);
+}
+
+/**
+ * Set a slice of the buffer content to given byte value.
+ *
+ * The `set()` function overwrites a substring of the buffer content with the
+ * given byte value, similar to the C `memset()` function, between the specified
+ * start and end positions.
+ *
+ * Both the start and end position values may be negative, in which case they're
+ * relative to the end of the buffer, e.g. `set(0, -2)` will overwrite the last
+ * two bytes of data with `\x00`.
+ *
+ * When the start or end positions are beyond the current buffer length, the
+ * buffer is grown accordingly.
+ *
+ * @function module:struct.buffer#set
+ *
+ * @param {number|string} [value=0]
+ * The byte value to use when overwriting buffer contents. When a string is
+ * given, the first character is used as value.
+ *
+ * @param {number} [start=0]
+ * The position to start overwriting from.
+ *
+ * @param {number} [end=buffer.length()]
+ * The position to end overwriting (exclusive).
+ *
+ * @returns {module:struct.buffer}
+ * The buffer instance.
+ *
+ * @example
+ * const buf = struct.buffer("abcde");
+ * buf.set("X", 2, 4).slice(); // Buffer content is now "abXXe"
+ * buf.set().slice(); // Buffer content is now "\x00\x00\x00\x00\x00"
+ */
+static uc_value_t *
+uc_fmtbuf_set(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+ uc_value_t *byte = uc_fn_arg(0);
+ uc_value_t *from = uc_fn_arg(1);
+ uc_value_t *to = uc_fn_arg(2);
+ long long spos, epos;
+ long bval;
+
+ if (!buffer)
+ return NULL;
+
+ bval = 0;
+ spos = 0;
+ epos = buffer->length;
+
+ if (ucv_type(byte) == UC_STRING)
+ bval = *ucv_string_get(byte);
+ else if (byte && !ucv_as_long(vm, byte, &bval))
+ return NULL;
+
+ if (from && !ucv_as_longlong(vm, from, &spos))
+ return NULL;
+
+ if (to && !ucv_as_longlong(vm, to, &epos))
+ return NULL;
+
+ if (spos < 0) spos += buffer->length;
+ if (spos < 0) spos = 0;
+
+ if (epos < 0) epos += buffer->length;
+
+ if (epos > spos) {
+ if ((unsigned long long)epos > buffer->length) {
+ if (!grow_buffer(vm, &buffer->resource.data, &buffer->capacity, epos))
+ return NULL;
+
+ buffer->length = epos;
+ }
+
+ memset((char *)buffer->resource.data + sizeof(uc_string_t) + spos,
+ bval, epos - spos);
+ }
+
+ return ucv_get(&buffer->resource.header);
+}
+
+/**
+ * Extract and remove all content from the buffer.
+ *
+ * The `pull()` function returns all content of the buffer as a string
+ * and resets the buffer to an empty state.
+ *
+ * @function module:struct.buffer#pull
+ *
+ * @returns {string}
+ * A string containing all the buffer content.
+ *
+ * @example
+ * const allData = buf.pull();
+ */
+static uc_value_t *
+uc_fmtbuf_pull(uc_vm_t *vm, size_t nargs)
+{
+ formatbuffer_t *buffer = formatbuffer_ctx(vm);
+ uc_string_t *us;
+
+ if (!buffer)
+ return NULL;
+
+ if (!buffer->resource.data)
+ return ucv_string_new_length("", 0);
+
+ us = buffer->resource.data;
+ us->header.type = UC_STRING;
+ us->header.refcount = 1;
+ us->length = buffer->length;
+
+ buffer->resource.data = NULL;
+ buffer->capacity = 0;
+ buffer->position = 0;
+ buffer->length = 0;
+
+ return &us->header;
}
@@ -2937,10 +3667,24 @@ static const uc_function_list_t struct_inst_fns[] = {
{ "unpack", uc_struct_unpack }
};
+static const uc_function_list_t buffer_inst_fns[] = {
+ { "pos", uc_fmtbuf_pos },
+ { "length", uc_fmtbuf_length },
+ { "start", uc_fmtbuf_start },
+ { "end", uc_fmtbuf_end },
+ { "set", uc_fmtbuf_set },
+ { "put", uc_fmtbuf_put },
+ { "get", uc_fmtbuf_get },
+ { "read", uc_fmtbuf_read },
+ { "slice", uc_fmtbuf_slice },
+ { "pull", uc_fmtbuf_pull },
+};
+
static const uc_function_list_t struct_fns[] = {
{ "pack", uc_pack },
{ "unpack", uc_unpack },
- { "new", uc_struct_new }
+ { "new", uc_struct_new },
+ { "buffer", uc_fmtbuf_new }
};
void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
@@ -2949,5 +3693,6 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
uc_function_list_register(scope, struct_fns);
- struct_type = uc_type_declare(vm, "struct", struct_inst_fns, uc_struct_gc);
+ uc_type_declare(vm, "struct.format", struct_inst_fns, free);
+ uc_type_declare(vm, "struct.buffer", buffer_inst_fns, free);
}
diff --git a/lib/ubus.c b/lib/ubus.c
index 36a5674..299b112 100644
--- a/lib/ubus.c
+++ b/lib/ubus.c
@@ -15,6 +15,7 @@
*/
#include <unistd.h>
+#include <limits.h>
#include <libubus.h>
#include <libubox/blobmsg.h>
@@ -22,6 +23,11 @@
#define ok_return(expr) do { set_error(0, NULL); return (expr); } while(0)
#define err_return(err, ...) do { set_error(err, __VA_ARGS__); return NULL; } while(0)
+#define errval_return(err, ...) do { set_error(err, __VA_ARGS__); return err; } while(0)
+
+#define REQUIRED 0
+#define OPTIONAL 1
+#define NAMED 2
static struct {
enum ubus_msg_status code;
@@ -62,14 +68,21 @@ _arg_type(uc_type_t type)
}
static bool
-_args_get(uc_vm_t *vm, size_t nargs, ...)
+_args_get(uc_vm_t *vm, bool named, size_t nargs, ...)
{
- uc_value_t **ptr, *arg;
+ uc_value_t **ptr, *arg, *obj = NULL;
uc_type_t type, t;
const char *name;
size_t index = 0;
va_list ap;
- bool opt;
+ int opt;
+
+ if (named) {
+ obj = uc_fn_arg(0);
+
+ if (nargs != 1 || ucv_type(obj) != UC_OBJECT)
+ named = false;
+ }
va_start(ap, nargs);
@@ -79,13 +92,18 @@ _args_get(uc_vm_t *vm, size_t nargs, ...)
if (!name)
break;
- arg = uc_fn_arg(index++);
-
type = va_arg(ap, uc_type_t);
opt = va_arg(ap, int);
ptr = va_arg(ap, uc_value_t **);
- if (!opt && !arg)
+ if (named)
+ arg = ucv_object_get(obj, name, NULL);
+ else if (opt != NAMED)
+ arg = uc_fn_arg(index++);
+ else
+ arg = NULL;
+
+ if (opt == REQUIRED && !arg)
err_return(UBUS_STATUS_INVALID_ARGUMENT, "Argument %s is required", name);
t = ucv_type(arg);
@@ -93,7 +111,7 @@ _args_get(uc_vm_t *vm, size_t nargs, ...)
if (t == UC_CFUNCTION)
t = UC_CLOSURE;
- if (arg && t != type)
+ if (arg && type && t != type)
err_return(UBUS_STATUS_INVALID_ARGUMENT, "Argument %s is not %s", name, _arg_type(type));
*ptr = arg;
@@ -104,7 +122,8 @@ _args_get(uc_vm_t *vm, size_t nargs, ...)
ok_return(true);
}
-#define args_get(vm, nargs, ...) do { if (!_args_get(vm, nargs, __VA_ARGS__, NULL)) return NULL; } while(0)
+#define args_get_named(vm, nargs, ...) do { if (!_args_get(vm, true, nargs, __VA_ARGS__, NULL)) return NULL; } while(0)
+#define args_get(vm, nargs, ...) do { if (!_args_get(vm, false, nargs, __VA_ARGS__, NULL)) return NULL; } while(0)
static uc_resource_type_t *subscriber_type;
static uc_resource_type_t *listener_type;
@@ -113,9 +132,7 @@ static uc_resource_type_t *notify_type;
static uc_resource_type_t *object_type;
static uc_resource_type_t *defer_type;
static uc_resource_type_t *conn_type;
-
-static uint64_t n_cb_active;
-static bool have_own_uloop;
+static uc_resource_type_t *chan_type;
static struct blob_buf buf;
@@ -123,6 +140,9 @@ typedef struct {
struct ubus_context ctx;
struct blob_buf buf;
int timeout;
+
+ uc_vm_t *vm;
+ int registry_index;
} uc_ubus_connection_t;
typedef struct {
@@ -275,14 +295,24 @@ _uc_reg_clear(uc_vm_t *vm, const char *key, size_t idx, size_t nptrs)
}
-#define request_reg_add(vm, request, cb, conn) \
- _uc_reg_add(vm, "ubus.requests", 3, request, cb, conn)
+#define connection_reg_add(vm, conn, cb, disconnect_cb, fd) \
+ _uc_reg_add(vm, "ubus.connections", 4, conn, cb, disconnect_cb, fd)
+
+#define connection_reg_get(vm, idx, conn, cb, disconnect_cb) \
+ _uc_reg_get(vm, "ubus.connections", idx, 3, conn, cb, disconnect_cb)
-#define request_reg_get(vm, idx, request, cb) \
- _uc_reg_get(vm, "ubus.requests", idx, 2, request, cb)
+#define connection_reg_clear(vm, idx) \
+ _uc_reg_clear(vm, "ubus.connections", idx, 4)
+
+
+#define request_reg_add(vm, request, cb, datacb, fdcb, conn, fd) \
+ _uc_reg_add(vm, "ubus.requests", 6, request, cb, datacb, fdcb, conn, fd)
+
+#define request_reg_get(vm, idx, request, cb, datacb, fdcb) \
+ _uc_reg_get(vm, "ubus.requests", idx, 4, request, cb, datacb, fdcb)
#define request_reg_clear(vm, idx) \
- _uc_reg_clear(vm, "ubus.requests", idx, 3)
+ _uc_reg_clear(vm, "ubus.requests", idx, 6)
#define object_reg_add(vm, obj, msg, cb) \
@@ -495,6 +525,7 @@ uc_ubus_connect(uc_vm_t *vm, size_t nargs)
"timeout", UC_INTEGER, true, &timeout);
c = xalloc(sizeof(*c));
+ c->registry_index = -1;
c->timeout = timeout ? ucv_int64_get(timeout) : 30;
if (ubus_connect_ctx(&c->ctx, socket ? ucv_string_get(socket) : NULL)) {
@@ -539,6 +570,8 @@ _conn_get(uc_vm_t *vm, uc_ubus_connection_t **conn)
uc_ubus_connection_t *c = uc_fn_thisval("ubus.connection");
if (!c)
+ c = uc_fn_thisval("ubus.channel");
+ if (!c)
err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid connection context");
if (c->ctx.sock.fd < 0)
@@ -602,7 +635,7 @@ uc_ubus_call_user_cb(uc_ubus_deferred_t *defer, int ret, uc_value_t *reply)
{
uc_value_t *this, *func;
- request_reg_get(defer->vm, defer->registry_index, &this, &func);
+ request_reg_get(defer->vm, defer->registry_index, &this, &func, NULL, NULL);
if (ucv_is_callable(func)) {
uc_vm_stack_push(defer->vm, ucv_get(this));
@@ -615,11 +648,6 @@ uc_ubus_call_user_cb(uc_ubus_deferred_t *defer, int ret, uc_value_t *reply)
}
request_reg_clear(defer->vm, defer->registry_index);
-
- n_cb_active--;
-
- if (have_own_uloop && n_cb_active == 0)
- uloop_end();
}
static void
@@ -632,6 +660,51 @@ uc_ubus_call_data_cb(struct ubus_request *req, int type, struct blob_attr *msg)
}
static void
+uc_ubus_call_data_user_cb(struct ubus_request *req, int type, struct blob_attr *msg)
+{
+ uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request);
+ uc_value_t *this, *func, *reply;
+
+ request_reg_get(defer->vm, defer->registry_index, &this, NULL, &func, NULL);
+
+ if (ucv_is_callable(func)) {
+ reply = blob_array_to_ucv(defer->vm, blob_data(msg), blob_len(msg), true);
+
+ uc_vm_stack_push(defer->vm, ucv_get(this));
+ uc_vm_stack_push(defer->vm, ucv_get(func));
+ uc_vm_stack_push(defer->vm, ucv_get(reply));
+
+ if (uc_vm_call(defer->vm, true, 1) == EXCEPTION_NONE)
+ ucv_put(uc_vm_stack_pop(defer->vm));
+ else
+ uloop_end();
+ }
+}
+
+static void
+uc_ubus_call_fd_cb(struct ubus_request *req, int fd)
+{
+ uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request);
+ uc_value_t *this, *func;
+
+ if (defer->complete)
+ return;
+
+ request_reg_get(defer->vm, defer->registry_index, &this, NULL, NULL, &func);
+
+ if (ucv_is_callable(func)) {
+ uc_vm_stack_push(defer->vm, ucv_get(this));
+ uc_vm_stack_push(defer->vm, ucv_get(func));
+ uc_vm_stack_push(defer->vm, ucv_int64_new(fd));
+
+ if (uc_vm_call(defer->vm, true, 1) == EXCEPTION_NONE)
+ ucv_put(uc_vm_stack_pop(defer->vm));
+ else
+ uloop_end();
+ }
+}
+
+static void
uc_ubus_call_done_cb(struct ubus_request *req, int ret)
{
uc_ubus_deferred_t *defer = container_of(req, uc_ubus_deferred_t, request);
@@ -659,117 +732,248 @@ uc_ubus_call_timeout_cb(struct uloop_timeout *timeout)
uc_ubus_call_user_cb(defer, UBUS_STATUS_TIMEOUT, NULL);
}
-static bool
-uc_ubus_have_uloop(void)
+static int
+get_fd(uc_vm_t *vm, uc_value_t *val)
{
- bool prev = uloop_cancelled;
- bool active;
+ uc_value_t *fn;
+ int64_t n;
-#ifdef HAVE_ULOOP_FD_SET_CB
- if (uloop_fd_set_cb)
- return true;
-#endif
+ fn = ucv_property_get(val, "fileno");
+
+ if (ucv_is_callable(fn)) {
+ uc_vm_stack_push(vm, ucv_get(val));
+ uc_vm_stack_push(vm, ucv_get(fn));
+
+ if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE)
+ return -1;
+
+ val = uc_vm_stack_pop(vm);
+ n = ucv_int64_get(val);
+ ucv_put(val);
+ }
+ else {
+ n = ucv_int64_get(val);
+ }
- uloop_cancelled = true;
- active = uloop_cancelling();
- uloop_cancelled = prev;
+ if (errno || n < 0 || n > (int64_t)INT_MAX)
+ return -1;
- return active;
+ return (int)n;
}
-static uc_value_t *
-uc_ubus_call(uc_vm_t *vm, size_t nargs)
+static int
+uc_ubus_call_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
+ uint32_t id, uc_value_t *funname, uc_value_t *funargs,
+ uc_value_t *fd, uc_value_t *fdcb, uc_value_t *mret)
{
- uc_value_t *objname, *funname, *funargs, *mret = NULL;
- uc_ubus_call_res_t res = { 0 };
- uc_ubus_connection_t *c;
+ uc_ubus_deferred_t defer = {};
enum ubus_msg_status rv;
- uint32_t id;
+ int fd_val = -1;
+
+ enum {
+ RET_MODE_SINGLE,
+ RET_MODE_MULTIPLE,
+ RET_MODE_IGNORE,
+ } ret_mode = RET_MODE_SINGLE;
+
+ const char * const ret_modes[] = {
+ [RET_MODE_SINGLE] = "single",
+ [RET_MODE_MULTIPLE] = "multiple",
+ [RET_MODE_IGNORE] = "ignore",
+ };
+
+ if (ucv_type(mret) == UC_STRING) {
+ const char *str = ucv_string_get(mret);
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(ret_modes); i++)
+ if (!strcmp(str, ret_modes[i]))
+ break;
- conn_get(vm, &c);
+ if (i == ARRAY_SIZE(ret_modes))
+ errval_return(UBUS_STATUS_INVALID_ARGUMENT,
+ "Invalid return mode argument");
- args_get(vm, nargs,
- "object name", UC_STRING, false, &objname,
- "function name", UC_STRING, false, &funname,
- "function arguments", UC_OBJECT, true, &funargs,
- "multiple return", UC_BOOLEAN, true, &mret);
+ ret_mode = i;
+ }
+ else if (ucv_type(mret) == UC_BOOLEAN) {
+ ret_mode = ucv_boolean_get(mret);
+ }
+ else if (ret_mode) {
+ errval_return(UBUS_STATUS_INVALID_ARGUMENT,
+ "Invalid return mode argument");
+ }
blob_buf_init(&c->buf, 0);
if (funargs)
ucv_object_to_blob(funargs, &c->buf);
- rv = ubus_lookup_id(&c->ctx, ucv_string_get(objname), &id);
+ if (fd) {
+ fd_val = get_fd(vm, fd);
- if (rv != UBUS_STATUS_OK)
- err_return(rv, "Failed to resolve object name '%s'",
- ucv_string_get(objname));
+ if (fd_val < 0)
+ errval_return(UBUS_STATUS_INVALID_ARGUMENT,
+ "Invalid file descriptor argument");
+ }
- res.mret = ucv_is_truish(mret);
+ res->mret = (ret_mode == RET_MODE_MULTIPLE);
- rv = ubus_invoke(&c->ctx, id, ucv_string_get(funname), c->buf.head,
- uc_ubus_call_cb, &res, c->timeout * 1000);
+ rv = ubus_invoke_async_fd(&c->ctx, id, ucv_string_get(funname),
+ c->buf.head, &defer.request, fd_val);
- if (rv != UBUS_STATUS_OK)
- err_return(rv, "Failed to invoke function '%s' on object '%s'",
- ucv_string_get(funname), ucv_string_get(objname));
+ defer.vm = vm;
+ defer.ctx = &c->ctx;
+ defer.request.data_cb = uc_ubus_call_cb;
+ defer.request.priv = res;
- ok_return(res.res);
+ if (ucv_is_callable(fdcb)) {
+ defer.request.fd_cb = uc_ubus_call_fd_cb;
+ defer.registry_index = request_reg_add(vm, NULL, NULL, NULL, ucv_get(fdcb), NULL, NULL);
+ }
+
+ if (rv == UBUS_STATUS_OK) {
+ if (ret_mode == RET_MODE_IGNORE)
+ ubus_abort_request(&c->ctx, &defer.request);
+ else
+ rv = ubus_complete_request(&c->ctx, &defer.request, c->timeout * 1000);
+ }
+
+ if (defer.request.fd_cb)
+ request_reg_clear(vm, defer.registry_index);
+
+ return rv;
}
static uc_value_t *
-uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+uc_ubus_call(uc_vm_t *vm, size_t nargs)
{
- uc_value_t *objname, *funname, *funargs, *replycb, *conn, *res = NULL;
- uc_ubus_deferred_t *defer;
+ uc_value_t *obj, *funname, *funargs, *fd, *fdcb, *mret = NULL;
+ uc_ubus_call_res_t res = { 0 };
uc_ubus_connection_t *c;
enum ubus_msg_status rv;
uint32_t id;
+ args_get_named(vm, nargs,
+ "object", 0, REQUIRED, &obj,
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "return", 0, OPTIONAL, &mret,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
conn_get(vm, &c);
- args_get(vm, nargs,
- "object name", UC_STRING, false, &objname,
- "function name", UC_STRING, false, &funname,
- "function arguments", UC_OBJECT, true, &funargs,
- "reply callback", UC_CLOSURE, true, &replycb);
+ if (ucv_type(obj) == UC_INTEGER) {
+ id = ucv_int64_get(obj);
+ }
+ else if (ucv_type(obj) == UC_STRING) {
+ rv = ubus_lookup_id(&c->ctx, ucv_string_get(obj), &id);
+
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to resolve object name '%s'",
+ ucv_string_get(obj));
+ }
+ else {
+ err_return(UBUS_STATUS_INVALID_ARGUMENT,
+ "Argument object is not string or integer");
+ }
+
+ rv = uc_ubus_call_common(vm, c, &res, id, funname, funargs, fd, fdcb, mret);
+
+ if (rv != UBUS_STATUS_OK) {
+ if (ucv_type(obj) == UC_STRING)
+ err_return(rv, "Failed to invoke function '%s' on object '%s'",
+ ucv_string_get(funname), ucv_string_get(obj));
+ else
+ err_return(rv, "Failed to invoke function '%s' on system object %d",
+ ucv_string_get(funname), (int)ucv_int64_get(obj));
+ }
+
+ ok_return(res.res);
+}
+
+static uc_value_t *
+uc_ubus_chan_request(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *funname, *funargs, *fd, *fdcb, *mret = NULL;
+ uc_ubus_call_res_t res = { 0 };
+ uc_ubus_connection_t *c;
+ enum ubus_msg_status rv;
+
+ args_get_named(vm, nargs,
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "return", 0, OPTIONAL, &mret,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
+ conn_get(vm, &c);
+
+ rv = uc_ubus_call_common(vm, c, &res, 0, funname, funargs, fd, fdcb, mret);
+
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to send request '%s' on channel",
+ ucv_string_get(funname));
+
+ ok_return(res.res);
+}
+
+static int
+uc_ubus_defer_common(uc_vm_t *vm, uc_ubus_connection_t *c, uc_ubus_call_res_t *res,
+ uint32_t id, uc_value_t *funname, uc_value_t *funargs,
+ uc_value_t *fd, uc_value_t *fdcb, uc_value_t *replycb,
+ uc_value_t *datacb)
+{
+ uc_ubus_deferred_t *defer;
+ enum ubus_msg_status rv;
+ uc_callframe_t *frame;
+ uc_value_t *conn;
+ int fd_val = -1;
blob_buf_init(&c->buf, 0);
if (funargs)
ucv_object_to_blob(funargs, &c->buf);
- rv = ubus_lookup_id(&c->ctx, ucv_string_get(objname), &id);
+ if (fd) {
+ fd_val = get_fd(vm, fd);
- if (rv != UBUS_STATUS_OK)
- err_return(rv, "Failed to resolve object name '%s'",
- ucv_string_get(objname));
+ if (fd_val < 0)
+ errval_return(UBUS_STATUS_INVALID_ARGUMENT,
+ "Invalid file descriptor argument");
+ }
defer = xalloc(sizeof(*defer));
- rv = ubus_invoke_async(&c->ctx, id, ucv_string_get(funname),
- c->buf.head, &defer->request);
+ rv = ubus_invoke_async_fd(&c->ctx, id, ucv_string_get(funname),
+ c->buf.head, &defer->request, fd_val);
if (rv == UBUS_STATUS_OK) {
defer->vm = vm;
defer->ctx = &c->ctx;
- defer->request.data_cb = uc_ubus_call_data_cb;
+ if (ucv_is_callable(datacb))
+ defer->request.data_cb = uc_ubus_call_data_user_cb;
+ else
+ defer->request.data_cb = uc_ubus_call_data_cb;
+
+ if (ucv_is_callable(fdcb))
+ defer->request.fd_cb = uc_ubus_call_fd_cb;
+
defer->request.complete_cb = uc_ubus_call_done_cb;
+
ubus_complete_request_async(&c->ctx, &defer->request);
defer->timeout.cb = uc_ubus_call_timeout_cb;
uloop_timeout_set(&defer->timeout, c->timeout * 1000);
- res = uc_resource_new(defer_type, defer);
- conn = uc_vector_last(&vm->callframes)->ctx;
+ res->res = uc_resource_new(defer_type, defer);
+ frame = uc_vector_last(&vm->callframes);
+ conn = frame ? frame->ctx : NULL;
- defer->registry_index = request_reg_add(vm, ucv_get(res), ucv_get(replycb), ucv_get(conn));
-
- if (!uc_ubus_have_uloop()) {
- have_own_uloop = true;
- uloop_run();
- }
+ defer->registry_index = request_reg_add(vm, ucv_get(res->res), ucv_get(replycb), ucv_get(datacb),
+ ucv_get(fdcb), ucv_get(conn), ucv_get(fd));
}
else {
uc_vm_stack_push(vm, ucv_get(replycb));
@@ -783,11 +987,69 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
free(defer);
}
+ return rv;
+}
+
+static uc_value_t *
+uc_ubus_defer(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *objname, *funname, *funargs, *replycb, *datacb, *fd, *fdcb = NULL;
+ uc_ubus_call_res_t res = { 0 };
+ uc_ubus_connection_t *c;
+ uint32_t id;
+ int rv;
+
+ conn_get(vm, &c);
+
+ rv = ubus_lookup_id(&c->ctx, ucv_string_get(objname), &id);
+
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to resolve object name '%s'",
+ ucv_string_get(objname));
+
+ args_get_named(vm, nargs,
+ "object", UC_STRING, REQUIRED, &objname,
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "cb", UC_CLOSURE, OPTIONAL, &replycb,
+ "data_cb", UC_CLOSURE, OPTIONAL, &datacb,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
+ rv = uc_ubus_defer_common(vm, c, &res, id, funname, funargs, fd, fdcb, replycb, datacb);
+
if (rv != UBUS_STATUS_OK)
err_return(rv, "Failed to invoke function '%s' on object '%s'",
ucv_string_get(funname), ucv_string_get(objname));
- ok_return(res);
+ ok_return(res.res);
+}
+
+static uc_value_t *
+uc_ubus_chan_defer(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *funname, *funargs, *replycb, *datacb, *fd, *fdcb = NULL;
+ uc_ubus_call_res_t res = { 0 };
+ uc_ubus_connection_t *c;
+ int rv;
+
+ conn_get(vm, &c);
+
+ args_get_named(vm, nargs,
+ "method", UC_STRING, REQUIRED, &funname,
+ "data", UC_OBJECT, OPTIONAL, &funargs,
+ "cb", UC_CLOSURE, OPTIONAL, &replycb,
+ "data_cb", UC_CLOSURE, OPTIONAL, &datacb,
+ "fd", 0, NAMED, &fd,
+ "fd_cb", UC_CLOSURE, NAMED, &fdcb);
+
+ rv = uc_ubus_defer_common(vm, c, &res, 0, funname, funargs, fd, fdcb, replycb, datacb);
+
+ if (rv != UBUS_STATUS_OK)
+ err_return(rv, "Failed to invoke function '%s' on channel",
+ ucv_string_get(funname));
+
+ ok_return(res.res);
}
@@ -797,20 +1059,37 @@ uc_ubus_defer(uc_vm_t *vm, size_t nargs)
*/
static void
-uc_ubus_request_finish(uc_ubus_request_t *callctx, int code, uc_value_t *reply)
+uc_ubus_request_finish_common(uc_ubus_request_t *callctx, int code)
{
- if (callctx->replied)
- return;
+ int fd;
- if (reply) {
- blob_buf_init(&buf, 0);
- ucv_object_to_blob(reply, &buf);
- ubus_send_reply(callctx->ctx, &callctx->req, buf.head);
- }
+ fd = ubus_request_get_caller_fd(&callctx->req);
- callctx->replied = true;
+ if (fd >= 0)
+ close(fd);
+ callctx->replied = true;
ubus_complete_deferred_request(callctx->ctx, &callctx->req, code);
+}
+
+static void
+uc_ubus_request_send_reply(uc_ubus_request_t *callctx, uc_value_t *reply)
+{
+ if (!reply)
+ return;
+
+ blob_buf_init(&buf, 0);
+ ucv_object_to_blob(reply, &buf);
+ ubus_send_reply(callctx->ctx, &callctx->req, buf.head);
+}
+
+static void
+uc_ubus_request_finish(uc_ubus_request_t *callctx, int code)
+{
+ if (callctx->replied)
+ return;
+
+ uc_ubus_request_finish_common(callctx, code);
request_reg_clear(callctx->vm, callctx->registry_index);
}
@@ -819,7 +1098,7 @@ uc_ubus_request_timeout(struct uloop_timeout *timeout)
{
uc_ubus_request_t *callctx = container_of(timeout, uc_ubus_request_t, timeout);
- uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL);
+ uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT);
}
static uc_value_t *
@@ -828,6 +1107,7 @@ uc_ubus_request_reply(uc_vm_t *vm, size_t nargs)
uc_ubus_request_t **callctx = uc_fn_this("ubus.request");
int64_t code = UBUS_STATUS_OK;
uc_value_t *reply, *rcode;
+ bool more = false;
if (!callctx || !*callctx)
err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid call context");
@@ -842,11 +1122,17 @@ uc_ubus_request_reply(uc_vm_t *vm, size_t nargs)
if (rcode) {
code = ucv_int64_get(rcode);
- if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST)
+ if (errno == ERANGE || code < -1 || code > __UBUS_STATUS_LAST)
code = UBUS_STATUS_UNKNOWN_ERROR;
+
+ if (code < 0)
+ more = true;
}
- uc_ubus_request_finish(*callctx, code, reply);
+ uc_ubus_request_send_reply(*callctx, reply);
+
+ if (!more)
+ uc_ubus_request_finish(*callctx, code);
ok_return(ucv_boolean_new(true));
}
@@ -864,6 +1150,36 @@ uc_ubus_request_defer(uc_vm_t *vm, size_t nargs)
}
static uc_value_t *
+uc_ubus_request_get_fd(uc_vm_t *vm, size_t nargs)
+{
+ uc_ubus_request_t *callctx = uc_fn_thisval("ubus.request");
+
+ if (!callctx)
+ return NULL;
+
+ return ucv_int64_new(ubus_request_get_caller_fd(&callctx->req));
+}
+
+static uc_value_t *
+uc_ubus_request_set_fd(uc_vm_t *vm, size_t nargs)
+{
+ uc_ubus_request_t *callctx = uc_fn_thisval("ubus.request");
+ int fd;
+
+ if (!callctx)
+ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid call context");
+
+ fd = get_fd(vm, uc_fn_arg(0));
+
+ if (fd < 0)
+ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid file descriptor");
+
+ ubus_request_set_fd(callctx->ctx, &callctx->req, fd);
+
+ return ucv_boolean_new(true);
+}
+
+static uc_value_t *
uc_ubus_request_error(uc_vm_t *vm, size_t nargs)
{
uc_ubus_request_t **callctx = uc_fn_this("ubus.request");
@@ -884,7 +1200,7 @@ uc_ubus_request_error(uc_vm_t *vm, size_t nargs)
if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST)
code = UBUS_STATUS_UNKNOWN_ERROR;
- uc_ubus_request_finish(*callctx, code, NULL);
+ uc_ubus_request_finish(*callctx, code);
ok_return(ucv_boolean_new(true));
}
@@ -1003,13 +1319,13 @@ uc_ubus_object_notify(uc_vm_t *vm, size_t nargs)
if (!uuobj || !*uuobj)
err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid object context");
- args_get(vm, nargs,
- "typename", UC_STRING, false, &typename,
- "message", UC_OBJECT, true, &message,
- "data callback", UC_CLOSURE, true, &data_cb,
- "status callback", UC_CLOSURE, true, &status_cb,
- "completion callback", UC_CLOSURE, true, &complete_cb,
- "timeout", UC_INTEGER, true, &timeout);
+ args_get_named(vm, nargs,
+ "type", UC_STRING, REQUIRED, &typename,
+ "data", UC_OBJECT, OPTIONAL, &message,
+ "data_cb", UC_CLOSURE, OPTIONAL, &data_cb,
+ "status_cb", UC_CLOSURE, OPTIONAL, &status_cb,
+ "cb", UC_CLOSURE, OPTIONAL, &complete_cb,
+ "timeout", UC_INTEGER, OPTIONAL, &timeout);
t = timeout ? ucv_int64_get(timeout) : -1;
@@ -1233,6 +1549,9 @@ uc_ubus_handle_reply_common(struct ubus_context *ctx,
ubus_defer_request(ctx, req, &callctx->req);
+ /* fd is copied to deferred request. ensure it does not get closed early */
+ ubus_request_get_caller_fd(req);
+
/* create ucode request type object and set properties */
reqobj = uc_resource_new(request_type, callctx);
@@ -1257,7 +1576,7 @@ uc_ubus_handle_reply_common(struct ubus_context *ctx,
/* Add wrapped request context into registry to prevent GC'ing
* until reply or timeout occurred */
- callctx->registry_index = request_reg_add(vm, ucv_get(reqobj), NULL, NULL);
+ callctx->registry_index = request_reg_add(vm, ucv_get(reqobj), NULL, NULL, NULL, NULL, NULL);
}
/* Otherwise, when the function returned an object, treat it as
@@ -1267,8 +1586,7 @@ uc_ubus_handle_reply_common(struct ubus_context *ctx,
ucv_object_to_blob(res, &buf);
ubus_send_reply(ctx, &callctx->req, buf.head);
- ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_OK);
- callctx->replied = true;
+ uc_ubus_request_finish_common(callctx, UBUS_STATUS_OK);
}
/* If neither a deferred ubus request, nor a plain object were
@@ -1284,8 +1602,7 @@ uc_ubus_handle_reply_common(struct ubus_context *ctx,
rv = UBUS_STATUS_UNKNOWN_ERROR;
}
- ubus_complete_deferred_request(ctx, &callctx->req, rv);
- callctx->replied = true;
+ uc_ubus_request_finish_common(callctx, rv);
}
ucv_put(res);
@@ -1299,15 +1616,13 @@ uc_ubus_handle_reply_common(struct ubus_context *ctx,
if (rv < UBUS_STATUS_OK || rv >= __UBUS_STATUS_LAST)
rv = UBUS_STATUS_UNKNOWN_ERROR;
- ubus_complete_deferred_request(ctx, &callctx->req, rv);
- callctx->replied = true;
+ uc_ubus_request_finish_common(callctx, rv);
break;
/* treat other exceptions as fatal and halt uloop */
default:
- ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_UNKNOWN_ERROR);
+ uc_ubus_request_finish_common(callctx, UBUS_STATUS_UNKNOWN_ERROR);
uloop_end();
- callctx->replied = true;
break;
}
@@ -1963,6 +2278,29 @@ uc_ubus_defer_completed(uc_vm_t *vm, size_t nargs)
}
static uc_value_t *
+uc_ubus_defer_await(uc_vm_t *vm, size_t nargs)
+{
+ uc_ubus_deferred_t *d = uc_fn_thisval("ubus.deferred");
+ int64_t remaining;
+
+ if (!d)
+ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid deferred context");
+
+ if (d->complete)
+ ok_return(ucv_boolean_new(false));
+
+#ifdef HAVE_ULOOP_TIMEOUT_REMAINING64
+ remaining = uloop_timeout_remaining64(&d->timeout);
+#else
+ remaining = uloop_timeout_remaining(&d->timeout);
+#endif
+
+ ubus_complete_request(d->ctx, &d->request, remaining);
+
+ ok_return(ucv_boolean_new(true));
+}
+
+static uc_value_t *
uc_ubus_defer_abort(uc_vm_t *vm, size_t nargs)
{
uc_ubus_deferred_t **d = uc_fn_this("ubus.deferred");
@@ -1978,20 +2316,161 @@ uc_ubus_defer_abort(uc_vm_t *vm, size_t nargs)
request_reg_clear((*d)->vm, (*d)->registry_index);
- n_cb_active--;
-
- if (have_own_uloop && n_cb_active == 0)
- uloop_end();
-
(*d)->complete = true;
ok_return(ucv_boolean_new(true));
}
+/*
+ * channel related methods
+ * --------------------------------------------------------------------------
+ */
+
+#ifdef HAVE_UBUS_CHANNEL_SUPPORT
+static int
+uc_ubus_channel_req_cb(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ uc_ubus_connection_t *c = container_of(ctx, uc_ubus_connection_t, ctx);
+ uc_value_t *this, *func, *args, *reqproto;
+
+ connection_reg_get(c->vm, c->registry_index, &this, &func, NULL);
+
+ if (!ucv_is_callable(func))
+ return UBUS_STATUS_METHOD_NOT_FOUND;
+
+ args = blob_array_to_ucv(c->vm, blob_data(msg), blob_len(msg), true);
+ reqproto = ucv_object_new(c->vm);
+ ucv_object_add(reqproto, "args", ucv_get(args));
+
+ if (method)
+ ucv_object_add(reqproto, "type", ucv_get(ucv_string_new(method)));
+
+ return uc_ubus_handle_reply_common(ctx, req, c->vm, this, func, reqproto);
+}
+
+static void
+uc_ubus_channel_disconnect_cb(struct ubus_context *ctx)
+{
+ uc_ubus_connection_t *c = container_of(ctx, uc_ubus_connection_t, ctx);
+ uc_value_t *this, *func;
+
+ connection_reg_get(c->vm, c->registry_index, &this, NULL, &func);
+
+ if (ucv_is_callable(func)) {
+ uc_vm_stack_push(c->vm, ucv_get(this));
+ uc_vm_stack_push(c->vm, ucv_get(func));
+
+ if (uc_vm_call(c->vm, true, 0) == EXCEPTION_NONE)
+ ucv_put(uc_vm_stack_pop(c->vm));
+ else
+ uloop_end();
+ }
+
+ blob_buf_free(&c->buf);
+
+ if (c->ctx.sock.fd >= 0) {
+ ubus_shutdown(&c->ctx);
+ c->ctx.sock.fd = -1;
+ }
+
+ if (c->registry_index >= 0)
+ connection_reg_clear(c->vm, c->registry_index);
+}
+
+static uc_value_t *
+uc_ubus_channel_add(uc_vm_t *vm, uc_ubus_connection_t *c, uc_value_t *cb,
+ uc_value_t *disconnect_cb, uc_value_t *fd)
+{
+ uc_value_t *chan;
+
+ c->vm = vm;
+
+ if (c->timeout < 0)
+ c->timeout = 30;
+
+ chan = uc_resource_new(chan_type, c);
+ c->registry_index = connection_reg_add(vm, ucv_get(chan), ucv_get(cb), ucv_get(disconnect_cb), ucv_get(fd));
+ c->ctx.connection_lost = uc_ubus_channel_disconnect_cb;
+ ubus_add_uloop(&c->ctx);
+
+ ok_return(chan);
+}
+#endif
+
+static uc_value_t *
+uc_ubus_request_new_channel(uc_vm_t *vm, size_t nargs)
+{
+#ifdef HAVE_UBUS_CHANNEL_SUPPORT
+ uc_ubus_request_t *callctx = uc_fn_thisval("ubus.request");
+ uc_value_t *cb, *disconnect_cb, *timeout;
+ uc_ubus_connection_t *c;
+ int fd;
+
+ if (!callctx)
+ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid call context");
+
+ args_get(vm, nargs,
+ "cb", UC_CLOSURE, true, &cb,
+ "disconnect_cb", UC_CLOSURE, true, &disconnect_cb,
+ "timeout", UC_INTEGER, true, &timeout);
+
+ c = xalloc(sizeof(*c));
+ c->timeout = timeout ? ucv_int64_get(timeout) : 30;
+
+ if (ubus_channel_create(&c->ctx, &fd, cb ? uc_ubus_channel_req_cb : NULL)) {
+ free(c);
+ err_return(UBUS_STATUS_UNKNOWN_ERROR, "Unable to create ubus channel");
+ }
+
+ ubus_request_set_fd(callctx->ctx, &callctx->req, fd);
+
+ return uc_ubus_channel_add(vm, c, cb, disconnect_cb, NULL);
+#else
+ err_return(UBUS_STATUS_NOT_SUPPORTED, "No ubus channel support");
+#endif
+}
+
+
+static uc_value_t *
+uc_ubus_channel_connect(uc_vm_t *vm, size_t nargs)
+{
+#ifdef HAVE_UBUS_CHANNEL_SUPPORT
+ uc_value_t *fd, *cb, *disconnect_cb, *timeout;
+ uc_ubus_connection_t *c;
+ int fd_val;
+
+ args_get(vm, nargs,
+ "fd", UC_NULL, false, &fd,
+ "cb", UC_CLOSURE, true, &cb,
+ "disconnect_cb", UC_CLOSURE, true, &disconnect_cb,
+ "timeout", UC_INTEGER, true, &timeout);
+
+ fd_val = get_fd(vm, fd);
+
+ if (fd_val < 0)
+ err_return(UBUS_STATUS_INVALID_ARGUMENT, "Invalid file descriptor argument");
+
+ c = xalloc(sizeof(*c));
+ c->timeout = timeout ? ucv_int64_get(timeout) : 30;
+
+ if (ubus_channel_connect(&c->ctx, fd_val, cb ? uc_ubus_channel_req_cb : NULL)) {
+ free(c);
+ err_return(UBUS_STATUS_UNKNOWN_ERROR, "Unable to create ubus channel");
+ }
+
+ return uc_ubus_channel_add(vm, c, cb, disconnect_cb, fd);
+#else
+ err_return(UBUS_STATUS_NOT_SUPPORTED, "No ubus channel support");
+#endif
+}
+
static const uc_function_list_t global_fns[] = {
{ "error", uc_ubus_error },
{ "connect", uc_ubus_connect },
+ { "open_channel", uc_ubus_channel_connect },
};
static const uc_function_list_t conn_fns[] = {
@@ -2007,7 +2486,15 @@ static const uc_function_list_t conn_fns[] = {
{ "disconnect", uc_ubus_disconnect },
};
+static const uc_function_list_t chan_fns[] = {
+ { "request", uc_ubus_chan_request },
+ { "defer", uc_ubus_chan_defer },
+ { "error", uc_ubus_error },
+ { "disconnect", uc_ubus_disconnect },
+};
+
static const uc_function_list_t defer_fns[] = {
+ { "await", uc_ubus_defer_await },
{ "completed", uc_ubus_defer_completed },
{ "abort", uc_ubus_defer_abort },
};
@@ -2022,6 +2509,9 @@ static const uc_function_list_t request_fns[] = {
{ "reply", uc_ubus_request_reply },
{ "error", uc_ubus_request_error },
{ "defer", uc_ubus_request_defer },
+ { "get_fd", uc_ubus_request_get_fd },
+ { "set_fd", uc_ubus_request_set_fd },
+ { "new_channel", uc_ubus_request_new_channel },
};
static const uc_function_list_t notify_fns[] = {
@@ -2046,6 +2536,8 @@ static void free_connection(void *ud) {
if (conn->ctx.sock.fd >= 0)
ubus_shutdown(&conn->ctx);
+ if (conn->registry_index >= 0)
+ connection_reg_clear(conn->vm, conn->registry_index);
free(conn);
}
@@ -2077,7 +2569,7 @@ static void free_object(void *ud) {
static void free_request(void *ud) {
uc_ubus_request_t *callctx = ud;
- uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL);
+ uc_ubus_request_finish(callctx, UBUS_STATUS_TIMEOUT);
uloop_timeout_cancel(&callctx->timeout);
free(callctx);
}
@@ -2123,7 +2615,14 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
ADD_CONST(STATUS_SYSTEM_ERROR);
#endif
+ /* virtual status code for reply */
+#define UBUS_STATUS_CONTINUE -1
+ ADD_CONST(STATUS_CONTINUE);
+
+ ADD_CONST(SYSTEM_OBJECT_ACL);
+
conn_type = uc_type_declare(vm, "ubus.connection", conn_fns, free_connection);
+ chan_type = uc_type_declare(vm, "ubus.channel", chan_fns, free_connection);
defer_type = uc_type_declare(vm, "ubus.deferred", defer_fns, free_deferred);
object_type = uc_type_declare(vm, "ubus.object", object_fns, free_object);
notify_type = uc_type_declare(vm, "ubus.notify", notify_fns, free_notify);
diff --git a/lib/uci.c b/lib/uci.c
index 69aee40..9431131 100644
--- a/lib/uci.c
+++ b/lib/uci.c
@@ -53,11 +53,15 @@
#include "ucode/module.h"
-#define ok_return(expr) do { last_error = 0; return (expr); } while(0)
-#define err_return(err) do { last_error = err; return NULL; } while(0)
+#define ok_return(expr) do { \
+ uc_vm_registry_delete(vm, "uci.error"); \
+ return (expr); \
+} while(0)
-static int last_error = 0;
-static uc_resource_type_t *cursor_type;
+#define err_return(err) do { \
+ uc_vm_registry_set(vm, "uci.error", ucv_int64_new(err)); \
+ return NULL; \
+} while(0)
enum pkg_cmd {
CMD_SAVE,
@@ -86,6 +90,7 @@ enum pkg_cmd {
static uc_value_t *
uc_uci_error(uc_vm_t *vm, size_t nargs)
{
+ int last_error = ucv_int64_get(uc_vm_registry_get(vm, "uci.error"));
char buf[sizeof("Unknown error: -9223372036854775808")];
uc_value_t *errmsg;
@@ -110,7 +115,7 @@ uc_uci_error(uc_vm_t *vm, size_t nargs)
errmsg = ucv_string_new(buf);
}
- last_error = 0;
+ uc_vm_registry_delete(vm, "uci.error");
return errmsg;
}
@@ -146,6 +151,13 @@ uc_uci_error(uc_vm_t *vm, size_t nargs)
* uncommitted application changes from the uci cli or other processes on the
* system.
*
+ * @param {string} [config2_dir=/var/run/uci]
+ * The directory to keep override config files in. Files are in the same format
+ * as in config_dir, but can individually override ones from that directory.
+ * It defaults to the uci configuration directory `/var/run/uci` but may be
+ * set to a different path for special purpose applications, or even disabled
+ * by setting this parameter to an empty string.
+ *
* @returns {?module:uci.cursor}
*/
static uc_value_t *
@@ -153,11 +165,13 @@ uc_uci_cursor(uc_vm_t *vm, size_t nargs)
{
uc_value_t *cdir = uc_fn_arg(0);
uc_value_t *sdir = uc_fn_arg(1);
+ uc_value_t *c2dir = uc_fn_arg(2);
struct uci_context *c;
int rv;
if ((cdir && ucv_type(cdir) != UC_STRING) ||
- (sdir && ucv_type(sdir) != UC_STRING))
+ (sdir && ucv_type(sdir) != UC_STRING) ||
+ (c2dir && ucv_type(c2dir) != UC_STRING))
err_return(UCI_ERR_INVAL);
c = uci_alloc_context();
@@ -179,7 +193,16 @@ uc_uci_cursor(uc_vm_t *vm, size_t nargs)
err_return(rv);
}
- ok_return(uc_resource_new(cursor_type, c));
+#ifdef HAVE_UCI_CONF2DIR
+ if (c2dir) {
+ rv = uci_set_conf2dir(c, ucv_string_get(c2dir));
+
+ if (rv)
+ err_return(rv);
+ }
+#endif
+
+ ok_return(ucv_resource_create(vm, "uci.cursor", c));
}
@@ -191,7 +214,8 @@ uc_uci_cursor(uc_vm_t *vm, size_t nargs)
*
* Any changes made to configuration values are local to the cursor object and
* held in memory only until they're written out to the filesystem using the
- * `save()` and `commit()` methods.
+ * {@link module:uci.cursor#save|save()} and
+ * {@link module:uci.cursor#commit|commit()} methods.
*
* Changes performed in one cursor instance are not reflected in another, unless
* the first instance writes those changes to the filesystem and the other
@@ -1123,6 +1147,143 @@ uc_uci_delete(uc_vm_t *vm, size_t nargs)
ok_return(ucv_boolean_new(true));
}
+static uc_value_t *
+uc_uci_list_modify(uc_vm_t *vm, size_t nargs,
+ int (*op)(struct uci_context *, struct uci_ptr *))
+{
+ struct uci_context **c = uc_fn_this("uci.cursor");
+ uc_value_t *conf = uc_fn_arg(0);
+ uc_value_t *sect = uc_fn_arg(1);
+ uc_value_t *opt = uc_fn_arg(2);
+ uc_value_t *val = uc_fn_arg(3);
+ struct uci_ptr ptr = { 0 };
+ bool is_list;
+ int rv;
+
+ if (ucv_type(conf) != UC_STRING ||
+ ucv_type(sect) != UC_STRING ||
+ ucv_type(opt) != UC_STRING)
+ err_return(UCI_ERR_INVAL);
+
+ ptr.package = ucv_string_get(conf);
+ ptr.section = ucv_string_get(sect);
+ ptr.option = ucv_string_get(opt);
+
+ rv = lookup_ptr(*c, &ptr, true);
+
+ if (rv != UCI_OK)
+ err_return(rv);
+
+ if (!ptr.s)
+ err_return(UCI_ERR_NOTFOUND);
+
+ if (uval_to_uci(vm, val, &ptr.value, &is_list) && !is_list)
+ rv = op(*c, &ptr);
+ else
+ rv = UCI_ERR_INVAL;
+
+ free((char *)ptr.value);
+
+ if (rv != UCI_OK)
+ err_return(rv);
+
+ ok_return(ucv_boolean_new(true));
+}
+
+/**
+ * Add an item to a list option in given configuration.
+ *
+ * Adds a single value to an existing list option within the specified section
+ * of the given configuration. The configuration is implicitly loaded into the
+ * cursor if not already present.
+ *
+ * The new value is appended to the end of the list, maintaining the existing order.
+ * No attempt is made to check for or remove duplicate values.
+ *
+ * Returns `true` if the item was successfully added to the list.
+ *
+ * Returns `null` on error, e.g. if the targeted option was not found or
+ * if an invalid value was passed.
+ *
+ * @function module:uci.cursor#list_append
+ *
+ * @param {string} config
+ * The name of the configuration file to modify, e.g. `"firewall"` to
+ * modify `/etc/config/firewall`.
+ *
+ * @param {string} section
+ * The section name containing the list option to modify.
+ *
+ * @param {string} option
+ * The list option name to add a value to.
+ *
+ * @param {string|boolean|number} value
+ * The value to add to the list option.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Add '192.168.1.1' to the 'dns' list in the 'lan' interface
+ * ctx.add_list('network', 'lan', 'dns', '192.168.1.1');
+ *
+ * // Add a port to the first redirect section
+ * ctx.add_list('firewall', '@redirect[0]', 'src_dport', '8080');
+ */
+static uc_value_t *
+uc_uci_list_append(uc_vm_t *vm, size_t nargs)
+{
+ return uc_uci_list_modify(vm, nargs, uci_add_list);
+}
+
+/**
+ * Remove an item from a list option in given configuration.
+ *
+ * Removes a single value from an existing list option within the specified section
+ * of the given configuration. The configuration is implicitly loaded into the
+ * cursor if not already present.
+ *
+ * If the specified value appears multiple times in the list, all matching occurrences
+ * will be removed.
+ *
+ * Returns `true` if the item was successfully removed from the list.
+ *
+ * Returns `null` on error, e.g. if the targeted option was not foundor if an
+ * invalid value was passed.
+ *
+ * @function module:uci.cursor#list_remove
+ *
+ * @param {string} config
+ * The name of the configuration file to modify, e.g. `"firewall"` to
+ * modify `/etc/config/firewall`.
+ *
+ * @param {string} section
+ * The section name containing the list option to modify.
+ *
+ * @param {string} option
+ * The list option name to remove a value from.
+ *
+ * @param {string|boolean|number} value
+ * The value to remove from the list option.
+ *
+ * @returns {?boolean}
+ *
+ * @example
+ * const ctx = cursor(…);
+ *
+ * // Remove '8.8.8.8' from the 'dns' list in the 'lan' interface
+ * ctx.delete_list('network', 'lan', 'dns', '8.8.8.8');
+ *
+ * // Remove a port from the first redirect section
+ * ctx.delete_list('firewall', '@redirect[0]', 'src_dport', '8080');
+ */
+static uc_value_t *
+uc_uci_list_remove(uc_vm_t *vm, size_t nargs)
+{
+ return uc_uci_list_modify(vm, nargs, uci_del_list);
+}
+
/**
* Rename an option or section in given configuration.
*
@@ -1835,23 +1996,25 @@ uc_uci_configs(uc_vm_t *vm, size_t nargs)
static const uc_function_list_t cursor_fns[] = {
- { "load", uc_uci_load },
- { "unload", uc_uci_unload },
- { "get", uc_uci_get },
- { "get_all", uc_uci_get_all },
- { "get_first", uc_uci_get_first },
- { "add", uc_uci_add },
- { "set", uc_uci_set },
- { "rename", uc_uci_rename },
- { "save", uc_uci_save },
- { "delete", uc_uci_delete },
- { "commit", uc_uci_commit },
- { "revert", uc_uci_revert },
- { "reorder", uc_uci_reorder },
- { "changes", uc_uci_changes },
- { "foreach", uc_uci_foreach },
- { "configs", uc_uci_configs },
- { "error", uc_uci_error },
+ { "load", uc_uci_load },
+ { "unload", uc_uci_unload },
+ { "get", uc_uci_get },
+ { "get_all", uc_uci_get_all },
+ { "get_first", uc_uci_get_first },
+ { "add", uc_uci_add },
+ { "set", uc_uci_set },
+ { "rename", uc_uci_rename },
+ { "save", uc_uci_save },
+ { "delete", uc_uci_delete },
+ { "list_append", uc_uci_list_append },
+ { "list_remove", uc_uci_list_remove },
+ { "commit", uc_uci_commit },
+ { "revert", uc_uci_revert },
+ { "reorder", uc_uci_reorder },
+ { "changes", uc_uci_changes },
+ { "foreach", uc_uci_foreach },
+ { "configs", uc_uci_configs },
+ { "error", uc_uci_error },
};
static const uc_function_list_t global_fns[] = {
@@ -1868,5 +2031,5 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
uc_function_list_register(scope, global_fns);
- cursor_type = uc_type_declare(vm, "uci.cursor", cursor_fns, close_uci);
+ uc_type_declare(vm, "uci.cursor", cursor_fns, close_uci);
}
diff --git a/lib/zlib.c b/lib/zlib.c
index 8190251..038a352 100644
--- a/lib/zlib.c
+++ b/lib/zlib.c
@@ -17,7 +17,7 @@
/**
* # Zlib bindings
*
- * The `zlib` module provides single-call-oriented functions for interacting with zlib data.
+ * The `zlib` module provides single-call and stream-oriented functions for interacting with zlib data.
*
* @module zlib
*/
@@ -41,6 +41,16 @@
*/
#define CHUNK 16384
+static uc_resource_type_t *zstrmd_type, *zstrmi_type;
+
+static int last_error = 0;
+#define err_return(err) do { last_error = err; return NULL; } while(0)
+
+typedef struct {
+ z_stream strm;
+ uc_stringbuf_t *outbuf;
+ int flush;
+} zstrm_t;
/* zlib init error message */
static const char * ziniterr(int ret)
@@ -68,24 +78,44 @@ static const char * ziniterr(int ret)
return msg;
}
-static uc_stringbuf_t *
-uc_zlib_def_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm)
+static int
+def_chunks(zstrm_t * const zstrm)
+{
+ int ret;
+
+ /* run deflate() on input until output buffer not full */
+ do {
+ printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
+ zstrm->outbuf->bpos -= CHUNK;
+
+ zstrm->strm.avail_out = CHUNK;
+ zstrm->strm.next_out = (unsigned char *)(zstrm->outbuf->buf + zstrm->outbuf->bpos);
+
+ ret = deflate(&zstrm->strm, zstrm->flush);
+ assert(ret != Z_STREAM_ERROR);
+
+ zstrm->outbuf->bpos += CHUNK - zstrm->strm.avail_out;
+ } while (zstrm->strm.avail_out == 0);
+ assert(zstrm->strm.avail_in == 0); // all input will be used
+
+ return ret;
+}
+
+static bool
+uc_zlib_def_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
{
int ret;
bool eof = false;
uc_value_t *rfn, *rbuf;
- uc_stringbuf_t *buf = NULL;
rfn = ucv_property_get(obj, "read");
if (!ucv_is_callable(rfn)) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Input object does not implement read() method");
- return NULL;
+ return false;
}
- buf = ucv_stringbuf_new();
-
do {
rbuf = NULL;
uc_vm_stack_push(vm, ucv_get(obj));
@@ -107,65 +137,33 @@ uc_zlib_def_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm)
/* check EOF */
eof = (rbuf == NULL || ucv_string_length(rbuf) == 0);
- strm->next_in = (unsigned char *)ucv_string_get(rbuf);
- strm->avail_in = ucv_string_length(rbuf);
-
- /* run deflate() on input until output buffer not full */
- do {
- // enlarge buf by CHUNK amount as needed
- printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
- buf->bpos -= CHUNK;
-
- strm->avail_out = CHUNK;
- strm->next_out = (unsigned char *)(buf->buf + buf->bpos);;
-
- ret = deflate(strm, eof ? Z_FINISH : Z_NO_FLUSH); // no bad return value here
- assert(ret != Z_STREAM_ERROR); // state never clobbered (would be mem corruption)
- (void)ret; // XXX make annoying compiler that ignores assert() happy
+ zstrm->strm.next_in = (unsigned char *)ucv_string_get(rbuf);
+ zstrm->strm.avail_in = ucv_string_length(rbuf);
- // update bpos past data written by deflate()
- buf->bpos += CHUNK - strm->avail_out;
- } while (strm->avail_out == 0);
- assert(strm->avail_in == 0); // all input will be used
+ zstrm->flush = eof ? Z_FINISH : Z_NO_FLUSH;
+ ret = def_chunks(zstrm);
+ (void)ret; // XXX make annoying compiler that ignores assert() happy
ucv_put(rbuf); // release rbuf
} while (!eof); // finish compression if all of source has been read in
assert(ret == Z_STREAM_END); // stream will be complete
- return buf;
+ return true;
fail:
ucv_put(rbuf);
- printbuf_free(buf);
- return NULL;
+ return false;
}
-static uc_stringbuf_t *
-uc_zlib_def_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm)
+static bool
+uc_zlib_def_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const zstrm)
{
- int ret;
- uc_stringbuf_t *buf = NULL;
-
- buf = ucv_stringbuf_new();
-
- strm->next_in = (unsigned char *)ucv_string_get(str);
- strm->avail_in = ucv_string_length(str);
-
- do {
- printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
- buf->bpos -= CHUNK;
-
- strm->avail_out = CHUNK;
- strm->next_out = (unsigned char *)(buf->buf + buf->bpos);
-
- ret = deflate(strm, Z_FINISH);
- assert(ret != Z_STREAM_ERROR);
+ zstrm->strm.next_in = (unsigned char *)ucv_string_get(str);
+ zstrm->strm.avail_in = ucv_string_length(str);
- buf->bpos += CHUNK - strm->avail_out;
- } while (ret != Z_STREAM_END);
- assert(strm->avail_in == 0);
+ last_error = def_chunks(zstrm);
- return buf;
+ return true;
}
/**
@@ -185,7 +183,7 @@ uc_zlib_def_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm)
* @function module:zlib#deflate
*
* @param {string} str_or_resource
- * The string or resource object to be parsed as JSON.
+ * The string or resource object to be compressed.
*
* @param {?boolean} [gzip=false]
* Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)
@@ -203,19 +201,22 @@ uc_zlib_def_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm)
* const deflated = deflate(content, Z_BEST_SPEED);
*/
static uc_value_t *
-uc_zlib_deflate(uc_vm_t *vm, size_t nargs)
+uc_zlib_deflate(uc_vm_t * const vm, const size_t nargs)
{
uc_value_t *rv = NULL;
uc_value_t *src = uc_fn_arg(0);
uc_value_t *gzip = uc_fn_arg(1);
uc_value_t *level = uc_fn_arg(2);
- uc_stringbuf_t *buf = NULL;
int ret, lvl = Z_DEFAULT_COMPRESSION;
- bool gz = false;
- z_stream strm = {
- .zalloc = Z_NULL,
- .zfree = Z_NULL,
- .opaque = Z_NULL,
+ bool success, gz = false;
+ zstrm_t zstrm = {
+ .strm = {
+ .zalloc = Z_NULL,
+ .zfree = Z_NULL,
+ .opaque = Z_NULL,
+ },
+ .outbuf = NULL,
+ .flush = Z_FINISH,
};
if (gzip) {
@@ -236,7 +237,7 @@ uc_zlib_deflate(uc_vm_t *vm, size_t nargs)
lvl = (int)ucv_int64_get(level);
}
- ret = deflateInit2(&strm, lvl,
+ ret = deflateInit2(&zstrm.strm, lvl,
Z_DEFLATED, // only allowed method
gz ? 15+16 : 15, // 15 Zlib default, +16 for gzip
8, // default value
@@ -246,15 +247,17 @@ uc_zlib_deflate(uc_vm_t *vm, size_t nargs)
goto out;
}
+ zstrm.outbuf = ucv_stringbuf_new();
+
switch (ucv_type(src)) {
case UC_STRING:
- buf = uc_zlib_def_string(vm, src, &strm);
+ success = uc_zlib_def_string(vm, src, &zstrm);
break;
case UC_RESOURCE:
case UC_OBJECT:
case UC_ARRAY:
- buf = uc_zlib_def_object(vm, src, &strm);
+ success = uc_zlib_def_object(vm, src, &zstrm);
break;
default:
@@ -263,37 +266,63 @@ uc_zlib_deflate(uc_vm_t *vm, size_t nargs)
goto out;
}
- if (!buf) {
+ if (!success) {
if (vm->exception.type == EXCEPTION_NONE) // do not clobber previous exception
- uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", strm.msg);
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", zstrm.strm.msg);
+ printbuf_free(zstrm.outbuf);
goto out;
}
- rv = ucv_stringbuf_finish(buf);
+ rv = ucv_stringbuf_finish(zstrm.outbuf);
out:
- (void)deflateEnd(&strm);
+ (void)deflateEnd(&zstrm.strm);
return rv;
}
-static uc_stringbuf_t *
-uc_zlib_inf_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm)
+static int
+inf_chunks(zstrm_t * const zstrm)
+{
+ int ret;
+
+ /* run inflate() on input until output buffer not full */
+ do {
+ printbuf_memset(zstrm->outbuf, printbuf_length(zstrm->outbuf) + CHUNK - 1, 0, 1);
+ zstrm->outbuf->bpos -= CHUNK;
+
+ zstrm->strm.avail_out = CHUNK;
+ zstrm->strm.next_out = (unsigned char *)(zstrm->outbuf->buf + zstrm->outbuf->bpos);
+
+ ret = inflate(&zstrm->strm, zstrm->flush);
+ assert(ret != Z_STREAM_ERROR);
+ switch (ret) {
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ return ret;
+ }
+
+ zstrm->outbuf->bpos += CHUNK - zstrm->strm.avail_out;
+ } while (zstrm->strm.avail_out == 0);
+
+ return ret;
+}
+
+static bool
+uc_zlib_inf_object(uc_vm_t *const vm, uc_value_t * const obj, zstrm_t * const zstrm)
{
int ret = Z_STREAM_ERROR; // error out if EOF on first loop
bool eof = false;
uc_value_t *rfn, *rbuf;
- uc_stringbuf_t *buf = NULL;
rfn = ucv_property_get(obj, "read");
if (!ucv_is_callable(rfn)) {
uc_vm_raise_exception(vm, EXCEPTION_TYPE,
"Input object does not implement read() method");
- return NULL;
+ return false;
}
- buf = ucv_stringbuf_new();
-
do {
rbuf = NULL;
uc_vm_stack_push(vm, ucv_get(obj));
@@ -317,80 +346,43 @@ uc_zlib_inf_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm)
if (eof)
break;
- strm->next_in = (unsigned char *)ucv_string_get(rbuf);
- strm->avail_in = ucv_string_length(rbuf);
-
- /* run deflate() on input until output buffer not full */
- do {
- // enlarge buf by CHUNK amount as needed
- printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
- buf->bpos -= CHUNK;
-
- strm->avail_out = CHUNK;
- strm->next_out = (unsigned char *)(buf->buf + buf->bpos);;
+ zstrm->strm.next_in = (unsigned char *)ucv_string_get(rbuf);
+ zstrm->strm.avail_in = ucv_string_length(rbuf);
- ret = inflate(strm, Z_NO_FLUSH);
- assert(ret != Z_STREAM_ERROR); // state never clobbered (would be mem corruption)
- switch (ret) {
- case Z_NEED_DICT:
- case Z_DATA_ERROR:
- case Z_MEM_ERROR:
- goto fail;
- }
-
- // update bpos past data written by deflate()
- buf->bpos += CHUNK - strm->avail_out;
- } while (strm->avail_out == 0);
+ ret = inf_chunks(zstrm);
+ switch (ret) {
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ goto fail;
+ }
ucv_put(rbuf); // release rbuf
} while (ret != Z_STREAM_END); // done when inflate() says it's done
- if (ret != Z_STREAM_END) { // data error
- printbuf_free(buf);
- buf = NULL;
- }
+ if (ret != Z_STREAM_END) // data error
+ return false;
- return buf;
+ return true;
fail:
ucv_put(rbuf);
- printbuf_free(buf);
- return NULL;
+ return false;
}
-static uc_stringbuf_t *
-uc_zlib_inf_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm)
+static bool
+uc_zlib_inf_string(uc_vm_t * const vm, uc_value_t * const str, zstrm_t * const zstrm)
{
int ret;
- uc_stringbuf_t *buf = NULL;
- buf = ucv_stringbuf_new();
+ zstrm->strm.next_in = (unsigned char *)ucv_string_get(str);
+ zstrm->strm.avail_in = ucv_string_length(str);
- strm->next_in = (unsigned char *)ucv_string_get(str);
- strm->avail_in = ucv_string_length(str);
+ ret = inf_chunks(zstrm);
+ assert(zstrm->strm.avail_in == 0);
+ last_error = ret;
- do {
- printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1);
- buf->bpos -= CHUNK;
-
- strm->avail_out = CHUNK;
- strm->next_out = (unsigned char *)(buf->buf + buf->bpos);
-
- ret = inflate(strm, Z_FINISH);
- assert(ret != Z_STREAM_ERROR);
- switch (ret) {
- case Z_NEED_DICT:
- case Z_DATA_ERROR:
- case Z_MEM_ERROR:
- printbuf_free(buf);
- return NULL;
- }
-
- buf->bpos += CHUNK - strm->avail_out;
- } while (ret != Z_STREAM_END);
- assert(strm->avail_in == 0);
-
- return buf;
+ return Z_STREAM_END == ret;
}
/**
@@ -415,36 +407,43 @@ uc_zlib_inf_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm)
* @returns {?string}
*/
static uc_value_t *
-uc_zlib_inflate(uc_vm_t *vm, size_t nargs)
+uc_zlib_inflate(uc_vm_t * const vm, const size_t nargs)
{
uc_value_t *rv = NULL;
uc_value_t *src = uc_fn_arg(0);
- uc_stringbuf_t *buf = NULL;
+ bool success;
int ret;
- z_stream strm = {
- .zalloc = Z_NULL,
- .zfree = Z_NULL,
- .opaque = Z_NULL,
- .avail_in = 0, // must be initialized before call to inflateInit
- .next_in = Z_NULL, // must be initialized before call to inflateInit
+ zstrm_t zstrm = {
+ .strm = {
+ .zalloc = Z_NULL,
+ .zfree = Z_NULL,
+ .opaque = Z_NULL,
+ .avail_in = 0, // must be initialized before call to inflateInit
+ .next_in = Z_NULL, // must be initialized before call to inflateInit
+ },
+ .outbuf = NULL,
};
/* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */
- ret = inflateInit2(&strm, 15+32);
+ ret = inflateInit2(&zstrm.strm, 15+32);
if (ret != Z_OK) {
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret));
goto out;
}
+ zstrm.outbuf = ucv_stringbuf_new();
+
switch (ucv_type(src)) {
case UC_STRING:
- buf = uc_zlib_inf_string(vm, src, &strm);
+ zstrm.flush = Z_FINISH;
+ success = uc_zlib_inf_string(vm, src, &zstrm);
break;
case UC_RESOURCE:
case UC_OBJECT:
case UC_ARRAY:
- buf = uc_zlib_inf_object(vm, src, &strm);
+ zstrm.flush = Z_NO_FLUSH;
+ success = uc_zlib_inf_object(vm, src, &zstrm);
break;
default:
@@ -453,28 +452,472 @@ uc_zlib_inflate(uc_vm_t *vm, size_t nargs)
goto out;
}
- if (!buf) {
+ if (!success) {
if (vm->exception.type == EXCEPTION_NONE) // do not clobber previous exception
- uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", strm.msg);
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", zstrm.strm.msg);
+ printbuf_free(zstrm.outbuf);
goto out;
}
- rv = ucv_stringbuf_finish(buf);
+ rv = ucv_stringbuf_finish(zstrm.outbuf);
out:
- (void)inflateEnd(&strm);
+ (void)inflateEnd(&zstrm.strm);
return rv;
}
+/**
+ * Represents a handle for interacting with a deflate stream initiated by defnew().
+ *
+ * @class module:zlib.zstrmd
+ * @hideconstructor
+ *
+ * @see {@link module:zlib#defnew()}
+ *
+ * @example
+ *
+ * const zstrmd = defnew(…);
+ *
+ * for (let data = ...; data; data = ...) {
+ * zstrmd.write(data, Z_PARTIAL_FLUSH); // write uncompressed data to stream
+ * if (foo)
+ * let defl = zstrmd.read(); // read back compressed stream content
+ * }
+ *
+ * // terminate the stream if needed (for e.g. file output)
+ * zstrmd.write('', Z_FINISH);
+ * defl = ztrmd.read();
+ *
+ * zstrmd.error();
+ */
+
+/**
+ * Initializes a deflate stream.
+ *
+ * Returns a stream handle on success.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:zlib#defnew
+ *
+ * @param {?boolean} [gzip=false]
+ * Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)
+ *
+ * @param {?number} [level=Z_DEFAULT_COMPRESSION]
+ * The compression level (0-9).
+ *
+ * @returns {?module:zlib.zstrmd}
+ *
+ * @example
+ * // initialize a Zlib deflate stream using default compression
+ * const zstrmd = defnew();
+ *
+ * // initialize a gzip deflate stream using fastest compression
+ * const zstrmd = defnew(true, Z_BEST_SPEED);
+ */
+ static uc_value_t *
+uc_zlib_defnew(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *gzip = uc_fn_arg(0);
+ uc_value_t *level = uc_fn_arg(1);
+ int ret, lvl = Z_DEFAULT_COMPRESSION;
+ bool gz = false;
+ zstrm_t *zstrm;
+
+ zstrm = calloc(1, sizeof(*zstrm));
+ if (!zstrm)
+ err_return(ENOMEM);
+
+ zstrm->strm.zalloc = Z_NULL;
+ zstrm->strm.zfree = Z_NULL;
+ zstrm->strm.opaque = Z_NULL;
+
+ if (gzip) {
+ if (ucv_type(gzip) != UC_BOOLEAN) {
+ last_error = EINVAL;
+ goto fail;
+ }
+
+ gz = (int)ucv_boolean_get(gzip);
+ }
+
+ if (level) {
+ if (ucv_type(level) != UC_INTEGER) {
+ last_error = EINVAL;
+ goto fail;
+ }
+
+ lvl = (int)ucv_int64_get(level);
+ }
+
+ ret = deflateInit2(&zstrm->strm, lvl,
+ Z_DEFLATED, // only allowed method
+ gz ? 15+16 : 15, // 15 Zlib default, +16 for gzip
+ 8, // default value
+ Z_DEFAULT_STRATEGY); // default value
+ if (ret != Z_OK) {
+ last_error = ret;
+ goto fail;
+ }
+
+ return uc_resource_new(zstrmd_type, zstrm);
+
+fail:
+ free(zstrm);
+ return NULL;
+}
+
+/**
+ * Writes a chunk of data to the deflate stream.
+ *
+ * Input data must be a string, it is internally compressed by the zlib deflate() routine,
+ * the end is buffered according to the requested `flush` mode until read via
+ * @see {@link module:zlib.zstrmd#read}.
+ * If `flush` is `Z_FINISH` (the default) then no more data can be written to the stream.
+ * Valid `flush`values are `Z_NO_FLUSH, Z_SYNC_FLUSH, Z_PARTIAL_FLUSH, Z_FULL_FLUSH, Z_FINISH`
+ *
+ * Returns `true` on success.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:zlib.zstrmd#write
+ *
+ * @param {string} src
+ * The string of data to deflate.
+ *
+ * @param {?number} [flush=Z_FINISH]
+ * The zlib flush mode.
+ *
+ * @returns {?boolean}
+ */
+static uc_value_t *
+uc_zlib_defwrite(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *src = uc_fn_arg(0);
+ uc_value_t *flush = uc_fn_arg(1);
+ zstrm_t **z = uc_fn_this("zlib.strmd");
+ zstrm_t *zstrm;
+
+ if (!z || !*z)
+ err_return(EBADF);
+
+ zstrm = *z;
+
+ if (Z_FINISH == zstrm->flush)
+ err_return(EPIPE); // can't reuse a finished stream
+
+ if (flush) {
+ if (ucv_type(flush) != UC_INTEGER)
+ err_return(EINVAL);
+
+ zstrm->flush = (int)ucv_int64_get(flush);
+ switch (zstrm->flush) {
+ case Z_NO_FLUSH:
+ case Z_SYNC_FLUSH:
+ case Z_PARTIAL_FLUSH:
+ case Z_FULL_FLUSH:
+ case Z_FINISH:
+ break;
+ default:
+ err_return(EINVAL);
+ }
+ }
+ else
+ zstrm->flush = Z_FINISH;
+
+ /* we only accept strings */
+ if (!src || ucv_type(src) != UC_STRING)
+ err_return(EINVAL);
+
+ if (!zstrm->outbuf)
+ zstrm->outbuf = ucv_stringbuf_new();
+
+ return ucv_boolean_new(uc_zlib_def_string(vm, src, zstrm));
+}
+
+/**
+ * Reads a chunk of compressed data from the deflate stream.
+ *
+ * Returns the current content of the deflate buffer, fed through
+ * @see {@link module:zlib.zstrmd#write}.
+ *
+ * Returns compressed chunk on success.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:zlib.zstrmd#read
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_zlib_defread(uc_vm_t *vm, size_t nargs)
+{
+ zstrm_t **z = uc_fn_this("zlib.strmd");
+ zstrm_t *zstrm;
+ uc_value_t *rv;
+
+ if (!z || !*z)
+ err_return(EBADF);
+
+ zstrm = *z;
+
+ if (!zstrm->outbuf)
+ err_return(ENODATA);
+
+ if (Z_FINISH == zstrm->flush)
+ (void)deflateEnd(&zstrm->strm);
+
+ rv = ucv_stringbuf_finish(zstrm->outbuf);
+ zstrm->outbuf = NULL; // outbuf is now unuseable
+ return rv;
+}
+
+/**
+ * Represents a handle for interacting with an inflate stream initiated by infnew().
+ *
+ * @class module:zlib.zstrmi
+ * @hideconstructor
+ *
+ * @see {@link module:zlib#infnew()}
+ *
+ * @example
+ *
+ * const zstrmi = infnew();
+ *
+ * for (let data = ...; data; data = ...) {
+ * zstrmi.write(data, Z_SYNC_FLUSH); // write compressed data to stream
+ * if (foo)
+ * let defl = zstrmi.read(); // read back decompressed stream content
+ * }
+ *
+ * // terminate the stream if needed (for e.g. file output)
+ * zstrmi.write('', Z_FINISH);
+ * defl = ztrmi.read();
+ *
+ * zstrmi.error();
+ */
+
+/**
+ * Initializes an inflate stream. Can process either Zlib or gzip data.
+ *
+ * Returns a stream handle on success.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:zlib#infnew
+ *
+ * @returns {?module:zlib.zstrmi}
+ *
+ * @example
+ * // initialize an inflate stream
+ * const zstrmi = infnew();
+ */
+ static uc_value_t *
+uc_zlib_infnew(uc_vm_t *vm, size_t nargs)
+{
+ int ret;
+ zstrm_t *zstrm;
+
+ zstrm = calloc(1, sizeof(*zstrm));
+ if (!zstrm)
+ err_return(ENOMEM);
+
+ zstrm->strm.zalloc = Z_NULL;
+ zstrm->strm.zfree = Z_NULL;
+ zstrm->strm.opaque = Z_NULL;
+ zstrm->strm.avail_in = 0;
+ zstrm->strm.next_in = Z_NULL;
+
+ /* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */
+ ret = inflateInit2(&zstrm->strm, 15+32);
+ if (ret != Z_OK) {
+ last_error = ret;
+ goto fail;
+ }
+
+ return uc_resource_new(zstrmi_type, zstrm);
+
+fail:
+ free(zstrm);
+ return NULL;
+}
+
+/**
+ * Writes a chunk of data to the inflate stream.
+ *
+ * Input data must be a string, it is internally decompressed by the zlib deflate() routine,
+ * the end is buffered according to the requested `flush` mode until read via
+ * @see {@link module:zlib.zstrmd#read}.
+ * If `flush` is `Z_FINISH` (the default) then no more data can be written to the stream.
+ * Valid `flush` values are `Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH`
+ *
+ * Returns `true` on success.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:zlib.zstrmi#write
+ *
+ * @param {string} src
+ * The string of data to deflate.
+ *
+ * @param {?number} [flush=Z_FINISH]
+ * The zlib flush mode.
+ *
+ * @returns {?boolean}
+ */
+static uc_value_t *
+uc_zlib_infwrite(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *src = uc_fn_arg(0);
+ uc_value_t *flush = uc_fn_arg(1);
+ zstrm_t **z = uc_fn_this("zlib.strmi");
+ zstrm_t *zstrm;
+
+ if (!z || !*z)
+ err_return(EBADF);
+
+ zstrm = *z;
+
+ if (Z_FINISH == zstrm->flush)
+ err_return(EPIPE); // can't reuse a finished stream
+
+ if (flush) {
+ if (ucv_type(flush) != UC_INTEGER)
+ err_return(EINVAL);
+
+ zstrm->flush = (int)ucv_int64_get(flush);
+ switch (zstrm->flush) {
+ case Z_NO_FLUSH:
+ case Z_SYNC_FLUSH:
+ case Z_FINISH:
+ break;
+ default:
+ err_return(EINVAL);
+ }
+ }
+ else
+ zstrm->flush = Z_FINISH;
+
+ /* we only accept strings */
+ if (!src || ucv_type(src) != UC_STRING)
+ err_return(EINVAL);
+
+ if (!zstrm->outbuf)
+ zstrm->outbuf = ucv_stringbuf_new();
+
+ return ucv_boolean_new(uc_zlib_inf_string(vm, src, zstrm));
+}
+
+/**
+ * Reads a chunk of decompressed data from the inflate stream.
+ *
+ * Returns the current content of the inflate buffer, fed through
+ * @see {@link module:zlib.zstrmi#write}.
+ *
+ * Returns compressed chunk on success.
+ *
+ * Returns `null` if an error occurred.
+ *
+ * @function module:zlib.zstrmd#read
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_zlib_infread(uc_vm_t *vm, size_t nargs)
+{
+ zstrm_t **z = uc_fn_this("zlib.strmi");
+ zstrm_t *zstrm;
+ uc_value_t *rv;
+
+ if (!z || !*z)
+ err_return(EBADF);
+
+ zstrm = *z;
+
+ if (!zstrm->outbuf)
+ err_return(ENODATA);
+
+ if (Z_FINISH == zstrm->flush)
+ (void)inflateEnd(&zstrm->strm);
+
+ rv = ucv_stringbuf_finish(zstrm->outbuf);
+ zstrm->outbuf = NULL; // outbuf is now unuseable
+ return rv;
+}
+
+/**
+ * Query error information.
+ *
+ * Returns a string containing a description of the last occurred error or
+ * `null` if there is no error information.
+ *
+ * @function module:zlib.zstrmd#error
+ *
+ *
+ * @returns {?string}
+ */
+static uc_value_t *
+uc_zlib_error(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *errmsg;
+
+ if (!last_error)
+ return NULL;
+
+ // negative last_error only happens for zlib init returns
+ errmsg = ucv_string_new(last_error < 0 ? ziniterr(last_error) : strerror(last_error));
+ last_error = 0;
+ return errmsg;
+}
+
+static const uc_function_list_t strmd_fns[] = {
+ { "write", uc_zlib_defwrite },
+ { "read", uc_zlib_defread },
+ { "error", uc_zlib_error },
+};
+
+static const uc_function_list_t strmi_fns[] = {
+ { "write", uc_zlib_infwrite },
+ { "read", uc_zlib_infread },
+ { "error", uc_zlib_error },
+};
+
static const uc_function_list_t global_fns[] = {
{ "deflate", uc_zlib_deflate },
{ "inflate", uc_zlib_inflate },
+ { "defnew", uc_zlib_defnew },
+ { "infnew", uc_zlib_infnew },
};
+static void destroy_zstrmd(void *z)
+{
+ zstrm_t *zstrm = z;
+
+ if (zstrm) {
+ (void)deflateEnd(&zstrm->strm);
+ printbuf_free(zstrm->outbuf);
+ free(zstrm);
+ }
+}
+
+static void destroy_zstrmi(void *z)
+{
+ zstrm_t *zstrm = z;
+
+ if (zstrm) {
+ (void)inflateEnd(&zstrm->strm);
+ printbuf_free(zstrm->outbuf);
+ free(zstrm);
+ }
+}
+
void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
uc_function_list_register(scope, global_fns);
+ zstrmd_type = uc_type_declare(vm, "zlib.strmd", strmd_fns, destroy_zstrmd);
+ zstrmi_type = uc_type_declare(vm, "zlib.strmi", strmi_fns, destroy_zstrmi);
+
#define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
/**
@@ -490,4 +933,20 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
ADD_CONST(Z_BEST_SPEED);
ADD_CONST(Z_BEST_COMPRESSION);
ADD_CONST(Z_DEFAULT_COMPRESSION);
+
+ /**
+ * @typedef
+ * @name flush options
+ * @description Constants representing flush options.
+ * @property {number} Z_NO_FLUSH.
+ * @property {number} Z_PARTIAL_FLUSH.
+ * @property {number} Z_SYNC_FLUSH.
+ * @property {number} Z_FULL_FLUSH.
+ * @property {number} Z_FINISH.
+ */
+ ADD_CONST(Z_NO_FLUSH);
+ ADD_CONST(Z_PARTIAL_FLUSH);
+ ADD_CONST(Z_SYNC_FLUSH);
+ ADD_CONST(Z_FULL_FLUSH);
+ ADD_CONST(Z_FINISH);
}