/** * @file app_ble_proto.c * @brief BLE frame protocol frontend implementation (mcm-ddc-ble, over rdtss). * * RX: byte-stream reassembly state machine (SOF -> header -> payload -> CRC). * A frame may span several BLE writes (each write <= att_mtu-3 bytes). * Frames with a bad CRC are dropped silently and the receiver resyncs on SOF. * * TX: CLI output text is chunked into CLI_RSP frames; the raw frame bytes go * into the ble_up ring buffer which the BLE schedule task flushes over notify * (20B per notify, paced by the notification confirm). * * Everything below runs in the BLE schedule task context (rx via the rdtss * write indication handler) - no locking needed. */ #include "app_ble_proto.h" #include "cli_core.h" #include "ble_up.h" #include "app_ota.h" #include "app_info.h" #include #include /* Frame layout */ #define BLE_FRAME_SOF 0xCA #define BLE_FRAME_HDR_LEN 5 /* SOF+TYPE+SEQ+LEN(2) */ #define BLE_FRAME_CRC_LEN 2 #define BLE_FRAME_OVERHEAD (BLE_FRAME_HDR_LEN + BLE_FRAME_CRC_LEN) /* Limits */ #define BLE_PROTO_MAX_PAYLOAD 480 /* protocol constraint on the phone side */ #define BLE_PROTO_RX_BUF_SIZE 512 /* >= one max frame */ #define BLE_PROTO_TX_FRAME_SIZE 251 /* max BLE packet: 244 payload + overhead */ #define BLE_PROTO_CLI_LINE_MAX 63 /* CLI_REQ payload limit */ /* notify payload is 20B at the default ATT MTU (23-3); one CLI_RSP frame * must fit one notify: payload chunk = 20 - frame overhead */ #define BLE_PROTO_CLI_CHUNK (20 - BLE_FRAME_OVERHEAD) /* RX states */ enum { RX_WAIT_SOF = 0, RX_HEADER, RX_BODY, }; static uint8_t s_rxBuf[BLE_PROTO_RX_BUF_SIZE]; static uint16_t s_rxHave; static uint16_t s_rxNeed; static uint8_t s_rxState; static uint8_t s_txSeq; /* device -> phone seq counter */ static uint8_t s_txFrame[BLE_PROTO_TX_FRAME_SIZE]; static uint8_t s_logEn; /* traffic log switch (default off) */ /* ------------------------------------------------------------------ */ /* Traffic log (UART printf; payload preview capped to keep lines short) */ /* ------------------------------------------------------------------ */ #define BLE_LOG_DUMP_MAX 24u static void proto_dump(const char* dir, uint8_t type, uint8_t seq, const uint8_t* payload, uint16_t len) { uint16_t i; uint16_t n = (len > BLE_LOG_DUMP_MAX) ? BLE_LOG_DUMP_MAX : len; printf("[BLE %s] type=0x%02X seq=%u len=%u", dir, type, seq, len); for (i = 0; i < n; i++) { printf(" %02X", payload[i]); } if (len > n) { printf(" ..."); } printf("\r\n"); } void app_ble_proto_log_set(uint8_t on) { s_logEn = (on != 0) ? 1u : 0u; } uint8_t app_ble_proto_log_get(void) { return s_logEn; } /* ------------------------------------------------------------------ */ /* CRC16-CCITT (init 0xFFFF, poly 0x1021) */ /* ------------------------------------------------------------------ */ static uint16_t proto_crc16(const uint8_t* data, uint16_t len) { uint16_t crc = 0xFFFF; uint16_t i; uint8_t bit; for (i = 0; i < len; i++) { crc ^= (uint16_t)data[i] << 8; for (bit = 0; bit < 8; bit++) { crc = (crc & 0x8000u) ? (uint16_t)((crc << 1) ^ 0x1021u) : (uint16_t)(crc << 1); } } return crc; } /* ------------------------------------------------------------------ */ /* TX path */ /* ------------------------------------------------------------------ */ /** * @brief Build one frame and push it into the ble_up uplink ring. */ int app_ble_proto_send_frame(uint8_t type, uint8_t seq, const uint8_t* payload, uint16_t len) { uint16_t crc; uint16_t frameLen = (uint16_t)(BLE_FRAME_OVERHEAD + len); if (frameLen > BLE_PROTO_TX_FRAME_SIZE) { return -1; } s_txFrame[0] = BLE_FRAME_SOF; s_txFrame[1] = type; s_txFrame[2] = seq; s_txFrame[3] = (uint8_t)(len & 0xFFu); s_txFrame[4] = (uint8_t)(len >> 8); if (len != 0) { memcpy(&s_txFrame[5], payload, len); } crc = proto_crc16(&s_txFrame[1], (uint16_t)(3u + len)); /* TYPE..PAYLOAD */ s_txFrame[5 + len] = (uint8_t)(crc & 0xFFu); s_txFrame[5 + len + 1] = (uint8_t)(crc >> 8); if (s_logEn) { proto_dump("TX", type, seq, payload, len); } ble_up_write(s_txFrame, frameLen); return 0; } /** * @brief cli_out_fn for the BLE frontend: chunk response text into * CLI_RSP frames (one frame per 20B notify at the default MTU). */ static void proto_cli_out(const char* str) { while (*str != '\0') { uint16_t n = (uint16_t)strlen(str); if (n > BLE_PROTO_CLI_CHUNK) { n = BLE_PROTO_CLI_CHUNK; } (void)app_ble_proto_send_frame(BLE_FRAME_CLI_RSP, s_txSeq++, (const uint8_t*)str, n); str += n; } } /* ------------------------------------------------------------------ */ /* CLI over the read/write characteristic: the phone writes a raw */ /* command line (write-with-response) and reads the output text back. */ /* ------------------------------------------------------------------ */ #define CLI_RW_CMD_MAX 63 /* same line limit as the frame CLI */ #define CLI_RW_RSP_MAX 512 /* ATT value length budget (0x200) */ static uint8_t s_cliRsp[CLI_RW_RSP_MAX]; static uint16_t s_cliRspLen; /* cli_out_fn capturing the command output into s_cliRsp */ static void cli_rsp_out(const char* str) { while (*str != '\0' && s_cliRspLen < CLI_RW_RSP_MAX) { s_cliRsp[s_cliRspLen++] = (uint8_t)*str++; } } void app_ble_proto_cli_exec(const uint8_t* cmd, uint16_t len) { static char s_line[CLI_RW_CMD_MAX + 1]; int status = 1; /* default: rejected */ s_cliRspLen = 0; if (len > 0 && len <= CLI_RW_CMD_MAX) { memcpy(s_line, cmd, len); s_line[len] = '\0'; /* same set/exec/restore policy as the frame CLI path */ cli_set_output(cli_rsp_out); status = cli_exec_line(s_line); cli_set_output(NULL); } if (s_cliRspLen == 0) { const char* fb = (status == 0) ? "ok\r\n" : "unknown command\r\n"; uint16_t n = (uint16_t)strlen(fb); memcpy(s_cliRsp, fb, n); s_cliRspLen = n; } if (s_logEn) { uint16_t i; printf("[BLE] cli exec: \""); for (i = 0; i < len && i < CLI_RW_CMD_MAX; i++) { printf("%c", (cmd[i] >= 32 && cmd[i] < 127) ? cmd[i] : '.'); } printf("\" -> %uB rsp\r\n", s_cliRspLen); } } const uint8_t* app_ble_proto_cli_rsp(uint16_t* out_len) { *out_len = s_cliRspLen; return s_cliRsp; } /* ------------------------------------------------------------------ */ /* RX path */ /* ------------------------------------------------------------------ */ /** * @brief Handle one complete, CRC-valid frame. */ static void proto_dispatch_frame(uint8_t type, uint8_t seq, const uint8_t* payload, uint16_t len) { if (s_logEn) { proto_dump("RX", type, seq, payload, len); } switch (type) { case BLE_FRAME_CLI_REQ: { static char s_line[BLE_PROTO_CLI_LINE_MAX + 1]; int status = 1; /* default: rejected */ if (len <= BLE_PROTO_CLI_LINE_MAX) { memcpy(s_line, payload, len); s_line[len] = '\0'; /* Route the CLI core output to BLE frames for this command. * Simple mutual exclusion with the UART frontend: the UART * frontend forces output back to default before each of its * own cli_exec_line() calls (see cli_core.c note). */ cli_set_output(proto_cli_out); status = cli_exec_line(s_line); cli_set_output(NULL); } { uint8_t st = (uint8_t)status; (void)app_ble_proto_send_frame(BLE_FRAME_CLI_RSP_END, seq, &st, 1); } break; } case BLE_FRAME_OTA_BEGIN: case BLE_FRAME_OTA_DATA: case BLE_FRAME_OTA_END: app_ota_handle_frame(type, seq, payload, len); break; case BLE_FRAME_INFO_QUERY: app_info_handle_query(seq, payload, len); break; default: /* unknown types: drop */ break; } } void app_ble_proto_rx_bytes(const uint8_t* data, uint16_t len) { uint16_t i; for (i = 0; i < len; i++) { uint8_t ch = data[i]; switch (s_rxState) { case RX_WAIT_SOF: if (ch == BLE_FRAME_SOF) { s_rxBuf[0] = ch; s_rxHave = 1; s_rxNeed = BLE_FRAME_HDR_LEN; s_rxState = RX_HEADER; } break; case RX_HEADER: s_rxBuf[s_rxHave++] = ch; if (s_rxHave == BLE_FRAME_HDR_LEN) { uint16_t payLen = (uint16_t)s_rxBuf[3] | ((uint16_t)s_rxBuf[4] << 8); if (payLen > BLE_PROTO_MAX_PAYLOAD) { /* implausible length: drop and resync */ s_rxState = RX_WAIT_SOF; s_rxHave = 0; } else { s_rxNeed = (uint16_t)(BLE_FRAME_OVERHEAD + payLen); s_rxState = RX_BODY; } } break; case RX_BODY: s_rxBuf[s_rxHave++] = ch; if (s_rxHave == s_rxNeed) { uint16_t payLen = (uint16_t)s_rxBuf[3] | ((uint16_t)s_rxBuf[4] << 8); uint16_t crcCalc = proto_crc16(&s_rxBuf[1], (uint16_t)(3u + payLen)); uint16_t crcRecv = (uint16_t)s_rxBuf[5 + payLen] | ((uint16_t)s_rxBuf[5 + payLen + 1] << 8); if (crcCalc == crcRecv) { proto_dispatch_frame(s_rxBuf[1], s_rxBuf[2], &s_rxBuf[5], payLen); } /* bad CRC: drop silently, resync on next SOF */ s_rxState = RX_WAIT_SOF; s_rxHave = 0; } break; default: s_rxState = RX_WAIT_SOF; s_rxHave = 0; break; } } } /* ------------------------------------------------------------------ */ /* State management */ /* ------------------------------------------------------------------ */ void app_ble_proto_init(void) { s_rxState = RX_WAIT_SOF; s_rxHave = 0; s_rxNeed = 0; s_txSeq = 0; } void app_ble_proto_on_disconnect(void) { s_rxState = RX_WAIT_SOF; /* drop a half-received frame */ s_rxHave = 0; ble_up_reset(); app_ota_abort(); /* abort any on-going OTA session */ }