feat(protocol): add optional STATUS_ERROR_DETAIL rejection reason (2.21.0)
Today a server rejection sends a bare STATUS_ERROR and the reason only
reaches the server log, so the client cannot say why a transfer was
refused. Add an optional, bounded server->client error-detail frame:
- Status gains STATUS_ERROR_DETAIL appended LAST so existing wire
values are unchanged.
- send_error_detail(fd, msg) sends STATUS_ERROR_DETAIL followed by the
existing length-prefixed string primitive, slicing over-long messages
to MAX_ERROR_DETAIL_BYTES (4096).
- receive_status() (and the timed/keepalive status readers) always
consume the detail body and map the status back to STATUS_ERROR,
capturing the text into a thread-local buffer exposed by
protocol_last_error(); a bare STATUS_ERROR leaves it cleared. Every
existing call site keeps working and the stream cannot desync.
- Upgrade the daemon module gate / config validation (config.c), the
final transfer failure (server.c) and receiver-side path/node
validation (file_receive.c) to send a concrete reason; surface it on
the client in client_send.c/config.c.
- Bump PROTOCOL_VERSION to 2.21.0 (CMake VERSION, CHANGELOG, docs) and
update the pinned config wire golden hash / CLI-version tests.
- Add tests/test_protocol_error.c covering mapping+capture, the
over-long bound, bare-error clearing, and thread-locality.
This commit is contained in:
+16
-6
@@ -773,8 +773,8 @@ static bool config_receive_module(int fd, Config* c, ConfigStringBudget* budget)
|
||||
return false;
|
||||
if (*module != '\0' && !daemon_module_name_valid(module)) {
|
||||
log_message(LOG_LEVEL_WARNING, "Daemon client sent an invalid or over-long module name");
|
||||
send_error_detail(fd, "invalid or over-long daemon module name");
|
||||
free(module);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (*module != '\0') {
|
||||
@@ -1238,7 +1238,11 @@ bool config_send(int file_descriptor, const Config* config) {
|
||||
return false;
|
||||
}
|
||||
if (status != STATUS_OK) {
|
||||
log_message(LOG_LEVEL_ERROR, "Error transmitting config");
|
||||
const char* detail = protocol_last_error();
|
||||
if (detail && detail[0] != '\0')
|
||||
log_message(LOG_LEVEL_ERROR, "Error transmitting config: %s", detail);
|
||||
else
|
||||
log_message(LOG_LEVEL_ERROR, "Error transmitting config");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1258,8 +1262,11 @@ Config* config_receive_with_validate(int file_descriptor, ConfigValidateFunc val
|
||||
char* escaped_version = output_escape(config->version, false);
|
||||
log_message(LOG_LEVEL_ERROR, "Protocol version mismatch: client=%s, server=%s",
|
||||
escaped_version ? escaped_version : "<allocation failed>", PROTOCOL_VERSION);
|
||||
char detail[160];
|
||||
snprintf(detail, sizeof(detail), "protocol version mismatch (client=%s, server=%s)",
|
||||
escaped_version ? escaped_version : "<allocation failed>", PROTOCOL_VERSION);
|
||||
send_error_detail(file_descriptor, detail);
|
||||
free(escaped_version);
|
||||
send_status(file_descriptor, STATUS_ERROR);
|
||||
goto error;
|
||||
}
|
||||
if (!receive_core_fields(file_descriptor, config, &budget) ||
|
||||
@@ -1285,13 +1292,16 @@ Config* config_receive_with_validate(int file_descriptor, ConfigValidateFunc val
|
||||
char* escaped_choice = output_escape(config->compress_choice, config->eight_bit_output);
|
||||
log_message(LOG_LEVEL_ERROR, "Unsupported compression choice: %s",
|
||||
escaped_choice ? escaped_choice : "<allocation failed>");
|
||||
char detail[128];
|
||||
snprintf(detail, sizeof(detail), "unsupported compression choice: %s",
|
||||
escaped_choice ? escaped_choice : "<allocation failed>");
|
||||
send_error_detail(file_descriptor, detail);
|
||||
free(escaped_choice);
|
||||
send_status(file_descriptor, STATUS_ERROR);
|
||||
goto error;
|
||||
}
|
||||
if (!validate_received_config(config)) {
|
||||
log_message(LOG_LEVEL_ERROR, "Invalid configuration received from client");
|
||||
send_status(file_descriptor, STATUS_ERROR);
|
||||
send_error_detail(file_descriptor, "invalid configuration received from client");
|
||||
goto error;
|
||||
}
|
||||
if (validate) {
|
||||
@@ -1305,7 +1315,7 @@ Config* config_receive_with_validate(int file_descriptor, ConfigValidateFunc val
|
||||
* written. */
|
||||
if (rejection != CONFIG_VALIDATE_ALREADY_TERMINATED) {
|
||||
log_message(LOG_LEVEL_ERROR, "%s", rejection);
|
||||
send_status(file_descriptor, STATUS_ERROR);
|
||||
send_error_detail(file_descriptor, rejection);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user