]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
varlink: omit empty parameters field in JSON messages (#38922)
authorGovind Venugopal <gvenugo3@asu.edu>
Thu, 16 Oct 2025 15:06:17 +0000 (08:06 -0700)
committerGitHub <noreply@github.com>
Thu, 16 Oct 2025 15:06:17 +0000 (17:06 +0200)
When varlink parameters are empty, omit the "parameters" field entirely
rather than sending "parameters":{}. This reduces message size and
follows varlink specification which allows parameters to be omitted.

The implementation supports three equivalent representations for empty
parameters: field omission, JSON null, and empty object {}. All three
are accepted on input for backward compatibility.

Fixes: #38474
man/sd-varlink.xml
src/libsystemd/sd-json/json-util.h
src/libsystemd/sd-json/sd-json.c
src/libsystemd/sd-varlink/sd-varlink-idl.c
src/libsystemd/sd-varlink/sd-varlink.c
src/shared/varlink-io.systemd.service.c
src/test/test-varlink.c

index c7cfceb71252f95f6dc667fdfceef2c64a8b0a78..282d6a330ef1f62ba5782a901bd7b51d73885ca0 100644 (file)
     <citerefentry><refentrytitle>sd-json</refentrytitle><manvolnum>3</manvolnum></citerefentry> API for JSON
     serialization, deserialization and manipulation.</para>
 
+    <para>Canonical encoding rules: sd-varlink omits the <literal>"parameters"</literal> member on the wire in replies,
+    errors, and notifications when there are no parameters to transmit. This reduces message size and
+    avoids ambiguity. Receivers must be tolerant and accept any of the following encodings for the
+    absence of parameters: an omitted <literal>"parameters"</literal> key (preferred), a JSON <literal>null</literal>
+    value, or an empty object <literal>{}</literal>. When decoding, sd-varlink treats JSON <literal>null</literal>
+    as if the member was omitted.</para>
+
     <para>The <citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> tool
     makes the functionality implemented by sd-varlink available from the command line.</para>
   </refsect1>
index d0db4d8035a93b41a0be41d0b535910f00a2929a..d5e8f57e4ab0ce627be77a7405d99267b5f7046f 100644 (file)
@@ -170,6 +170,7 @@ enum {
         _JSON_BUILD_PAIR_STRV_NON_EMPTY,
         _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY,
         _JSON_BUILD_PAIR_VARIANT_NON_NULL,
+        _JSON_BUILD_PAIR_VARIANT_NON_EMPTY,
         /* _SD_JSON_BUILD_PAIR_VARIANT_ARRAY_NON_EMPTY, */
         _JSON_BUILD_PAIR_BYTE_ARRAY_NON_EMPTY,
         _JSON_BUILD_PAIR_IN4_ADDR_NON_NULL,
@@ -218,6 +219,7 @@ enum {
 #define JSON_BUILD_PAIR_STRV_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_NON_EMPTY, (const char*) { name }, (char**) { l }
 #define JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY, (const char*) { name }, (char**) { l }
 #define JSON_BUILD_PAIR_VARIANT_NON_NULL(name, v) _JSON_BUILD_PAIR_VARIANT_NON_NULL, (const char*) { name }, (sd_json_variant*) { v }
+#define JSON_BUILD_PAIR_VARIANT_NON_EMPTY(name, v) _JSON_BUILD_PAIR_VARIANT_NON_EMPTY, (const char*) { name }, (sd_json_variant*) { v }
 #define JSON_BUILD_PAIR_BYTE_ARRAY_NON_EMPTY(name, v, n) _JSON_BUILD_PAIR_BYTE_ARRAY_NON_EMPTY, (const char*) { name }, (const void*) { v }, (size_t) { n }
 #define JSON_BUILD_PAIR_IN4_ADDR_NON_NULL(name, v) _JSON_BUILD_PAIR_IN4_ADDR_NON_NULL, (const char*) { name }, (const struct in_addr*) { v }
 #define JSON_BUILD_PAIR_IN6_ADDR_NON_NULL(name, v) _JSON_BUILD_PAIR_IN6_ADDR_NON_NULL, (const char*) { name }, (const struct in6_addr*) { v }
index 77f7524d2a1317785e40ae928b323c3decbf0a7f..79cfa4cc60ddf1b66cab653a029113972622d5d7 100644 (file)
@@ -4445,7 +4445,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4475,7 +4475,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4503,7 +4503,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4530,7 +4530,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4567,7 +4567,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4593,7 +4593,33 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                 add_more = sd_json_variant_ref(v);
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
+
+                        current->expect = EXPECT_OBJECT_KEY;
+                        break;
+                }
+
+                case _JSON_BUILD_PAIR_VARIANT_NON_EMPTY: {
+                        sd_json_variant *v;
+                        const char *n;
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
+                                goto finish;
+                        }
+
+                        n = va_arg(ap, const char *);
+                        v = va_arg(ap, sd_json_variant *);
+
+                        if (v && !sd_json_variant_is_blank_object(v) && current->n_suppress == 0) {
+                                r = sd_json_variant_new_string(&add, n);
+                                if (r < 0)
+                                        goto finish;
+
+                                add_more = sd_json_variant_ref(v);
+                        }
+
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4623,7 +4649,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4651,7 +4677,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4679,7 +4705,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4709,7 +4735,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4737,7 +4763,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4765,7 +4791,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4795,7 +4821,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4825,7 +4851,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4887,7 +4913,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                 }
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4923,7 +4949,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
@@ -4951,7 +4977,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) {
                                         goto finish;
                         }
 
