Browse Source

action nodes: send BGP node dump as text response to GUI, display in popup

- route_node_format_all(): format all BGP nodes to snprintf buffer
- control_server: 'nodes' action sends ETCPMON_RSP_ACTION_RESULT (0x89) with text
- etcpmon_protocol.h: new response type + struct, increase MAX_MSG_SIZE to 32K
- etcpmon_client: on_action_result callback, send_action via send_request with seq_id
- etcpmon_gui: ETCPMonActionResult popup window class with EDIT multiline,
  on_action_result creates popup (same size as main) or appends text + scrolls
chatgui
Evgeny 3 months ago
parent
commit
35871891db
  1. 20
      src/control_server.c
  2. 145
      src/route_node.c
  3. 1
      src/route_node.h
  4. 66
      tools/etcpmon/etcpmon_client.c
  5. 2
      tools/etcpmon/etcpmon_client.h
  6. 77
      tools/etcpmon/etcpmon_gui.c
  7. 4
      tools/etcpmon/etcpmon_gui.h
  8. 13
      tools/etcpmon/etcpmon_protocol.h

20
src/control_server.c

@ -767,7 +767,25 @@ static void handle_client_data(struct control_server* server, struct control_cli
route_bgp_request_nat_check_all(server->instance->bgp);
} else if (strncmp(cmd->action, "nodes", 5) == 0) {
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nodes' received, dumping all BGP nodes");
route_node_dump_all(server->instance->bgp);
uint16_t text_max = (ETCPMON_MAX_MSG_SIZE > 1024) ? ETCPMON_MAX_MSG_SIZE - 512 : 1024;
char* text = (char*)u_malloc(text_max);
if (text) {
int text_len = route_node_format_all(server->instance->bgp, text, text_max);
if (text_len < 0) text_len = 0;
text[text_len] = '\0';
uint16_t rsp_size = ETCPMON_ACTION_RESULT_SIZE(text_len);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (buffer) {
struct etcpmon_msg_header* rsp_hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(rsp_hdr, sizeof(struct etcpmon_rsp_action_result) + text_len + 1,
ETCPMON_RSP_ACTION_RESULT, req_seq);
struct etcpmon_rsp_action_result* rsp = (struct etcpmon_rsp_action_result*)(buffer + sizeof(*rsp_hdr));
rsp->code = 0;
memcpy(buffer + sizeof(*rsp_hdr) + sizeof(*rsp), text, text_len + 1);
control_client_send(server, client, buffer, rsp_size);
}
u_free(text);
}
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
}

145
src/route_node.c

