/* USB 链路分帧。格式与两条「为什么」在 ulink.h。 */ #include "ulink.h" #include #include #include "cmd_json.h" uint16_t ulink_crc16(const char *data, size_t len) { uint16_t crc = 0xFFFF; for (size_t i = 0; i < len; i++) { crc ^= (uint16_t)((uint8_t)data[i] << 8); for (int b = 0; b < 8; b++) { crc = (crc & 0x8000u) ? (uint16_t)((crc << 1) ^ 0x1021u) : (uint16_t)(crc << 1); } } return crc; } static int hexval(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 -1; } /* s[0..len) 末尾是不是 "*XXXX"。是则返回 1 并给出 CRC 值。 */ static int crc_suffix(const char *s, size_t len, uint16_t *crc) { if (len < ULINK_CRC_LEN || s[len - ULINK_CRC_LEN] != '*') { return 0; } uint16_t v = 0; for (size_t i = len - 4; i < len; i++) { const int h = hexval(s[i]); if (h < 0) { return 0; } v = (uint16_t)((v << 4) | (uint16_t)h); } *crc = v; return 1; } /* ---- 接收 ---------------------------------------------------------------- */ void ulink_rx_init(ulink_rx_t *rx) { memset(rx, 0, sizeof(*rx)); } static bool is_cmd_line(const ulink_rx_t *rx) { return rx->n >= ULINK_PREFIX_LEN && memcmp(rx->buf, ULINK_CMD_PREFIX, ULINK_PREFIX_LEN) == 0; } static void end_of_line(ulink_rx_t *rx, ulink_rx_fn fn, void *ctx) { if (rx->n > 0 && rx->buf[rx->n - 1] == '\r') { rx->n--; } rx->buf[rx->n] = '\0'; if (!is_cmd_line(rx)) { rx->ignored++; } else if (rx->overflow) { rx->too_long++; fn(ctx, ULINK_RX_TOO_LONG, ""); } else { char *json = rx->buf + ULINK_PREFIX_LEN; size_t len = rx->n - ULINK_PREFIX_LEN; uint16_t want = 0; const int has_crc = crc_suffix(json, len, &want); if (has_crc) { len -= ULINK_CRC_LEN; json[len] = '\0'; } if (rx->bad) { rx->bad_bytes++; fn(ctx, ULINK_RX_BAD_BYTES, json); } else if (has_crc && ulink_crc16(json, len) != want) { rx->bad_crc++; fn(ctx, ULINK_RX_BAD_CRC, json); } else { rx->cmds++; fn(ctx, ULINK_RX_CMD, json); } } rx->n = 0; rx->overflow = false; rx->bad = false; } void ulink_rx_feed(ulink_rx_t *rx, const uint8_t *data, size_t len, ulink_rx_fn fn, void *ctx) { for (size_t i = 0; i < len; i++) { const uint8_t c = data[i]; if (c == '\n') { end_of_line(rx, fn, ctx); continue; } /* NUL 与控制字符:记下来,行尾整行拒。⚠️ 不能只是跳过 —— NUL 会让后面的 JSON 解析在 NUL 处提前结束,`{"cmd":"status"}\0垃圾` 就被当成一条合法命令了。 */ if ((c < 0x20 && c != '\r' && c != '\t') || c == 0x7F) { rx->bad = true; } if (rx->n >= ULINK_RX_LINE_MAX) { rx->overflow = true; /* 前缀已经在 buf 里了,行尾还认得出它是不是命令 */ continue; } rx->buf[rx->n++] = (char)c; } } /* ---- 发送 ---------------------------------------------------------------- */ void ulink_tx_init(ulink_tx_t *tx, ulink_sink_fn sink, void *ctx) { memset(tx, 0, sizeof(*tx)); tx->sink = sink; tx->ctx = ctx; tx->at_bol = true; } /* 写一段,按**真正写出去的**最后一个字节更新 at_bol。 */ static bool put(ulink_tx_t *tx, const char *d, size_t n) { if (n == 0) { return true; } const size_t w = tx->sink(tx->ctx, d, n); if (w > 0) { tx->at_bol = (d[w - 1] == '\n'); } if (w < n) { tx->short_writes++; return false; } return true; } void ulink_tx_log(ulink_tx_t *tx, const char *data, size_t len) { size_t start = 0; for (size_t i = 0; i <= len; i++) { const bool eol = (i < len && data[i] == '\n'); if (i < len && !eol) { continue; } const size_t seg = i - start + (eol ? 1u : 0u); /* 带上这个 \n */ if (seg > 0) { if (tx->at_bol && data[start] == '@') { if (!put(tx, " ", 1)) { return; } } if (!put(tx, data + start, seg)) { return; /* 写不下就丢掉剩下的日志:日志 UART 上还有一份 */ } } start = i + 1; } } bool ulink_tx_frame(ulink_tx_t *tx, const char *json, size_t len) { if (!tx->at_bol && !put(tx, "\n", 1)) { return false; } char tail[ULINK_CRC_LEN + 2]; snprintf(tail, sizeof(tail), "*%04X\n", (unsigned)ulink_crc16(json, len)); /* 三段分开写:前缀、JSON、CRC。中间被别的写者插进来的话 CRC 会对不上, 上位机丢弃这一行 —— 这正是必带 CRC 的理由(ulink.h)。 */ const bool ok = put(tx, ULINK_RSP_PREFIX, ULINK_PREFIX_LEN) && put(tx, json, len) && put(tx, tail, ULINK_CRC_LEN + 1); if (ok) { tx->frames++; } return ok; } /* ---- 链路层错误 ---------------------------------------------------------- */ const char *ulink_rx_err_str(ulink_rx_kind_t kind) { switch (kind) { case ULINK_RX_CMD: return NULL; case ULINK_RX_TOO_LONG: return "too_long"; case ULINK_RX_BAD_CRC: return "bad_crc"; case ULINK_RX_BAD_BYTES: return "bad_frame"; } return "bad_frame"; } size_t ulink_link_error(const char *payload, const char *err, char *out, size_t cap) { /* `cj_parse()` 是纯函数(不碰任何静态量),在链路任务里调是安全的 —— 与只许在执行器任务里调的 `cmd_handle_line()` 不同。 */ cj_obj_t o; int64_t seq = 0; const bool has_seq = payload != NULL && cj_parse(payload, &o) && cj_get_int(&o, "seq", &seq) == 1 && seq >= 1 && seq <= (int64_t)UINT32_MAX; cj_writer_t w; cj_w_init(&w, out, cap); if (has_seq) { cj_w(&w, "{\"seq\":%lu,\"ok\":false,\"err\":\"%s\"}", (unsigned long)seq, err); } else { cj_w(&w, "{\"ok\":false,\"err\":\"%s\"}", err); } return w.overflow ? 0 : w.pos; } /* ---- 参考解帧器 ---------------------------------------------------------- */ int ulink_deframe(const char *line, char *out, size_t cap) { size_t len = strlen(line); if (len > 0 && line[len - 1] == '\r') { len--; } if (len < ULINK_PREFIX_LEN + ULINK_CRC_LEN || memcmp(line, ULINK_RSP_PREFIX, ULINK_PREFIX_LEN) != 0) { return -1; } const char *json = line + ULINK_PREFIX_LEN; size_t jlen = len - ULINK_PREFIX_LEN; uint16_t want = 0; if (!crc_suffix(json, jlen, &want)) { return -1; /* 回应必带 CRC */ } jlen -= ULINK_CRC_LEN; if (ulink_crc16(json, jlen) != want || jlen + 1 > cap) { return -1; } memcpy(out, json, jlen); out[jlen] = '\0'; return (int)jlen; }