-                        n_subtract = 2; /* we generated two item */
+                        n_subtract = 2; /* we generated two items */
 
                         current->expect = EXPECT_OBJECT_KEY;
                         break;
index 7e6680e0e60529e5ef17fb8d31b2168bef3383bb..03ed52f1720b7d0b739a418db83b3d760ff08739 100644 (file)
@@ -5,6 +5,7 @@
 #include "alloc-util.h"
 #include "ansi-color.h"
 #include "extract-word.h"
+#include "json-internal.h"
 #include "json-util.h"
 #include "log.h"
 #include "memstream-util.h"
@@ -1793,11 +1794,9 @@ static int varlink_idl_validate_symbol(const sd_varlink_symbol *symbol, sd_json_
         assert(symbol);
         assert(!IN_SET(symbol->symbol_type, _SD_VARLINK_SYMBOL_COMMENT, _SD_VARLINK_INTERFACE_COMMENT));
 
-        if (!v) {
-                if (reterr_bad_field)
-                        *reterr_bad_field = NULL;
-                return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
-        }
+        /* Consider a NULL pointer equivalent to an empty object */
+        if (!v)
+                v = JSON_VARIANT_MAGIC_EMPTY_OBJECT;
 
         switch (symbol->symbol_type) {
 
index e0d5bf20aaa1254859af36ca6181dda72ad537cc..6e50073049aa45090d23228d7f10c6e55aaad127 100644 (file)
@@ -1020,6 +1020,7 @@ static int varlink_test_timeout(sd_varlink *v) {
 }
 
 static int varlink_dispatch_local_error(sd_varlink *v, const char *error) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *empty = NULL;
         int r;
 
         assert(v);
@@ -1028,7 +1029,11 @@ static int varlink_dispatch_local_error(sd_varlink *v, const char *error) {
         if (!v->reply_callback)
                 return 0;
 
-        r = v->reply_callback(v, NULL, error, SD_VARLINK_REPLY_ERROR|SD_VARLINK_REPLY_LOCAL, v->userdata);
+        r = sd_json_variant_new_object(&empty, NULL, 0);
+        if (r < 0)
+                return r;
+
+        r = v->reply_callback(v, empty, error, SD_VARLINK_REPLY_ERROR|SD_VARLINK_REPLY_LOCAL, v->userdata);
         if (r < 0)
                 varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m");
 
@@ -1061,25 +1066,23 @@ static int varlink_dispatch_disconnect(sd_varlink *v) {
         return 1;
 }
 
-static int varlink_sanitize_parameters(sd_json_variant **v) {
+static int varlink_sanitize_incoming_parameters(sd_json_variant **v) {
         int r;
-
         assert(v);
 
-        /* Varlink always wants a parameters list, hence make one if the caller doesn't want any */
-        if (!*v)
-                return sd_json_variant_new_object(v, NULL, 0);
-        if (sd_json_variant_is_null(*v)) {
-                sd_json_variant *empty;
-
+        /* Convert NULL or JSON null to empty object for method handlers (backward compatibility) */
+        if (!*v || sd_json_variant_is_null(*v)) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *empty = NULL;
                 r = sd_json_variant_new_object(&empty, NULL, 0);
                 if (r < 0)
                         return r;
-
+                /* sd_json_variant_unref() is a NOP if *v is NULL */
                 sd_json_variant_unref(*v);
-                *v = empty;
+                *v = TAKE_PTR(empty);
                 return 0;
         }
+
+        /* Ensure we have an object */
         if (!sd_json_variant_is_object(*v))
                 return -EINVAL;
 
@@ -1146,7 +1149,7 @@ static int varlink_dispatch_reply(sd_varlink *v) {
         if (error && FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
                 goto invalid;
 
-        r = varlink_sanitize_parameters(&parameters);
+        r = varlink_sanitize_incoming_parameters(&parameters);
         if (r < 0)
                 goto invalid;
 
@@ -1327,7 +1330,7 @@ static int varlink_dispatch_method(sd_varlink *v) {
         if (!method)
                 goto invalid;
 
-        r = varlink_sanitize_parameters(&parameters);
+        r = varlink_sanitize_incoming_parameters(&parameters);
         if (r < 0)
                 goto fail;
 
@@ -1575,13 +1578,14 @@ _public_ int sd_varlink_get_current_parameters(sd_varlink *v, sd_json_variant **
         if (!v->current)
                 return -ENODATA;
 
-        p = sd_json_variant_by_key(v->current, "parameters");
-        if (!p)
-                return -ENODATA;
+        if (!ret)
+                return 0;
 
-        if (ret)
-                *ret = sd_json_variant_ref(p);
+        p = sd_json_variant_by_key(v->current, "parameters");
+        if (!p || sd_json_variant_is_null(p))
+                return sd_json_variant_new_object(ret, NULL, 0);
 
+        *ret = sd_json_variant_ref(p);
         return 0;
 }
 
@@ -2024,14 +2028,10 @@ _public_ int sd_varlink_send(sd_varlink *v, const char *method, sd_json_variant
         if (!IN_SET(v->state, VARLINK_IDLE_CLIENT, VARLINK_AWAITING_REPLY))
                 return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy.");
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
         r = sd_json_buildo(
                         &m,
                         SD_JSON_BUILD_PAIR("method", SD_JSON_BUILD_STRING(method)),
-                        SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)),
+                        JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters),
                         SD_JSON_BUILD_PAIR("oneway", SD_JSON_BUILD_BOOLEAN(true)));
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to build json message: %m");
@@ -2076,14 +2076,10 @@ _public_ int sd_varlink_invoke(sd_varlink *v, const char *method, sd_json_varian
         if (!IN_SET(v->state, VARLINK_IDLE_CLIENT, VARLINK_AWAITING_REPLY))
                 return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy.");
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
         r = sd_json_buildo(
                         &m,
                         SD_JSON_BUILD_PAIR("method", SD_JSON_BUILD_STRING(method)),
-                        SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)));
+                        JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters));
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to build json message: %m");
 
@@ -2130,14 +2126,10 @@ _public_ int sd_varlink_observe(sd_varlink *v, const char *method, sd_json_varia
         if (v->state != VARLINK_IDLE_CLIENT)
                 return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy.");
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
         r = sd_json_buildo(
                         &m,
                         SD_JSON_BUILD_PAIR("method", SD_JSON_BUILD_STRING(method)),
-                        SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)),
+                        JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters),
                         SD_JSON_BUILD_PAIR("more", SD_JSON_BUILD_BOOLEAN(true)));
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to build json message: %m");
@@ -2195,14 +2187,10 @@ _public_ int sd_varlink_call_full(
          * that we can assign a new reply shortly. */
         varlink_clear_current(v);
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
         r = sd_json_buildo(
                         &m,
                         SD_JSON_BUILD_PAIR("method", SD_JSON_BUILD_STRING(method)),
-                        SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)));
+                        JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters));
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to build json message: %m");
 