@ -280,6 +280,151 @@ void route_node_dump_all(struct ROUTE_BGP* bgp) {
}
}
int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size) {
if (!bgp || !bgp->nodes || !buf || buf_size == 0) return 0;
int pos = 0;
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
"=== BGP Nodes dump: %d nodes ===\n", queue_entry_count(bgp->nodes));
int idx = 0;
struct ll_entry* e = bgp->nodes->head;
while (e && (size_t)pos < buf_size) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
struct NODEINFO* ni = &nq->node;
int is_local = (bgp->local_node == nq);
const char* name_s = (ni->node_name_len > 0) ? (const char*)((const uint8_t*)ni + sizeof(struct NODEINFO)) : "";
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
"--- Node #%d: id=%016llx name=\"%.*s\" ver=%u dirty=%u alien=%u%s ---\n",
idx, (unsigned long long)ni->node_id, ni->node_name_len, name_s,
ni->ver, nq->dirty, nq->alien, is_local ? " [LOCAL]" : "");
if (nq->best_socket)
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
" best_socket=%p\n", (void*)nq->best_socket);
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " pubkey: ");
for (int k = 0; k < SC_PUBKEY_SIZE; k++)
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->public_key[k]);
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n");
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " ed25519: ");
for (int k = 0; k < SC_PUBKEY_SIZE; k++)
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->ed25519_public_key[k]);
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n");
#define FMT_ADD(fmt, ...) \
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, fmt, ##__VA_ARGS__)
const struct NODEINFO_IPV4_SOCKET_META* v4sm;
int v4sm_cnt = get_node_v4_sockets_meta(nq, &v4sm);
for (int i = 0; i < v4sm_cnt; i++)
FMT_ADD(" v4sock[%d]: id=%u type=%u\n", i, v4sm[i].id, v4sm[i].type);
const struct NODEINFO_IPV4_ADDR* v4a;
int v4a_cnt = get_node_v4_addrs(nq, &v4a);
if (v4a_cnt > 0) {
const char* atn[] = {"INTERFACE","NAT","REAL"};
for (int i = 0; i < v4a_cnt; i++)
FMT_ADD(" v4addr[%d]: %s:%u %s sock=%u\n",
i, ip_to_str(v4a[i].addr, AF_INET).str, v4a[i].port,
(v4a[i].type<=2)?atn[v4a[i].type]:"?", v4a[i].socket_id);
}
const struct NODEINFO_IPV6_SOCKET_META* v6sm;
int v6sm_cnt = get_node_v6_sockets_meta(nq, &v6sm);
for (int i = 0; i < v6sm_cnt; i++)
FMT_ADD(" v6sock[%d]: id=%u type=%u\n", i, v6sm[i].id, v6sm[i].type);
const struct NODEINFO_IPV6_ADDR* v6a;
int v6a_cnt = get_node_v6_addrs(nq, &v6a);
for (int i = 0; i < v6a_cnt; i++)
FMT_ADD(" v6addr[%d]: %s:%u type=%u sock=%u\n",
i, ip_to_str(v6a[i].addr, AF_INET6).str, v6a[i].port,
v6a[i].type, v6a[i].socket_id);
const struct NODEINFO_IPV4_SUBNET* sub4;
int sub4_cnt = get_node_routes(nq, &sub4);
for (int i = 0; i < sub4_cnt; i++)
FMT_ADD(" v4sub[%d]: %s/%d\n", i,
ip_to_str(sub4[i].addr, AF_INET).str, sub4[i].prefix_length);
const struct NODEINFO_IPV6_SUBNET* sub6;
int sub6_cnt = get_node_v6_routes(nq, &sub6);
for (int i = 0; i < sub6_cnt; i++)
FMT_ADD(" v6sub[%d]: %s/%d\n", i,
ip_to_str(sub6[i].addr, AF_INET6).str, sub6[i].prefix_length);
if (ni->tranzit_nodes > 0) {
const uint8_t* dyn = node_dyn_start(ni);
dyn = skip_name(dyn, ni->node_name_len);
dyn = skip_v4_sockets_meta(dyn, ni->local_v4_sockets);
dyn = skip_v4_addrs(dyn, ni->local_v4_addrs);
dyn = skip_v6_sockets_meta(dyn, ni->local_v6_sockets);
dyn = skip_v6_addrs(dyn, ni->local_v6_addrs);
dyn = skip_v4_subnets(dyn, ni->local_v4_subnets);
dyn += ni->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
const struct NODEINFO_TRANZIT_NODE* tr = (const struct NODEINFO_TRANZIT_NODE*)dyn;
for (uint8_t i = 0; i < ni->tranzit_nodes; i++)
FMT_ADD(" tranzit[%d]: node=%016llx rtt=%u linkq=%u\n",
i, (unsigned long long)tr[i].node_id, tr[i].rtt, tr[i].link_q);
}
if (nq->paths) {
int path_idx = 0;
struct ll_entry* pe = nq->paths->head;
while (pe) {
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe;
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
" path[%d]: conn=%s hop_count=%u hops=[",
path_idx, path->conn ? path->conn->log_name : "NULL", path->hop_count);
for (uint8_t hi = 0; hi < path->hop_count; hi++)
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
"%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]);
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n");
path_idx++;
pe = pe->next;
}
}
struct NODE_CONNECTIVITY* c = &nq->connectivity;
if (is_node_connectivity_active(c)) {
FMT_ADD(" connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu\n",
probe_status_str(c->probe_status),
probe_result_str(c->interface_status), c->interface_min_rtt,
probe_result_str(c->nat_status), c->nat_min_rtt,
probe_result_str(c->real_status), c->real_min_rtt,
c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt,
(unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time);
}
if (nq->conn_mgr_type != CONN_TYPE_NONE) {
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
" conn_mgr: type=%s intermediates=[",
conn_mgr_type_str(nq->conn_mgr_type));
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++)
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
"%s%016llx", i ? "," : "",
(unsigned long long)nq->conn_mgr_intermediaries[i]);
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n");
}
#undef FMT_ADD
e = e->next;
idx++;
}
if ((size_t)pos >= buf_size) {
pos = (int)buf_size - 14;
if (pos < 0) pos = 0;
pos += snprintf(buf + pos, buf_size - pos, "\n[TRUNCATED]\n");
}
return pos;
}
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
if (!instance || !bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");

1
src/route_node.h

@ -188,5 +188,6 @@ int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR *
struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id);
void route_node_dump_all(struct ROUTE_BGP* bgp);
int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size);
#endif // ROUTE_NODE_H

