/* * etcpmon_client.c - ETCP Monitor Client Network Implementation */ #include "etcpmon_client.h" #include "etcpmon_graph.h" #include #include #include #include #include //#pragma comment(lib, "ws2_32.lib") /* Log file name */ #define LOG_FILENAME "etcpmon.log" #define REQUEST_TIMEOUT_MS 5000 static struct etcpmon_pending_request* find_pending_request(struct etcpmon_client* client, uint8_t seq_id); /* Get current timestamp in milliseconds (Unix epoch) */ uint64_t get_timestamp_ms(void) { FILETIME ft; GetSystemTimeAsFileTime(&ft); /* Convert FILETIME (100-nanosecond intervals since 1601) to milliseconds */ uint64_t time = ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime; /* Convert from 1601 to 1970 epoch (11644473600000 milliseconds) */ return (time / 10000) - 11644473600000ULL; } /* Log hex data to file */ static void log_hex_data(FILE* log, const char* prefix, const uint8_t* data, size_t len) { if (!log || !data || len == 0) return; fprintf(log, "%llu: [%s] ", (unsigned long long)get_timestamp_ms(), prefix); for (size_t i = 0; i < len && i < 256; i++) { /* Limit to 256 bytes per line */ fprintf(log, "%02X ", data[i]); } if (len > 256) { fprintf(log, "... (%llu bytes total)", (unsigned long long)len); } fprintf(log, "\n"); fflush(log); } void etcpmon_client_init(struct etcpmon_client* client) { if (!client) return; memset(client, 0, sizeof(*client)); client->sock = INVALID_SOCKET; client->connected = 0; client->fully_connected = 0; client->log_file = NULL; client->next_seq_id = 1; /* Start from 1, 0 = broadcast */ memset(&client->history, 0, sizeof(client->history)); for (int i = 0; i < GRAPH_METRIC_COUNT; i++) { client->history.min_val[i] = 1e9f; client->history.max_val[i] = -1e9f; } client->history.last_minmax_update = 0; /* Новый таймер авто-запроса метрик */ client->last_metrics_request_ms = 0; } void etcpmon_client_cleanup(struct etcpmon_client* client) { if (!client) return; etcpmon_client_disconnect(client); /* Free pending requests */ struct etcpmon_pending_request* req = client->pending_head; while (req) { struct etcpmon_pending_request* next = req->next; free(req); req = next; } client->pending_head = NULL; client->pending_tail = NULL; /* Close log file */ if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Log closed\n", (unsigned long long)get_timestamp_ms()); fclose(client->log_file); client->log_file = NULL; } /* Free cached data */ if (client->conn_list) { free(client->conn_list); client->conn_list = NULL; } if (client->socket_list) { free(client->socket_list); client->socket_list = NULL; } if (client->last_metrics) { free(client->last_metrics); client->last_metrics = NULL; } if (client->last_links) { free(client->last_links); client->last_links = NULL; } } int etcpmon_client_connect(struct etcpmon_client* client, const char* addr, uint16_t port) { if (!client || !addr) return -1; if (client->connected) { etcpmon_client_disconnect(client); } /* Open log file (truncate on start) */ if (!client->log_file) { client->log_file = fopen(LOG_FILENAME, "w"); if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] ETCP Monitor communication log started\n", (unsigned long long)get_timestamp_ms()); fprintf(client->log_file, "%llu: [LOG] Connecting to %s:%d\n", (unsigned long long)get_timestamp_ms(), addr, port); fflush(client->log_file); } } /* Create socket */ client->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (client->sock == INVALID_SOCKET) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Failed to create socket\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); } if (client->on_error) { client->on_error("Failed to create socket", client->user_data); } return -1; } /* Set non-blocking mode */ u_long nonblock = 1; ioctlsocket(client->sock, FIONBIO, &nonblock); /* Setup address */ struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); if (inet_pton(AF_INET, addr, &server_addr.sin_addr) != 1) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Invalid address: %s\n", (unsigned long long)get_timestamp_ms(), addr); fflush(client->log_file); } closesocket(client->sock); client->sock = INVALID_SOCKET; if (client->on_error) { client->on_error("Invalid address", client->user_data); } return -1; } /* Connect (non-blocking) */ int result = connect(client->sock, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (result == SOCKET_ERROR) { int err = WSAGetLastError(); if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Connection failed: %d\n", (unsigned long long)get_timestamp_ms(), err); fflush(client->log_file); } closesocket(client->sock); client->sock = INVALID_SOCKET; if (client->on_error) { char msg[256]; snprintf(msg, sizeof(msg), "Connection failed: %d", err); client->on_error(msg, client->user_data); } return -1; } /* Connection in progress, will complete asynchronously */ } strncpy(client->server_addr, addr, sizeof(client->server_addr) - 1); client->server_port = port; client->connected = 1; client->fully_connected = 0; if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Socket created, connection in progress\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); } return 0; } void etcpmon_client_disconnect(struct etcpmon_client* client) { if (!client) return; if (client->sock != INVALID_SOCKET) { /* Send disconnect command */ if (client->connected) { struct etcpmon_msg_header hdr; etcpmon_build_header(&hdr, 0, ETCPMON_CMD_DISCONNECT, 0); if (client->log_file) { log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr)); fprintf(client->log_file, "%llu: [LOG] Sending CMD_DISCONNECT\n", (unsigned long long)get_timestamp_ms()); } send(client->sock, (const char*)&hdr, sizeof(hdr), 0); } if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Connection closed\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); } closesocket(client->sock); client->sock = INVALID_SOCKET; } client->connected = 0; client->fully_connected = 0; client->recv_len = 0; client->selected_peer_id = 0; client->last_metrics_request_ms = 0; free(client->conn_list); client->conn_list = NULL; client->conn_count = 0; free(client->socket_list); client->socket_list = NULL; client->socket_count = 0; free(client->last_metrics); client->last_metrics = NULL; free(client->last_links); client->last_links = NULL; client->last_links_count = 0; memset(&client->history, 0, sizeof(client->history)); /* Очистить очередь pending requests и сбросить seq_id */ struct etcpmon_pending_request* req = client->pending_head; while (req) { struct etcpmon_pending_request* next = req->next; free(req); req = next; } client->pending_head = NULL; client->pending_tail = NULL; client->next_seq_id = 1; client->last_response_seq_id = 0; } int etcpmon_client_is_connected(const struct etcpmon_client* client) { return client ? client->connected : 0; } int etcpmon_client_request_list(struct etcpmon_client* client) { if (!client || client->sock == INVALID_SOCKET) return -1; struct etcpmon_msg_header hdr; etcpmon_build_header(&hdr, 0, ETCPMON_CMD_LIST_CONN, 0); /* seq=0 = legacy режим */ if (client->log_file) { log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr)); fprintf(client->log_file, "%llu: [LOG] CMD_LIST_CONN (seq=0)\n", (unsigned long long)get_timestamp_ms()); 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_LIST_CONN: %d\n", (unsigned long long)get_timestamp_ms(), WSAGetLastError()); fflush(client->log_file); } return -1; } return 0; } int etcpmon_client_select_connection(struct etcpmon_client* client, uint64_t peer_node_id) { if (!client || client->sock == INVALID_SOCKET) return -1; /* SELECT is special - it's not a request/response, it just sets state * So we send with seq_id=0 (broadcast) */ uint8_t buffer[sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_cmd_select)]; struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer; struct etcpmon_cmd_select* cmd = (struct etcpmon_cmd_select*)(buffer + sizeof(*hdr)); etcpmon_build_header(hdr, sizeof(*cmd), ETCPMON_CMD_SELECT_CONN, 0); /* seq_id=0 for SELECT */ cmd->peer_node_id = peer_node_id; if (client->log_file) { log_hex_data(client->log_file, "TX", buffer, sizeof(buffer)); fprintf(client->log_file, "%llu: [LOG] CMD_SELECT_CONN peer_id=%016llX\n", (unsigned long long)get_timestamp_ms(), (unsigned long long)peer_node_id); fflush(client->log_file); } int sent = send(client->sock, (const char*)buffer, sizeof(buffer), 0); if (sent != sizeof(buffer)) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_SELECT_CONN: %d\n", (unsigned long long)get_timestamp_ms(), WSAGetLastError()); fflush(client->log_file); } return -1; } client->selected_peer_id = peer_node_id; client->last_metrics_request_ms = 0; memset(&client->history, 0, sizeof(client->history)); return 0; } int etcpmon_client_request_socket_list(struct etcpmon_client* client) { if (!client || client->sock == INVALID_SOCKET) return -1; struct etcpmon_msg_header hdr; etcpmon_build_header(&hdr, 0, ETCPMON_CMD_LIST_SOCKETS, 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_LIST_SOCKETS (seq=0)\n", (unsigned long long)get_timestamp_ms()); 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_LIST_SOCKETS: %d\n", (unsigned long long)get_timestamp_ms(), WSAGetLastError()); fflush(client->log_file); } return -1; } return 0; } int etcpmon_client_request_metrics(struct etcpmon_client* client) { if (!client || client->sock == INVALID_SOCKET) return -1; struct etcpmon_msg_header hdr; etcpmon_build_header(&hdr, 0, ETCPMON_CMD_GET_METRICS, 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_GET_METRICS\n", (unsigned long long)get_timestamp_ms()); 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_GET_METRICS: %d\n", (unsigned long long)get_timestamp_ms(), WSAGetLastError()); fflush(client->log_file); } return -1; } 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; struct etcpmon_cmd_action cmd; memset(&cmd, 0, sizeof(cmd)); strncpy(cmd.action, action, sizeof(cmd.action) - 1); cmd.action[sizeof(cmd.action) - 1] = '\0'; return etcpmon_client_send_request(client, ETCPMON_CMD_ACTION, &cmd, sizeof(cmd), action_auto_callback, NULL); } /* Send request with callback - queued version */ int etcpmon_client_send_request(struct etcpmon_client* client, uint8_t type, const void* payload, uint16_t payload_size, void (*callback)(void* arg, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size), void* arg) { if (!client || client->sock == INVALID_SOCKET || !callback) return -1; if (payload_size > ETCPMON_MAX_MSG_SIZE - sizeof(struct etcpmon_msg_header) || (payload_size && !payload)) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Invalid request payload: type=0x%02X size=%u\n", (unsigned long long)get_timestamp_ms(), type, payload_size); fflush(client->log_file); } return -1; } /* Не переиспользуем seq_id ещё ожидающего ответа запроса. */ int attempts = 0; while (find_pending_request(client, client->next_seq_id) && attempts++ < 255) { if (++client->next_seq_id == 0) client->next_seq_id = 1; } if (attempts >= 255) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] All request sequence IDs are in use\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); } return -1; } /* Allocate pending request */ struct etcpmon_pending_request* req = (struct etcpmon_pending_request*)malloc(sizeof(*req)); if (!req) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Failed to allocate pending request\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); } return -1; } /* Assign sequence ID */ req->seq_id = client->next_seq_id++; if (client->next_seq_id == 0) client->next_seq_id = 1; /* Skip 0 (broadcast) */ req->callback = callback; req->arg = arg; req->type = type; req->sent_at = GetTickCount(); req->next = NULL; /* Build message with seq_id */ uint8_t buffer[ETCPMON_MAX_MSG_SIZE]; struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer; etcpmon_build_header(hdr, payload_size, type, req->seq_id); if (payload && payload_size > 0) { memcpy(buffer + sizeof(*hdr), payload, payload_size); } uint16_t total_size = sizeof(*hdr) + payload_size; if (client->log_file) { log_hex_data(client->log_file, "TX", buffer, total_size); fprintf(client->log_file, "%llu: [LOG] CMD 0x%02X seq=%d queued\n", (unsigned long long)get_timestamp_ms(), type, req->seq_id); fflush(client->log_file); } int sent = send(client->sock, (const char*)buffer, total_size, 0); if (sent != total_size) { free(req); if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Failed to send request: type=0x%02X sent=%d/%u error=%d\n", (unsigned long long)get_timestamp_ms(), type, sent, total_size, sent == SOCKET_ERROR ? WSAGetLastError() : 0); fflush(client->log_file); } /* После частичной отправки следующий пакет уже нельзя писать в этот поток. */ etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } /* В очередь попадает только полностью отправленный запрос. */ if (client->pending_tail) client->pending_tail->next = req; else client->pending_head = req; client->pending_tail = req; return 0; } static void handle_response(struct etcpmon_client* client, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size) { switch (msg_type) { case ETCPMON_RSP_CONN_LIST: { if (payload_size < sizeof(struct etcpmon_rsp_conn_list)) { if (client->log_file) fprintf(client->log_file, "[ERROR] Empty RSP_CONN_LIST payload\n"); break; } struct etcpmon_rsp_conn_list* rsp = (struct etcpmon_rsp_conn_list*)payload; uint8_t count = rsp->count; if (count > ETCPMON_MAX_CONNECTIONS || payload_size != sizeof(*rsp) + count * sizeof(struct etcpmon_conn_info)) { if (client->log_file) fprintf(client->log_file, "[ERROR] Invalid RSP_CONN_LIST: count=%u size=%u\n", count, payload_size); break; } /* Update cached list */ struct etcpmon_conn_info* list = count ? malloc(count * sizeof(*list)) : NULL; if (count && !list) { if (client->log_file) fprintf(client->log_file, "[ERROR] Failed to allocate connection list: count=%u\n", count); break; } { if (count) memcpy(list, payload + sizeof(*rsp), count * sizeof(*list)); for (uint8_t i = 0; i < count; i++) list[i].name[ETCPMON_MAX_CONN_NAME - 1] = '\0'; free(client->conn_list); client->conn_list = list; client->conn_count = count; if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] RSP_CONN_LIST count=%d\n", (unsigned long long)get_timestamp_ms(), count); for (uint8_t i = 0; i < count; i++) { fprintf(client->log_file, "%llu: [LOG] [%d] peer_id=%016llX name='%s'\n", (unsigned long long)get_timestamp_ms(), i, (unsigned long long)client->conn_list[i].peer_node_id, client->conn_list[i].name); } fflush(client->log_file); } if (client->on_conn_list) { client->on_conn_list(client->conn_list, count, client->user_data); } } break; } case ETCPMON_RSP_SOCKET_LIST: { if (payload_size < sizeof(struct etcpmon_rsp_socket_list)) break; struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)payload; uint8_t count = rsp->count; if (count > ETCPMON_MAX_CONNECTIONS || payload_size != sizeof(*rsp) + count * sizeof(struct etcpmon_socket_info)) { if (client->log_file) fprintf(client->log_file, "[ERROR] Invalid RSP_SOCKET_LIST: count=%u size=%u\n", count, payload_size); break; } struct etcpmon_socket_info* list = count ? malloc(count * sizeof(*list)) : NULL; if (count && !list) { if (client->log_file) fprintf(client->log_file, "[ERROR] Failed to allocate socket list: count=%u\n", count); break; } { if (count) memcpy(list, payload + sizeof(*rsp), count * sizeof(*list)); for (uint8_t i = 0; i < count; i++) { list[i].name[sizeof(list[i].name) - 1] = '\0'; list[i].ip[sizeof(list[i].ip) - 1] = '\0'; list[i].nat_ip[sizeof(list[i].nat_ip) - 1] = '\0'; } free(client->socket_list); client->socket_list = list; client->socket_count = count; if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] RSP_SOCKET_LIST count=%d\n", (unsigned long long)get_timestamp_ms(), count); fflush(client->log_file); } if (client->on_socket_list) { client->on_socket_list(client->socket_list, count, client->user_data); } } break; } case ETCPMON_RSP_METRICS: { if (payload_size < sizeof(struct etcpmon_rsp_metrics)) break; struct etcpmon_rsp_metrics* rsp = (struct etcpmon_rsp_metrics*)payload; uint8_t links_count = rsp->etcp.links_count; if (rsp->etcp.peer_node_id != client->selected_peer_id || client->selected_peer_id == 0) { if (client->log_file) { fprintf(client->log_file, "[LOG] Ignoring stale metrics: peer=%016llX selected=%016llX\n", (unsigned long long)rsp->etcp.peer_node_id, (unsigned long long)client->selected_peer_id); } break; } if (links_count > ETCPMON_MAX_LINKS || payload_size != sizeof(*rsp) + links_count * sizeof(struct etcpmon_link_metrics)) { if (client->log_file) fprintf(client->log_file, "[ERROR] Invalid RSP_METRICS: links=%u size=%u\n", links_count, payload_size); break; } /* Update cached metrics */ struct etcpmon_rsp_metrics* metrics = malloc(sizeof(*metrics)); struct etcpmon_link_metrics* links = links_count ? malloc(links_count * sizeof(*links)) : NULL; if (!metrics || (links_count && !links)) { free(metrics); free(links); if (client->log_file) fprintf(client->log_file, "[ERROR] Failed to allocate metrics: links=%u\n", links_count); break; } { free(client->last_metrics); free(client->last_links); client->last_metrics = metrics; client->last_links = links; memcpy(client->last_metrics, rsp, sizeof(*rsp)); if (links_count) memcpy(client->last_links, payload + sizeof(*rsp), links_count * sizeof(struct etcpmon_link_metrics)); client->last_links_count = links_count; if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] RSP_METRICS rtt=%u bytes=%llu links=%d\n", (unsigned long long)get_timestamp_ms(), rsp->etcp.rtt_last, (unsigned long long)rsp->etcp.bytes_sent_total, links_count); fflush(client->log_file); } etcpmon_client_add_to_history(client, client->last_metrics); if (client->on_metrics) { client->on_metrics(client->last_metrics, client->last_links, links_count, client->user_data); } } break; } case ETCPMON_RSP_ERROR: { if (payload_size > sizeof(struct etcpmon_rsp_error) && memchr(payload + sizeof(struct etcpmon_rsp_error), 0, 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", (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); } } else if (client->log_file) { fprintf(client->log_file, "[ERROR] Invalid RSP_ERROR string: size=%u\n", payload_size); } break; } case ETCPMON_RSP_ACTION_RESULT: { if (payload_size > sizeof(struct etcpmon_rsp_action_result) && memchr(payload + sizeof(struct etcpmon_rsp_action_result), 0, 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); } } else if (client->log_file) { fprintf(client->log_file, "[ERROR] Invalid RSP_ACTION_RESULT string: size=%u\n", payload_size); } break; } case ETCPMON_RSP_DEBUG_CONFIG: { const uint8_t* names_end = payload_size > 2 ? memchr(payload + 2, 0, payload_size - 2) : NULL; if (!names_end || payload[1] > ETCPMON_MAX_DEBUG_CATEGORIES || (size_t)(names_end - payload) + 1 + payload[1] != payload_size) { if (client->log_file) fprintf(client->log_file, "[ERROR] Invalid RSP_DEBUG_CONFIG: size=%u\n", payload_size); break; } { client->debug_global_level = payload[0]; client->debug_category_count = payload[1]; uint16_t names_len = (uint16_t)(names_end - payload - 2) + 1; if (2 + names_len <= payload_size) { size_t copy_len = names_len < sizeof(client->debug_category_names) ? names_len : sizeof(client->debug_category_names) - 1; memcpy(client->debug_category_names, payload + 2, copy_len); client->debug_category_names[copy_len] = '\0'; uint16_t levels_off = 2 + names_len; uint8_t lvl_count = client->debug_category_count; if (lvl_count > ETCPMON_MAX_DEBUG_CATEGORIES) lvl_count = ETCPMON_MAX_DEBUG_CATEGORIES; for (uint8_t i = 0; i < lvl_count && levels_off + i < payload_size; i++) client->debug_category_levels[i] = payload[levels_off + i]; client->debug_config_dirty = 1; if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] RSP_DEBUG_CONFIG global=%d categories=%d\n", (unsigned long long)get_timestamp_ms(), client->debug_global_level, client->debug_category_count); fflush(client->log_file); } } } break; } } } /* Find pending request by seq_id */ static struct etcpmon_pending_request* find_pending_request(struct etcpmon_client* client, uint8_t seq_id) { struct etcpmon_pending_request* req = client->pending_head; while (req) { if (req->seq_id == seq_id) { return req; } req = req->next; } return NULL; } /* Remove pending request from queue */ static void remove_pending_request(struct etcpmon_client* client, struct etcpmon_pending_request* req) { struct etcpmon_pending_request** curr = &client->pending_head; struct etcpmon_pending_request* prev = NULL; while (*curr) { if (*curr == req) { *curr = req->next; if (client->pending_tail == req) { client->pending_tail = prev; } free(req); return; } prev = *curr; curr = &(*curr)->next; } } /* Handle response - find matching request and call callback */ static void handle_response_with_callback(struct etcpmon_client* client, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size) { uint8_t seq_id = client->last_response_seq_id; /* Find pending request */ struct etcpmon_pending_request* req = find_pending_request(client, seq_id); if (req) { /* Callback может отправить новый запрос или отключить клиент. */ void (*callback)(void*, uint8_t, const uint8_t*, uint16_t) = req->callback; void* arg = req->arg; remove_pending_request(client, req); if (callback) callback(arg, msg_type, payload, payload_size); if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Response handled, seq=%d removed from queue\n", (unsigned long long)get_timestamp_ms(), seq_id); fflush(client->log_file); } } else { /* No pending request - maybe broadcast/unsolicited or stale */ if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Response seq=%d - no pending request (broadcast?)\n", (unsigned long long)get_timestamp_ms(), seq_id); fflush(client->log_file); } } /* === НОВОЕ: ВСЕГДА вызываем legacy обработчики (on_conn_list / on_metrics / on_error) === * Это позволяет использовать и старый код, и новый асинхронный 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_ACTION_RESULT) { handle_response(client, msg_type, payload, payload_size); } } static void metrics_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 on_metrics вызывается автоматически ниже */ } int etcpmon_client_process(struct etcpmon_client* client) { if (!client || client->sock == INVALID_SOCKET) return -1; /* Проверка завершения non-blocking connect */ if (client->connected && !client->fully_connected) { fd_set writable, failed; FD_ZERO(&writable); FD_ZERO(&failed); FD_SET(client->sock, &writable); FD_SET(client->sock, &failed); struct timeval timeout = {0, 0}; int ready = select(0, NULL, &writable, &failed, &timeout); if (ready == 0) return 0; int error = 0; int len = sizeof(error); if (ready > 0 && getsockopt(client->sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len) == 0) { if (error == 0) { client->fully_connected = 1; if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Connection established\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); } if (client->on_connected) { client->on_connected(client->user_data); } return 0; } else if (error == WSAEINPROGRESS || error == WSAEALREADY) { return 0; } else { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Connect failed: %d\n", (unsigned long long)get_timestamp_ms(), error); fflush(client->log_file); } etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); if (client->on_error) { char msg[256]; snprintf(msg, sizeof(msg), "Connect failed: %d", error); client->on_error(msg, client->user_data); } return -1; } } if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Failed to check connect completion: %d\n", (unsigned long long)get_timestamp_ms(), WSAGetLastError()); fflush(client->log_file); } etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } /* Один запрос метрик в полёте; зависший сервер не раздувает очередь. */ int metrics_pending = 0; DWORD tick = GetTickCount(); for (struct etcpmon_pending_request* req = client->pending_head; req; req = req->next) { if (tick - req->sent_at >= REQUEST_TIMEOUT_MS) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Response timeout: type=0x%02X seq=%u elapsed=%lu ms\n", (unsigned long long)get_timestamp_ms(), req->type, req->seq_id, (unsigned long)(tick - req->sent_at)); fflush(client->log_file); } etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } if (req->type == ETCPMON_CMD_GET_METRICS) metrics_pending = 1; } /* Авто-запрос метрик каждые 100 мс (асинхронно) */ if (client->fully_connected && client->selected_peer_id != 0 && !metrics_pending) { uint64_t now = GetTickCount64(); if (now - client->last_metrics_request_ms >= ETCPMON_UPDATE_INTERVAL_MS) { if (etcpmon_client_send_request(client, ETCPMON_CMD_GET_METRICS, NULL, 0, metrics_auto_callback, NULL) != 0) { etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } client->last_metrics_request_ms = now; } } /* Приём данных */ uint8_t* buf = client->recv_buffer + client->recv_len; int buf_space = ETCPMON_MAX_MSG_SIZE - client->recv_len; if (buf_space <= 0) { client->recv_len = 0; /* защита от переполнения */ buf = client->recv_buffer; buf_space = ETCPMON_MAX_MSG_SIZE; } int received = recv(client->sock, (char*)buf, buf_space, 0); if (received == SOCKET_ERROR) { int err = WSAGetLastError(); if (err == WSAEWOULDBLOCK) return 0; if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] recv failed: %d\n", (unsigned long long)get_timestamp_ms(), err); fflush(client->log_file); } etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } if (received == 0) { if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Connection closed by server\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); } etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Received %d bytes (offset=%d)\n", (unsigned long long)get_timestamp_ms(), received, client->recv_len); log_hex_data(client->log_file, "RX", buf, received); } client->recv_len += received; /* === ОСНОВНОЙ ПАРСЕР (ИСПРАВЛЕННЫЙ) === */ int processed = 0; while (client->recv_len >= sizeof(struct etcpmon_msg_header)) { struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)client->recv_buffer; /* Валидация */ if (etcpmon_validate_header(hdr) != 0) { if (client->log_file) { fprintf(client->log_file, "%llu: [ERROR] Invalid header, skipping byte %02X\n", (unsigned long long)get_timestamp_ms(), client->recv_buffer[0]); fflush(client->log_file); } memmove(client->recv_buffer, client->recv_buffer + 1, client->recv_len - 1); client->recv_len--; continue; } /* hdr->size — это ПОЛНЫЙ размер сообщения (header + payload) */ uint16_t total_size = hdr->size; if (client->recv_len < total_size) { break; /* ждём остаток */ } uint16_t payload_size = total_size - sizeof(*hdr); uint8_t* payload = client->recv_buffer + sizeof(*hdr); client->last_response_seq_id = hdr->seq_id; if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] Parsed message type=0x%02X total=%d payload=%d seq=%d\n", (unsigned long long)get_timestamp_ms(), hdr->type, total_size, payload_size, hdr->seq_id); fflush(client->log_file); } handle_response_with_callback(client, hdr->type, payload, payload_size); if (!client->connected) return -1; processed++; /* Удаляем обработанное сообщение */ if (client->recv_len > total_size) { memmove(client->recv_buffer, client->recv_buffer + total_size, client->recv_len - total_size); } client->recv_len -= total_size; } return processed; } void etcpmon_client_set_callbacks(struct etcpmon_client* client, void (*on_connected)(void*), void (*on_disconnected)(void*), void (*on_conn_list)(struct etcpmon_conn_info*, uint8_t, void*), void (*on_socket_list)(struct etcpmon_socket_info*, uint8_t, void*), void (*on_metrics)(struct etcpmon_rsp_metrics*, 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; client->on_connected = on_connected; client->on_disconnected = on_disconnected; client->on_conn_list = on_conn_list; 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; } const char* etcpmon_client_get_conn_name(struct etcpmon_client* client, uint64_t peer_id) { if (!client || !client->conn_list) return NULL; for (uint8_t i = 0; i < client->conn_count; i++) { if (client->conn_list[i].peer_node_id == peer_id) { return client->conn_list[i].name; } } return NULL; } struct metrics_history* etcpmon_client_get_history(struct etcpmon_client* client) { if (!client) return NULL; return &client->history; } static void debug_config_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; } int etcpmon_client_request_debug_config(struct etcpmon_client* client) { if (!client || client->sock == INVALID_SOCKET) return -1; return etcpmon_client_send_request(client, ETCPMON_CMD_GET_DEBUG_CONFIG, NULL, 0, debug_config_auto_callback, NULL); } int etcpmon_client_send_debug_config(struct etcpmon_client* client, uint8_t global_level, uint8_t count, const uint8_t* levels) { if (!client || client->sock == INVALID_SOCKET || !levels || count == 0 || count > ETCPMON_MAX_DEBUG_CATEGORIES) return -1; uint8_t payload[2 + ETCPMON_MAX_DEBUG_CATEGORIES]; payload[0] = global_level; payload[1] = count; memcpy(payload + 2, levels, count); uint16_t payload_size = 2 + count; uint8_t buffer[sizeof(struct etcpmon_msg_header) + sizeof(payload)]; struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer; etcpmon_build_header(hdr, payload_size, ETCPMON_CMD_SET_DEBUG_CONFIG, 0); memcpy(buffer + sizeof(*hdr), payload, payload_size); if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] CMD_SET_DEBUG_CONFIG global=%d count=%d\n", (unsigned long long)get_timestamp_ms(), global_level, count); fflush(client->log_file); } int total_size = sizeof(*hdr) + payload_size; int sent = send(client->sock, (const char*)buffer, total_size, 0); if (sent != total_size) { if (client->log_file) { fprintf(client->log_file, "[ERROR] Failed to send debug config: sent=%d/%d error=%d\n", sent, total_size, sent == SOCKET_ERROR ? WSAGetLastError() : 0); fflush(client->log_file); } etcpmon_client_disconnect(client); if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } client->debug_global_level = global_level; for (uint8_t i = 0; i < count; i++) client->debug_category_levels[i] = levels[i]; return 0; } void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon_rsp_metrics* metrics) { if (!client || !metrics) return; struct metrics_history* h = &client->history; int idx = h->head; /* BBR per-link metrics (use first link as representative) */ if (client->last_links_count > 0 && client->last_links) { struct etcpmon_link_metrics* l = &client->last_links[0]; h->values[GRAPH_METRIC_INFLIGHT_BYTES][idx] = l->inflight_bytes / 1024.f; h->values[GRAPH_METRIC_INFLIGHT_LIM][idx] = l->inflight_lim_bytes / 1024.f; h->values[GRAPH_METRIC_MIN_RTT][idx] = l->bbr_min_rtt_us / 1000.f; h->values[GRAPH_METRIC_BW_HI][idx] = l->bbr_bw_hi / 125.f; h->values[GRAPH_METRIC_PACING_RATE][idx] = l->bbr_pacing_rate / 125.f; h->values[GRAPH_METRIC_BW_LO][idx] = l->bbr_bw_lo / 125.f; h->values[GRAPH_METRIC_INFLIGHT_HI][idx] = l->bbr_inflight_hi / 1024.f; h->values[GRAPH_METRIC_BBR_MODE][idx] = (float)l->bbr_mode; } h->head = (h->head + 1) % GRAPH_HISTORY_SIZE; if (h->count < GRAPH_HISTORY_SIZE) h->count++; if (client->log_file) { fprintf(client->log_file, "history updated: count=%d head=%d\n", h->count, h->head); fflush(client->log_file); } }