@@ -2353,14 +2341,10 @@ _public_ int sd_varlink_collect_full(
          * that we can assign a new reply shortly. */
         varlink_clear_current(v);
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
         r = sd_json_buildo(
                         &m,
                         SD_JSON_BUILD_PAIR("method", SD_JSON_BUILD_STRING(method)),
-                        SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)),
+                        JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters),
                         SD_JSON_BUILD_PAIR("more", SD_JSON_BUILD_BOOLEAN(true)));
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to build json message: %m");
@@ -2501,14 +2485,7 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) {
                     VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE))
                 return -EBUSY;
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
-        r = sd_json_buildo(&m, SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)));
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to build json message: %m");
-
+        /* Validate parameters BEFORE sanitization */
         if (v->current_method) {
                 const char *bad_field = NULL;
 
@@ -2519,6 +2496,10 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) {
                                           v->current_method->name, strna(bad_field));
         }
 
+        r = sd_json_buildo(&m, JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters));
+        if (r < 0)
+                return varlink_log_errno(v, r, "Failed to build json message: %m");
+
         r = varlink_enqueue_json(v, m);
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
@@ -2588,17 +2569,7 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia
          * the callers don't need to do this explicitly. */
         sd_varlink_reset_fds(v);
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
-        r = sd_json_buildo(
-                        &m,
-                        SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_STRING(error_id)),
-                        SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)));
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to build json message: %m");
-
+        /* Validate parameters BEFORE sanitization */
         sd_varlink_symbol *symbol = hashmap_get(v->server->symbols, error_id);
         if (!symbol)
                 varlink_log(v, "No interface description defined for error '%s', not validating.", error_id);