66
tools/etcpmon/etcpmon_client.c

@ -355,6 +355,11 @@ int etcpmon_client_request_metrics(struct etcpmon_client* client) {
return 0;
}
static void action_auto_callback(void* arg, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size) {
(void)arg; (void)msg_type; (void)payload; (void)payload_size;
/* legacy callback is called via on_action_result in handle_response */
}
int etcpmon_client_send_action(struct etcpmon_client* client, const char* action) {
if (!client || client->sock == INVALID_SOCKET || !action) return -1;
@ -363,37 +368,8 @@ int etcpmon_client_send_action(struct etcpmon_client* client, const char* action
strncpy(cmd.action, action, sizeof(cmd.action) - 1);
cmd.action[sizeof(cmd.action) - 1] = '\0';
struct etcpmon_msg_header hdr;
etcpmon_build_header(&hdr, sizeof(cmd), ETCPMON_CMD_ACTION, 0);
if (client->log_file) {
log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr));
fprintf(client->log_file, "%llu: [LOG] CMD_ACTION '%s'\n",
(unsigned long long)get_timestamp_ms(), cmd.action);
fflush(client->log_file);
}
int sent = send(client->sock, (const char*)&hdr, sizeof(hdr), 0);
if (sent != sizeof(hdr)) {
if (client->log_file) {
fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_ACTION header: %d\n",
(unsigned long long)get_timestamp_ms(), WSAGetLastError());
fflush(client->log_file);
}
return -1;
}
sent = send(client->sock, (const char*)&cmd, sizeof(cmd), 0);
if (sent != sizeof(cmd)) {
if (client->log_file) {
fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_ACTION payload: %d\n",
(unsigned long long)get_timestamp_ms(), WSAGetLastError());
fflush(client->log_file);
}
return -1;
}
return 0;
return etcpmon_client_send_request(client, ETCPMON_CMD_ACTION, &cmd, sizeof(cmd),
action_auto_callback, NULL);
}
/* Send request with callback - queued version */
@ -589,13 +565,13 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type,
if (payload_size >= sizeof(struct etcpmon_rsp_error)) {
struct etcpmon_rsp_error* rsp = (struct etcpmon_rsp_error*)payload;
const char* msg = (const char*)(payload + sizeof(*rsp));
if (client->log_file) {
fprintf(client->log_file, "%llu: [LOG] RSP_ERROR code=%d msg='%s'\n",
fprintf(client->log_file, "%llu: [LOG] RSP_ERROR code=%d msg='%s'\n",
(unsigned long long)get_timestamp_ms(), rsp->error_code, msg);
fflush(client->log_file);
}
if (client->on_error) {
client->on_error(msg, client->user_data);
}
@ -603,6 +579,23 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type,
break;
}
case ETCPMON_RSP_ACTION_RESULT: {
if (payload_size >= sizeof(struct etcpmon_rsp_action_result)) {
const char* text = (const char*)(payload + sizeof(struct etcpmon_rsp_action_result));
if (client->log_file) {
fprintf(client->log_file, "%llu: [LOG] RSP_ACTION_RESULT len=%u\n",
(unsigned long long)get_timestamp_ms(), payload_size);
fflush(client->log_file);
}
if (client->on_action_result) {
client->on_action_result(text, client->user_data);
}
}
break;
}
case ETCPMON_RSP_DEBUG_CONFIG: {
if (payload_size >= 2) {
client->debug_global_level = payload[0];
@ -692,7 +685,8 @@ static void handle_response_with_callback(struct etcpmon_client* client, uint8_t
* Это позволяет использовать и старый код, и новый асинхронный send_request одновременно */
if (seq_id == 0 || msg_type == ETCPMON_RSP_CONN_LIST ||
msg_type == ETCPMON_RSP_SOCKET_LIST || msg_type == ETCPMON_RSP_DEBUG_CONFIG ||
msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR) {
msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR ||
msg_type == ETCPMON_RSP_ACTION_RESULT) {
handle_response(client, msg_type, payload, payload_size);
}
}
@ -856,6 +850,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client,
struct etcpmon_link_metrics*,
uint8_t, void*),
void (*on_error)(const char*, void*),
void (*on_action_result)(const char*, void*),
void* user_data) {
if (!client) return;
@ -865,6 +860,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client,
client->on_socket_list = on_socket_list;
client->on_metrics = on_metrics;
client->on_error = on_error;
client->on_action_result = on_action_result;
client->user_data = user_data;
}

2
tools/etcpmon/etcpmon_client.h

@ -105,6 +105,7 @@ struct etcpmon_client {
struct etcpmon_link_metrics* links,
uint8_t links_count, void* user_data);
void (*on_error)(const char* msg, void* user_data);
void (*on_action_result)(const char* text, void* user_data);
void* user_data;
uint64_t last_metrics_request_ms;
@ -182,6 +183,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client,
struct etcpmon_link_metrics*,
uint8_t, void*),
void (*on_error)(const char*, void*),
void (*on_action_result)(const char*, void*),
void* user_data);
/* Get connection name by peer_id */

