/*============================================================================== * parser.c - BLE command parser / dispatcher * * Common module that parses binary command packets received over BLE and * dispatches them to the appropriate handler from the command table. * * Key point: this file does not know which Cmd_* functions exist. * cmd_table.c injects the table pointer at boot via dr_parser_init(). * * Packet layout: [TAG 4B] [data N] [CRC16 2B (optional)] * - TAG : 4-char ASCII identifier (e.g. "mid?") * - data : Big-Endian uint16 or ASCII * - CRC16: present when g_plat.crc_check is true; trailing 2 bytes (LE) * * Flow: * 1) dr_cmd_parser() - external entry point * 2) dr_parse_cmd() - length / CRC verification + TAG/data split * 3) dr_cmd_dispatch() - walks the command table -> calls handler * * Error responses (all formatted as [err_tag 4B] [echo cmd_tag 4B] = 8 bytes): * - rxs: Too short (< 4B, or < 7B with CRC enabled) * - rxc: CRC fail * - rxd: Disabled command * - rxn: NULL handler * - rxx: Unknown command *============================================================================*/ #include "parser.h" #include /*------------------------------------------------------------------------------ * Globals (declared extern in header) *----------------------------------------------------------------------------*/ dr_platform_if_t g_plat = { 0, 0, 0 }; bool g_log_enable = false; /*------------------------------------------------------------------------------ * Internal state - command table pointer injected via dr_parser_init() *----------------------------------------------------------------------------*/ static const CmdEntry *m_cmd_table = NULL; static uint16_t m_cmd_count = 0; /*============================================================================== * Internal utilities (static) *============================================================================*/ /* Copy first 4 bytes of buffer into a null-terminated TAG string. */ static void dr_copy_tag(const uint8_t *buf, char *tag_out) { tag_out[0] = (char)buf[0]; tag_out[1] = (char)buf[1]; tag_out[2] = (char)buf[2]; tag_out[3] = (char)buf[3]; tag_out[4] = '\0'; } /* Compare two 4-char TAGs (byte-by-byte, faster than memcmp here). */ static bool dr_tag_eq(const char *tag, const char *key4) { return (tag[0] == key4[0] && tag[1] == key4[1] && tag[2] == key4[2] && tag[3] == key4[3]); } /*============================================================================== * Public utilities - used by handlers *============================================================================*/ bool dr_get_u16(const ParsedCmd *cmd, uint8_t word_index, uint16_t *out) { uint8_t pos = (uint8_t)(word_index * 2); if (cmd->data_len < (uint8_t)(pos + 2)) { return false; } *out = (uint16_t)((uint16_t)cmd->data[pos] << 8) | (uint16_t)cmd->data[pos + 1]; return true; } void dr_get_ascii(const ParsedCmd *cmd, uint8_t offset, char *out, uint8_t max_len) { uint8_t i; uint8_t remain; if (offset >= cmd->data_len) { out[0] = '\0'; return; } remain = (uint8_t)(cmd->data_len - offset); if (remain > max_len) { remain = max_len; } for (i = 0; i < remain; i++) { out[i] = (char)cmd->data[offset + i]; } out[remain] = '\0'; } /*============================================================================== * CRC16 (Nordic SDK CRC-CCITT variant) *============================================================================*/ uint16_t dr_crc16_compute(const uint8_t *p_data, uint32_t size, const uint16_t *p_crc) { uint32_t i; uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc; for (i = 0; i < size; i++) { crc = (uint8_t)(crc >> 8) | (crc << 8); crc ^= p_data[i]; crc ^= (uint8_t)(crc & 0xFF) >> 4; crc ^= (crc << 8) << 4; crc ^= ((crc & 0xFF) << 4) << 1; } return crc; } static bool dr_crc16_check(const uint8_t *p_data, uint32_t data_len, uint16_t expected_crc) { return (dr_crc16_compute(p_data, data_len, NULL) == expected_crc); } /* Extract trailing 2 bytes (Little-Endian) as expected CRC and verify. */ static bool dr_crc16_check_packet(const uint8_t *packet, uint32_t packet_len) { uint16_t expected_crc; uint32_t data_len; if (packet_len < 2) { return false; } data_len = packet_len - 2; expected_crc = (uint16_t)packet[packet_len - 2] | ((uint16_t)packet[packet_len - 1] << 8); return dr_crc16_check(packet, data_len, expected_crc); } /*============================================================================== * Error response helper * * Packet: [err_tag 4B] [echo cmd_tag 4B] = 8 bytes = 4 words * CRC16 is appended automatically by the tx_bin layer. *============================================================================*/ static void dr_send_error(const char *err_tag, const char *cmd_tag) { if (g_plat.tx_bin) { uint8_t err_buf[8]; memcpy(&err_buf[0], err_tag, 4); memcpy(&err_buf[4], cmd_tag, 4); g_plat.tx_bin(err_buf, 4); } } /*============================================================================== * Packet parser - raw buffer -> ParsedCmd * * Error responses (rxs: / rxc:) are emitted directly from this function. *============================================================================*/ static bool dr_parse_cmd(const uint8_t *buffer, uint8_t length, ParsedCmd *out) { uint8_t data_len; /* Less than 4 bytes -> TAG cannot be identified */ if (length < 4) { dr_send_error("rxs:", "????"); if (g_plat.log && g_log_enable) { g_plat.log("[parser] too short (%u bytes) -> rxs:\r\n", length); } return false; } /* Extract TAG first (used in error echoes below) */ dr_copy_tag(buffer, out->tag); /* CRC verification */ if (g_plat.crc_check) { if (length < 7) { dr_send_error("rxs:", out->tag); if (g_plat.log && g_log_enable) { g_plat.log("[parser] CRC enabled but too short (%u) -> rxs:\r\n", length); } return false; } if (!dr_crc16_check_packet(buffer, length)) { dr_send_error("rxc:", out->tag); if (g_plat.log && g_log_enable) { g_plat.log("[parser] CRC mismatch '%s' -> rxc:\r\n", out->tag); } return false; } data_len = (uint8_t)(length - 4 - 2); /* strip TAG and CRC */ } else { data_len = (uint8_t)(length - 4); } if (data_len > DR_MAX_DATA) { data_len = DR_MAX_DATA; } if (data_len > 0) { memcpy(out->data, buffer + 4, data_len); } out->data_len = data_len; return true; } /*============================================================================== * Command dispatcher * * Lower-cases the parsed TAG and walks the command table to find a matching * handler. Emits the matching error response on miss / disabled / NULL handler. *============================================================================*/ static int dr_cmd_dispatch(const ParsedCmd *cmd) { uint16_t i; char tag_lower[5]; if (m_cmd_table == NULL) { dr_send_error("rxn:", cmd->tag); if (g_plat.log) { g_plat.log("[parser] table not initialized -> rxn:\n"); } return 0; } /* Case-insensitive matching */ for (i = 0; i < 4 && cmd->tag[i]; i++) { tag_lower[i] = (cmd->tag[i] >= 'A' && cmd->tag[i] <= 'Z') ? (cmd->tag[i] + 32) : cmd->tag[i]; } tag_lower[i] = '\0'; for (i = 0; i < m_cmd_count; i++) { if (dr_tag_eq(tag_lower, m_cmd_table[i].tag)) { if (!m_cmd_table[i].enabled) { dr_send_error("rxd:", cmd->tag); if (g_plat.log && g_log_enable) { g_plat.log("Command '%s' disabled -> rxd:\n", cmd->tag); } return 0; } if (m_cmd_table[i].handler == NULL) { dr_send_error("rxn:", cmd->tag); if (g_plat.log) { g_plat.log("[parser] NULL handler for '%s' -> rxn:\n", cmd->tag); } return 0; } return m_cmd_table[i].handler(cmd); } } /* TAG not found in table */ dr_send_error("rxx:", cmd->tag); if (g_plat.log && g_log_enable) { g_plat.log("Unknown TAG '%s' -> rxx:\n", cmd->tag); } return 0; } /*============================================================================== * Public API *============================================================================*/ /* Called by cmd_table.c at boot to inject the command table. */ void dr_parser_init(const CmdEntry *table, uint16_t count) { m_cmd_table = table; m_cmd_count = count; } /* Entry point - called from BLE NUS / UART receive callbacks. * * Returns: * 1 = command processed successfully * 0 = unknown TAG / disabled command (error response already sent) * -1 = parse failure (rxs: / rxc: error response already sent) */ int dr_cmd_parser(const uint8_t *buf, uint8_t len) { ParsedCmd cmd; if (!dr_parse_cmd(buf, len, &cmd)) { if (g_plat.log) g_plat.log("[PARSER] PARSE FAIL\r\n"); return -1; } return dr_cmd_dispatch(&cmd); }