@@ -2612,6 +2583,13 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia
                                           error_id, strna(bad_field));
         }
 
+        r = sd_json_buildo(
+                        &m,
+                        SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_STRING(error_id)),
+                        JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters));
+        if (r < 0)
+                return varlink_log_errno(v, r, "Failed to build json message: %m");
+
         r = varlink_enqueue_json(v, m);
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
@@ -2726,17 +2704,7 @@ _public_ int sd_varlink_notify(sd_varlink *v, sd_json_variant *parameters) {
         if (!IN_SET(v->state, VARLINK_PROCESSING_METHOD_MORE, VARLINK_PENDING_METHOD_MORE))
                 return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection busy.");
 
-        r = varlink_sanitize_parameters(&parameters);
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
-
-        r = sd_json_buildo(
-                        &m,
-                        SD_JSON_BUILD_PAIR("parameters", SD_JSON_BUILD_VARIANT(parameters)),
-                        SD_JSON_BUILD_PAIR("continues", SD_JSON_BUILD_BOOLEAN(true)));
-        if (r < 0)
-                return varlink_log_errno(v, r, "Failed to build json message: %m");
-
+        /* Validate parameters BEFORE sanitization */
         if (v->current_method) {
                 const char *bad_field = NULL;
 
@@ -2750,6 +2718,13 @@ _public_ int sd_varlink_notify(sd_varlink *v, sd_json_variant *parameters) {
                                           v->current_method->name, strna(bad_field));
         }
 
+        r = sd_json_buildo(
+                        &m,
+                        JSON_BUILD_PAIR_VARIANT_NON_EMPTY("parameters", parameters),
+                        SD_JSON_BUILD_PAIR("continues", SD_JSON_BUILD_BOOLEAN(true)));
+        if (r < 0)
+                return varlink_log_errno(v, r, "Failed to build json message: %m");
+
         r = varlink_enqueue_json(v, m);
         if (r < 0)
                 return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
@@ -2783,6 +2758,7 @@ _public_ int sd_varlink_dispatch(sd_varlink *v, sd_json_variant *parameters, con
 
         /* A wrapper around json_dispatch_full() that returns a nice InvalidParameter error if we hit a problem with some field. */
 
+        /* sd_json_dispatch_full() now handles NULL parameters gracefully */
         r = sd_json_dispatch_full(parameters, dispatch_table, /* bad= */ NULL, /* flags= */ 0, userdata, &bad_field);
         if (r < 0) {
                 if (bad_field)
index d130f0df51a5e441c0d69882a00d25c792d54b27..8cbf299e07baa86e568bab1f21f38cb2c0188019 100644 (file)
@@ -47,6 +47,7 @@ int varlink_method_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlin
         int r;
 
         assert(link);
+        assert(parameters);
 
         r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL);
         if (r != 0)
index b298b9cfbfed73adc5accc1bbd46a5b6a0db5bbe..0b834fad17dadb3a31bf5705e3916dc89ad02897 100644 (file)
@@ -205,6 +205,9 @@ static int overload_reply(sd_varlink *link, sd_json_variant *parameters, const c
 
         log_debug("Over reply triggered with error: %s", strna(error_id));
         ASSERT_STREQ(error_id, SD_VARLINK_ERROR_DISCONNECTED);
+        /* Local disconnect errors carry empty parameters. Ensure we propagate
+         * a consistent empty object for API reliability. */
+        ASSERT_TRUE(sd_json_variant_is_blank_object(parameters));
         sd_event_exit(sd_varlink_get_event(link), 0);
 
         return 0;