77
tools/etcpmon/etcpmon_gui.c

@ -27,6 +27,7 @@
static struct etcpmon_app* g_app = NULL;
/* Forward declarations */
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK ActionResultWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static void CreateControls(struct etcpmon_app* app);
static void OnConnect(struct etcpmon_app* app);
static void OnDisconnect(struct etcpmon_app* app);
@ -47,6 +48,7 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
struct etcpmon_link_metrics* links,
uint8_t links_count, void* user_data);
static void on_error(const char* msg, void* user_data);
static void on_action_result(const char* text, void* user_data);
int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) {
if (!app) return -1;
@ -97,6 +99,20 @@ int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) {
wcexGraph.hIconSm = NULL;
RegisterClassExA(&wcexGraph);
/* Register ActionResult window class */
WNDCLASSEXA wcexAR;
memset(&wcexAR, 0, sizeof(wcexAR));
wcexAR.cbSize = sizeof(wcexAR);
wcexAR.style = CS_HREDRAW | CS_VREDRAW;
wcexAR.lpfnWndProc = ActionResultWndProc;
wcexAR.hInstance = hInstance;
wcexAR.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcexAR.hCursor = LoadCursor(NULL, IDC_ARROW);
wcexAR.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wcexAR.lpszClassName = "ETCPMonActionResult";
wcexAR.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassExA(&wcexAR);
/* Initialize Winsock */
WSADATA wsaData;
@ -141,6 +157,7 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) {
etcpmon_client_set_callbacks(app->client,
on_connected, on_disconnected,
on_conn_list, on_socket_list, on_metrics, on_error,
on_action_result,
app);
}
@ -1302,6 +1319,60 @@ static void on_error(const char* msg, void* user_data) {
struct etcpmon_app* app = (struct etcpmon_app*)user_data;
etcpmon_gui_set_status(app, msg);
}
static LRESULT CALLBACK ActionResultWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_CREATE: {
HWND hEdit = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | WS_VSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0, hWnd, (HMENU)1, ((CREATESTRUCTA*)lParam)->hInstance, NULL);
HFONT hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT);
SendMessage(hEdit, WM_SETFONT, (WPARAM)hFont, TRUE);
SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)hEdit);
break;
}
case WM_SIZE:
if (GetWindowLongPtrA(hWnd, GWLP_USERDATA))
MoveWindow((HWND)GetWindowLongPtrA(hWnd, GWLP_USERDATA), 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
break;
case WM_CLOSE:
ShowWindow(hWnd, SW_HIDE);
return 0;
default:
return DefWindowProcA(hWnd, message, wParam, lParam);
}
return 0;
}
static void on_action_result(const char* text, void* user_data) {
struct etcpmon_app* app = (struct etcpmon_app*)user_data;
if (!app || !app->hWndMain || !text || !text[0]) return;
if (!IsWindow(app->hActionResultWnd)) {
RECT rc;
GetWindowRect(app->hWndMain, &rc);
int w = rc.right - rc.left;
int h = rc.bottom - rc.top;
app->hActionResultWnd = CreateWindowExA(0, "ETCPMonActionResult", "Action Result",
WS_OVERLAPPEDWINDOW | WS_SIZEBOX,
rc.left, rc.top, w, h,
NULL, NULL, app->hInstance, NULL);
app->hActionResultEdit = (HWND)GetWindowLongPtrA(app->hActionResultWnd, GWLP_USERDATA);
ShowWindow(app->hActionResultWnd, SW_SHOW);
UpdateWindow(app->hActionResultWnd);
}
if (IsWindow(app->hActionResultEdit)) {
SendMessageA(app->hActionResultEdit, EM_SETSEL, -1, -1);
SendMessageA(app->hActionResultEdit, EM_REPLACESEL, FALSE, (LPARAM)text);
int lines = (int)SendMessage(app->hActionResultEdit, EM_GETLINECOUNT, 0, 0);
SendMessage(app->hActionResultEdit, EM_LINESCROLL, 0, lines);
}
}
int etcpmon_gui_run(struct etcpmon_app* app) {
if (!app || !app->hWndMain) return -1;
ShowWindow(app->hWndMain, SW_SHOW);
@ -1332,6 +1403,12 @@ int etcpmon_gui_run(struct etcpmon_app* app) {
continue;
}
}
if (hFocus == app->hEditAction) {
if (msg.wParam == VK_RETURN) {
OnAction(app);
continue;
}
}
}
TranslateMessage(&msg);
DispatchMessageA(&msg);

