/** ****************************************************************************** * @file cmd_parser.c * @brief ASCII指令解析模块实现 * @author Application Layer * @version 1.1 ****************************************************************************** * @attention * 本模块实现ASCII文本指令的解析和处理 * 关键特性: * 1. 状态机解析,健壮可靠 * 2. 完善的安全防护(缓冲区边界检查、超时重置、字符过滤) * 3. 异或校验,FF特权后门 * 4. 支持RL、DI、ECHO指令 * * 修订历史: * v1.1 - 修复审查报告高危-6、中危-7/8 ****************************************************************************** */ #include "cmd_parser.h" #include "uart2_print.h" #include "io_monitor.h" #include "relay_control.h" #include #include #include // snprintf #include // atoi #define DEBUG_CMD_PARSER 1 #if DEBUG_CMD_PARSER #define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[CMD] " fmt "\r\n", ##__VA_ARGS__) #else #define DEBUG_LOG(fmt, ...) #endif typedef enum { PARSE_IDLE, PARSE_CMD, PARSE_PARAM1, PARSE_PARAM2, PARSE_CHECKSUM, PARSE_COMPLETE } parse_state_t; typedef struct { parse_state_t state; cmd_frame_t frame; uint8_t field_index; uint8_t checksum_acc; uint8_t cs_buffer[2]; uint8_t cs_index; uint32_t last_rx_tick; uint32_t error_count; uint32_t valid_count; } parser_context_t; static parser_context_t ctx; static void reset_parser(void) { ctx.state = PARSE_IDLE; ctx.field_index = 0; ctx.checksum_acc = 0; ctx.cs_index = 0; memset(&ctx.frame, 0, sizeof(ctx.frame)); } static bool is_valid_cmd_char(char c) { return isupper((unsigned char)c) || isdigit((unsigned char)c); } static bool is_valid_param_char(char c) { return isprint((unsigned char)c) && c != '*' && c != '\r' && c != '\n'; } static uint8_t hex_char_to_val(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; return 0; } static uint8_t hex_to_byte(char high, char low) { return (hex_char_to_val(high) << 4) | hex_char_to_val(low); } static uint8_t calc_checksum(const char *data, uint8_t len) { uint8_t cs = 0; for (uint8_t i = 0; i < len; i++) { cs ^= (uint8_t)data[i]; } return cs; } static void send_response_ok(const char *content) { char msg[64]; uint8_t cs; int len = snprintf(msg, sizeof(msg), "$OK,%s*", content); cs = calc_checksum(msg + 1, len - 1); snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs); UART2_Print_String(msg); } static void send_response_err(const char *err_code) { char msg[32]; uint8_t cs; int len = snprintf(msg, sizeof(msg), "$ERR,%s*", err_code); cs = calc_checksum(msg + 1, len - 1); snprintf(msg + len, sizeof(msg) - len, "%02X\r\n", cs); UART2_Print_String(msg); } static bool is_str_empty(const char *str) { return (str == NULL || str[0] == '\0'); } static bool is_str_numeric(const char *str) { if (is_str_empty(str)) { return false; } while (*str) { if (!isdigit((unsigned char)*str)) { return false; } str++; } return true; } static void process_cmd_frame(const cmd_frame_t *frame) { DEBUG_LOG("CMD=%s P1=%s P2=%s CS=%02X/%02X %s", frame->cmd, frame->param1, frame->param2, frame->received_cs, frame->calculated_cs, frame->skip_checksum ? "(skip)" : ""); if (strcmp(frame->cmd, "RL") == 0) { if (!is_str_numeric(frame->param1) || !is_str_numeric(frame->param2)) { send_response_err("PARAM"); DEBUG_LOG("Invalid RL params: not numeric"); return; } int relay_id = atoi(frame->param1); int state = atoi(frame->param2); if (relay_id >= 1 && relay_id <= 4 && (state == 0 || state == 1)) { Relay_SetState(relay_id, state ? true : false); char resp[32]; snprintf(resp, sizeof(resp), "RL,%d,%d", relay_id, state); send_response_ok(resp); DEBUG_LOG("Relay %d -> %s", relay_id, state ? "ON" : "OFF"); } else { send_response_err("PARAM"); DEBUG_LOG("Invalid RL params: id=%d state=%d", relay_id, state); } } else if (strcmp(frame->cmd, "DI") == 0) { if (is_str_empty(frame->param1) || strcmp(frame->param1, "0") == 0) { uint8_t states = IO_Monitor_GetAllStates(); char resp[32]; snprintf(resp, sizeof(resp), "DI,%d%d%d%d", (states >> 0) & 1, (states >> 1) & 1, (states >> 2) & 1, (states >> 3) & 1); send_response_ok(resp); DEBUG_LOG("DI all states: 0x%02X", states); } else if (is_str_numeric(frame->param1)) { int channel = atoi(frame->param1); if (channel >= 1 && channel <= 4) { uint8_t state = IO_Monitor_GetState(channel - 1); char resp[32]; snprintf(resp, sizeof(resp), "DI,%d,%d", channel, state); send_response_ok(resp); DEBUG_LOG("DI%d = %d", channel, state); } else { send_response_err("PARAM"); DEBUG_LOG("Invalid DI channel: %d", channel); } } else { send_response_err("PARAM"); DEBUG_LOG("Invalid DI param: not numeric"); } } else if (strcmp(frame->cmd, "ECHO") == 0) { send_response_ok("ECHO"); DEBUG_LOG("ECHO response sent"); } else { send_response_err("CMD"); DEBUG_LOG("Unknown command: %s", frame->cmd); } } void CmdParser_Init(void) { memset(&ctx, 0, sizeof(ctx)); ctx.state = PARSE_IDLE; DEBUG_LOG("Init OK"); } void CmdParser_FeedByte(uint8_t byte, uint32_t current_tick) { if (ctx.state != PARSE_IDLE && ctx.state != PARSE_COMPLETE) { if (current_tick - ctx.last_rx_tick >= PARSE_TIMEOUT_MS) { ctx.error_count++; DEBUG_LOG("Timeout, reset parser"); reset_parser(); if (byte == '$') { ctx.state = PARSE_CMD; } return; } } ctx.last_rx_tick = current_tick; switch (ctx.state) { case PARSE_IDLE: if (byte == '$') { reset_parser(); ctx.state = PARSE_CMD; } break; case PARSE_CMD: if (byte == ',') { ctx.frame.cmd[ctx.field_index] = '\0'; ctx.state = PARSE_PARAM1; ctx.field_index = 0; } else if (byte == '*') { ctx.frame.cmd[ctx.field_index] = '\0'; ctx.state = PARSE_CHECKSUM; ctx.field_index = 0; ctx.cs_index = 0; } else if (is_valid_cmd_char(byte)) { if (ctx.field_index < CMD_MAX_LEN - 1) { ctx.frame.cmd[ctx.field_index++] = byte; ctx.checksum_acc ^= byte; } else { ctx.error_count++; reset_parser(); } } else { ctx.error_count++; reset_parser(); } break; case PARSE_PARAM1: if (byte == ',') { ctx.frame.param1[ctx.field_index] = '\0'; ctx.state = PARSE_PARAM2; ctx.field_index = 0; } else if (byte == '*') { ctx.frame.param1[ctx.field_index] = '\0'; ctx.state = PARSE_CHECKSUM; ctx.field_index = 0; ctx.cs_index = 0; } else if (is_valid_param_char(byte)) { if (ctx.field_index < PARAM_MAX_LEN - 1) { ctx.frame.param1[ctx.field_index++] = byte; ctx.checksum_acc ^= byte; } else { ctx.error_count++; reset_parser(); } } else { ctx.error_count++; reset_parser(); } break; case PARSE_PARAM2: if (byte == '*') { ctx.frame.param2[ctx.field_index] = '\0'; ctx.state = PARSE_CHECKSUM; ctx.field_index = 0; ctx.cs_index = 0; } else if (is_valid_param_char(byte)) { if (ctx.field_index < PARAM_MAX_LEN - 1) { ctx.frame.param2[ctx.field_index++] = byte; ctx.checksum_acc ^= byte; } else { ctx.error_count++; reset_parser(); } } else { ctx.error_count++; reset_parser(); } break; case PARSE_CHECKSUM: if (byte == '\n') { ctx.frame.received_cs = hex_to_byte(ctx.cs_buffer[0], ctx.cs_buffer[1]); ctx.frame.calculated_cs = ctx.checksum_acc; ctx.frame.skip_checksum = (ctx.frame.received_cs == 0xFF); if (ctx.frame.skip_checksum || ctx.frame.received_cs == ctx.frame.calculated_cs) { ctx.frame.valid = true; ctx.state = PARSE_COMPLETE; ctx.valid_count++; } else { ctx.error_count++; DEBUG_LOG("Checksum error: recv=%02X calc=%02X", ctx.frame.received_cs, ctx.frame.calculated_cs); send_response_err("CS"); reset_parser(); } } else if (byte != '\r') { if (ctx.cs_index < 2) { ctx.cs_buffer[ctx.cs_index++] = byte; } } break; case PARSE_COMPLETE: reset_parser(); if (byte == '$') { ctx.state = PARSE_CMD; } break; } } void CmdParser_Task(void) { if (ctx.state == PARSE_COMPLETE && ctx.frame.valid) { process_cmd_frame(&ctx.frame); reset_parser(); } } bool CmdParser_HasCompleteFrame(cmd_frame_t *frame) { if (ctx.state == PARSE_COMPLETE && ctx.frame.valid) { if (frame) { memcpy(frame, &ctx.frame, sizeof(cmd_frame_t)); } return true; } return false; } void CmdParser_Acknowledge(void) { reset_parser(); } uint32_t CmdParser_GetErrorCount(void) { return ctx.error_count; } uint32_t CmdParser_GetValidCount(void) { return ctx.valid_count; }