From: Govind Venugopal Date: Thu, 16 Oct 2025 15:06:17 +0000 (-0700) Subject: varlink: omit empty parameters field in JSON messages (#38922) X-Git-Tag: v259-rc1~309 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=48c64813ec6bdbe536b7a62281292ea5981295ba;p=thirdparty%2Fsystemd.git varlink: omit empty parameters field in JSON messages (#38922) 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 --- diff --git a/man/sd-varlink.xml b/man/sd-varlink.xml index c7cfceb7125..282d6a330ef 100644 --- a/man/sd-varlink.xml +++ b/man/sd-varlink.xml @@ -44,6 +44,13 @@ sd-json3 API for JSON serialization, deserialization and manipulation. + Canonical encoding rules: sd-varlink omits the "parameters" 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 "parameters" key (preferred), a JSON null + value, or an empty object {}. When decoding, sd-varlink treats JSON null + as if the member was omitted. + The varlinkctl1 tool makes the functionality implemented by sd-varlink available from the command line. diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index d0db4d8035a..d5e8f57e4ab 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -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 } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 77f7524d2a1..79cfa4cc60d 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -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; diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 7e6680e0e60..03ed52f1720 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -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) { diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index e0d5bf20aaa..6e50073049a 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -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(¶meters); + r = varlink_sanitize_incoming_parameters(¶meters); 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(¶meters); + r = varlink_sanitize_incoming_parameters(¶meters); 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(¶meters); - 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(¶meters); - 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(¶meters); - 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(¶meters); - 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(¶meters); - 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(¶meters); - 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(¶meters); - 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(¶meters); - 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) diff --git a/src/shared/varlink-io.systemd.service.c b/src/shared/varlink-io.systemd.service.c index d130f0df51a..8cbf299e07b 100644 --- a/src/shared/varlink-io.systemd.service.c +++ b/src/shared/varlink-io.systemd.service.c @@ -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) diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index b298b9cfbfe..0b834fad17d 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -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;