4
tools/etcpmon/etcpmon_gui.h

@ -212,6 +212,10 @@ struct etcpmon_app {
HWND hEditAction;
HWND hBtnAction;
/* Action result popup */
HWND hActionResultWnd;
HWND hActionResultEdit;
/* ETCP metrics controls */
HWND hEditEtcpName;
HWND hEditEtcpRttLast;

13
tools/etcpmon/etcpmon_protocol.h

@ -31,7 +31,7 @@ extern "C" {
#define NAT_VERIFIED_EIM 5 /* EIM NAT */
#define NAT_VERIFIED_STRICT 6 /* strict NAT */
#define NAT_VERIFIED_DIRECT 7
#define ETCPMON_MAX_MSG_SIZE 4096
#define ETCPMON_MAX_MSG_SIZE 32768
#define ETCPMON_MAX_CONN_NAME 32
#define ETCPMON_MAX_CONNECTIONS 250
#define ETCPMON_MAX_LINKS 16
@ -63,6 +63,7 @@ extern "C" {
#define ETCPMON_RSP_NODE_INFO 0x85 /* Single node info (payload: raw BGP_NODEINFO_PACKET) */
#define ETCPMON_RSP_NODE_INFO_END 0x86 /* End of node list (no payload) */
#define ETCPMON_RSP_NODE_REMOVED 0x88 /* Node removed (payload: uint64_t node_id) */
#define ETCPMON_RSP_ACTION_RESULT 0x89 /* Action result text response */
#define ETCPMON_RSP_ERROR 0xFF /* Error response */
/* Error codes for RSP_ERROR */
@ -309,6 +310,12 @@ struct etcpmon_cmd_set_debug {
uint8_t levels[ETCPMON_MAX_DEBUG_CATEGORIES]; /* Actual length = category_count */
};
/* RSP_ACTION_RESULT: Text result of an action command */
struct etcpmon_rsp_action_result {
uint8_t code; /* 0=success, 1=error */
/* Followed by null-terminated UTF-8 text string */
};
/* RSP_ERROR: Error response */
struct etcpmon_rsp_error {
uint8_t error_code; /* Error code */
@ -340,6 +347,10 @@ struct etcpmon_rsp_error {
#define ETCPMON_ERROR_SIZE(msg_len) \
(sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_error) + (msg_len) + 1)
/* Calculate size of action result response */
#define ETCPMON_ACTION_RESULT_SIZE(text_len) \
(sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_action_result) + (text_len) + 1)
/* ============================================================================
* Utility Functions (optional, for both client and server)
* ============================================================================ */

Loading…
Cancel
Save