mothercup/mcm-ddc-04/app/ble/app_ble_proto.c

332 lines
9.9 KiB
C

/**
* @file app_ble_proto.c
* @brief BLE frame protocol frontend implementation.
*
* 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 (the OTA offset/ack protocol
* resynchronizes: the peer resends and gets BLE_OTA_ST_BAD_STATE).
*
* TX: CLI output text is chunked into CLI_RSP frames sized to the current
* ATT MTU. app_ble_notify_send() returns -1 while a previous notification
* is in flight, so frames that cannot be sent immediately are appended to a
* small pending FIFO which app_ble_proto_poll() flushes from the BleTask loop.
*
* Everything below runs in the BLE schedule task context (rx via the CLS
* write indication handler, poll via the BleTask loop) - no locking needed.
*/
#include "app_ble_proto.h"
#include "app_ble.h"
#include "app_ota.h"
#include "cli_core.h"
#include <string.h>
/* 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_PENDING_SIZE 512 /* TX pending FIFO (holds >= 2 frames) */
#define BLE_PROTO_CLI_LINE_MAX 63 /* CLI_REQ payload limit */
/* 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_pending[BLE_PROTO_PENDING_SIZE];
static uint16_t s_pendLen;
static uint16_t s_txDropCnt;
/* ------------------------------------------------------------------ */
/* 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 Send one built frame immediately, or append it to the pending FIFO
* when the notify channel is busy. Keeps frame ordering.
*/
static int proto_emit_frame(const uint8_t* frame, uint16_t len)
{
if (!app_ble_is_connected())
{
return -1;
}
if (s_pendLen == 0 && app_ble_notify_send(frame, len) == 0)
{
return 0;
}
if ((uint32_t)s_pendLen + len <= BLE_PROTO_PENDING_SIZE)
{
memcpy(s_pending + s_pendLen, frame, len);
s_pendLen = (uint16_t)(s_pendLen + len);
return 0;
}
s_txDropCnt++;
return -1;
}
/**
* @brief Build one frame into s_txFrame and emit it.
*/
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);
return proto_emit_frame(s_txFrame, frameLen);
}
/**
* @brief cli_out_fn for the BLE frontend: chunk response text into
* CLI_RSP frames sized to the current ATT MTU.
*/
static void proto_cli_out(const char* str)
{
/* Per-frame payload: BLE packet (att_mtu-3, capped at 244) minus frame overhead */
uint16_t pktMax = app_ble_max_payload();
uint16_t chunkMax;
if (pktMax > 244u)
{
pktMax = 244u;
}
if (pktMax <= BLE_FRAME_OVERHEAD)
{
return;
}
chunkMax = (uint16_t)(pktMax - BLE_FRAME_OVERHEAD);
while (*str != '\0')
{
uint16_t n = (uint16_t)strlen(str);
if (n > chunkMax)
{
n = chunkMax;
}
(void)app_ble_proto_send_frame(BLE_FRAME_CLI_RSP, s_txSeq++, (const uint8_t*)str, n);
str += n;
}
}
void app_ble_proto_poll(void)
{
if (s_pendLen == 0 || !app_ble_is_connected())
{
return;
}
/* Length of the first queued frame: LEN field + overhead */
uint16_t frameLen = (uint16_t)(BLE_FRAME_OVERHEAD +
((uint16_t)s_pending[3] | ((uint16_t)s_pending[4] << 8)));
if (frameLen > s_pendLen)
{
/* should never happen; drop everything to resync */
s_pendLen = 0;
return;
}
if (app_ble_notify_send(s_pending, frameLen) == 0)
{
s_pendLen = (uint16_t)(s_pendLen - frameLen);
if (s_pendLen != 0)
{
memmove(s_pending, s_pending + frameLen, s_pendLen);
}
}
}
/* ------------------------------------------------------------------ */
/* 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)
{
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;
default:
/* unknown frame type: 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 (OTA resyncs via offset/ack) */
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;
s_pendLen = 0;
s_txDropCnt = 0;
}
void app_ble_proto_on_disconnect(void)
{
s_pendLen = 0; /* drop queued TX frames */
s_rxState = RX_WAIT_SOF; /* drop a half-received frame */
s_rxHave = 0;
app_ota_abort();
}