From 93ffa389f94eaa0d2c43308fe8dcaa2b923f24ba Mon Sep 17 00:00:00 2001 From: lxowalle Date: Tue, 7 Jul 2026 13:47:35 +0800 Subject: [PATCH] fix(protocol): include error code in response length --- components/basic/src/maix_protocol.cpp | 131 +++++++++++++++------- docs/doc/convention/protocol.md | 8 +- docs/doc/dev/comm_protocol_internal.md | 2 + docs/doc_zh/convention/protocol.md | 10 +- docs/doc_zh/dev/comm_protocol_internal.md | 2 + 5 files changed, 100 insertions(+), 53 deletions(-) diff --git a/components/basic/src/maix_protocol.cpp b/components/basic/src/maix_protocol.cpp index 114e1bdf..8bf02743 100644 --- a/components/basic/src/maix_protocol.cpp +++ b/components/basic/src/maix_protocol.cpp @@ -13,6 +13,27 @@ namespace maix::protocol { uint32_t HEADER = 0xBBACCAAA; + + namespace + { + // Frame layout: + // [header][data_len][flags][cmd][body][crc16] + // RESP_ERR stores the error code as the first byte of body. + constexpr int HEADER_LEN = sizeof(uint32_t); + constexpr int DATA_LEN_FIELD_LEN = sizeof(uint32_t); + constexpr int FLAGS_LEN = sizeof(uint8_t); + constexpr int CMD_LEN = sizeof(uint8_t); + constexpr int CRC_LEN = sizeof(uint16_t); + constexpr int ERROR_CODE_LEN = sizeof(uint8_t); + constexpr int DATA_LEN_OFFSET = HEADER_LEN; + constexpr int FLAGS_OFFSET = HEADER_LEN + DATA_LEN_FIELD_LEN; + constexpr int CMD_OFFSET = FLAGS_OFFSET + FLAGS_LEN; + constexpr int BODY_OFFSET = CMD_OFFSET + CMD_LEN; + constexpr int FRAME_DATA_OFFSET = HEADER_LEN + DATA_LEN_FIELD_LEN; + constexpr int FRAME_DATA_OVERHEAD_LEN = FLAGS_LEN + CMD_LEN + CRC_LEN; + constexpr int FRAME_OVERHEAD_LEN = HEADER_LEN + DATA_LEN_FIELD_LEN + FRAME_DATA_OVERHEAD_LEN; + } + uint16_t crc16_IBM(uint8_t *ptr, size_t len) { unsigned int i; @@ -47,32 +68,40 @@ namespace maix::protocol if (version != VERSION) return -err::ERR_ARGS; - if (out_buff_len < body_len + 12) + if (body_len < 0 || (body_len > 0 && !body)) + return -err::ERR_ARGS; + const int code_len = code == 0xFF ? 0 : ERROR_CODE_LEN; + const int frame_len = body_len + FRAME_OVERHEAD_LEN + code_len; + const int data_len = body_len + FRAME_DATA_OVERHEAD_LEN + code_len; + if (out_buff_len < frame_len) return -err::ERR_ARGS; ((uint32_t *)out_buff)[0] = HEADER; - ((uint32_t *)out_buff)[1] = body_len + 4; - out_buff[8] = flags | version; - out_buff[9] = cmd; + ((uint32_t *)out_buff)[1] = data_len; + out_buff[FLAGS_OFFSET] = flags | version; + out_buff[CMD_OFFSET] = cmd; + const int crc_offset = BODY_OFFSET + code_len + body_len; if (code != 0xFF) { - out_buff[10] = code; - memcpy(out_buff + 11, body, body_len); - uint16_t crc16 = crc16_IBM(out_buff, body_len + 11); - out_buff[11 + body_len] = crc16 & 0xFF; - out_buff[12 + body_len] = crc16 >> 8 & 0xFF; - return body_len + 13; + out_buff[BODY_OFFSET] = code; + if (body_len > 0) + memcpy(out_buff + BODY_OFFSET + ERROR_CODE_LEN, body, body_len); + uint16_t crc16 = crc16_IBM(out_buff, crc_offset); + out_buff[crc_offset] = crc16 & 0xFF; + out_buff[crc_offset + 1] = crc16 >> 8 & 0xFF; + return frame_len; } - memcpy(out_buff + 10, body, body_len); - uint16_t crc16 = crc16_IBM(out_buff, body_len + 10); - out_buff[10 + body_len] = crc16 & 0xFF; - out_buff[11 + body_len] = crc16 >> 8 & 0xFF; - return body_len + 12; + if (body_len > 0) + memcpy(out_buff + BODY_OFFSET, body, body_len); + uint16_t crc16 = crc16_IBM(out_buff, crc_offset); + out_buff[crc_offset] = crc16 & 0xFF; + out_buff[crc_offset + 1] = crc16 >> 8 & 0xFF; + return frame_len; } Bytes *encode_resp_ok(uint8_t cmd, uint8_t *body, int body_len) { - uint8_t *buff = new uint8_t[12 + body_len]; - int len = encode(buff, 12 + body_len, cmd, FLAG_RESP | FLAG_RESP_OK, body, body_len); + uint8_t *buff = new uint8_t[FRAME_OVERHEAD_LEN + body_len]; + int len = encode(buff, FRAME_OVERHEAD_LEN + body_len, cmd, FLAG_RESP | FLAG_RESP_OK, body, body_len); if (len < 0) { delete[] buff; @@ -84,14 +113,16 @@ namespace maix::protocol Bytes *encode_resp_ok(uint8_t cmd, Bytes *body) { + if (!body) + return protocol::encode_resp_ok(cmd, nullptr, 0); int body_len = body->size(); return protocol::encode_resp_ok(cmd, body->data, body_len); } Bytes *encode_resp_err(uint8_t cmd, err::Err code, const std::string &msg) { - uint8_t *buff = new uint8_t[13 + msg.length()]; - int len = encode(buff, 13 + msg.length(), cmd, FLAG_RESP | FLAG_RESP_ERR, (uint8_t *)msg.c_str(), msg.length(), code); + uint8_t *buff = new uint8_t[FRAME_OVERHEAD_LEN + ERROR_CODE_LEN + msg.length()]; + int len = encode(buff, FRAME_OVERHEAD_LEN + ERROR_CODE_LEN + msg.length(), cmd, FLAG_RESP | FLAG_RESP_ERR, (uint8_t *)msg.c_str(), msg.length(), code); if (len < 0) { delete[] buff; @@ -116,12 +147,12 @@ namespace maix::protocol size_t data_len = 0; *idx = 0; - if (len < 12) + if (len < FRAME_OVERHEAD_LEN) return false; // find header uint32_t i = 0; bool found = false; - for (; i < (uint32_t)len - 4; i++) + for (; i < (uint32_t)len - HEADER_LEN; i++) { if (data[i] == (header & 0xFF) && data[i + 1] == ((header >> 8) & 0xFF) && @@ -132,35 +163,39 @@ namespace maix::protocol break; } } - // len >= 12 + // Have at least one full minimal frame after the header position. if (!found) { *idx = i; return false; } - if (len - i < 12) + if (len - i < FRAME_OVERHEAD_LEN) return false; // get data_len, and check data length - data_len = data[i + 4] | (data[i + 5] << 8) | (data[i + 6] << 16) | (data[i + 7] << 24); - if (data_len > len - i - 8) + data_len = data[i + DATA_LEN_OFFSET] | + (data[i + DATA_LEN_OFFSET + 1] << 8) | + (data[i + DATA_LEN_OFFSET + 2] << 16) | + (data[i + DATA_LEN_OFFSET + 3] << 24); + if (data_len < FRAME_DATA_OVERHEAD_LEN || data_len > len - i - FRAME_DATA_OFFSET) return false; - *idx = i + 8 + data_len; + *idx = i + FRAME_DATA_OFFSET + data_len; // check crc - uint16_t crc16 = crc16_IBM(data + i, data_len + 6); - if (data[i + 6 + data_len] != (crc16 & 0xFF) || data[i + 7 + data_len] != (crc16 >> 8 & 0xFF)) + const int crc_offset = i + FRAME_DATA_OFFSET + data_len - CRC_LEN; + uint16_t crc16 = crc16_IBM(data + i, FRAME_DATA_OFFSET + data_len - CRC_LEN); + if (data[crc_offset] != (crc16 & 0xFF) || data[crc_offset + 1] != (crc16 >> 8 & 0xFF)) { return false; } // parse data - frame->version = data[i + 8] & FLAG_VERSION_MASK; - frame->is_resp = data[i + 8] & FLAG_IS_RESP_MASK; + frame->version = data[i + FLAGS_OFFSET] & FLAG_VERSION_MASK; + frame->is_resp = data[i + FLAGS_OFFSET] & FLAG_IS_RESP_MASK; frame->is_req = !frame->is_resp; - frame->is_report = data[i + 8] & FLAG_REPORT_MASK; - frame->resp_ok = data[i + 8] & FLAG_RESP_OK_MASK; - frame->cmd = data[i + 9]; - frame->set_body(data + i + 10, data_len - 4); - frame->body_len = data_len - 4; + frame->is_report = data[i + FLAGS_OFFSET] & FLAG_REPORT_MASK; + frame->resp_ok = data[i + FLAGS_OFFSET] & FLAG_RESP_OK_MASK; + frame->cmd = data[i + CMD_OFFSET]; + frame->set_body(data + i + BODY_OFFSET, data_len - FRAME_DATA_OVERHEAD_LEN); + frame->body_len = data_len - FRAME_DATA_OVERHEAD_LEN; return true; } @@ -210,6 +245,8 @@ namespace maix::protocol Bytes *MSG::encode_resp_ok(Bytes *body) { + if (!body) + return protocol::encode_resp_ok(this->cmd, nullptr, 0); return protocol::encode_resp_ok(this->cmd, body); } @@ -220,8 +257,8 @@ namespace maix::protocol Bytes *MSG::encode_report(uint8_t *body, int body_len) { - uint8_t *buff = new uint8_t[12 + body_len]; - int len = protocol::encode(buff, 12 + body_len, this->cmd, FLAG_RESP | FLAG_RESP_OK | FLAG_REPORT, body, body_len); + uint8_t *buff = new uint8_t[FRAME_OVERHEAD_LEN + body_len]; + int len = protocol::encode(buff, FRAME_OVERHEAD_LEN + body_len, this->cmd, FLAG_RESP | FLAG_RESP_OK | FLAG_REPORT, body, body_len); if (len < 0) { delete[] buff; @@ -233,8 +270,10 @@ namespace maix::protocol Bytes *MSG::encode_report(Bytes *body) { - uint8_t *buff = new uint8_t[12 + body_len]; - int len = protocol::encode(buff, 12 + body_len, this->cmd, FLAG_RESP | FLAG_RESP_OK | FLAG_REPORT, body->data, (int)body->size()); + int body_len = body ? (int)body->size() : 0; + uint8_t *body_data = body ? body->data : nullptr; + uint8_t *buff = new uint8_t[FRAME_OVERHEAD_LEN + body_len]; + int len = protocol::encode(buff, FRAME_OVERHEAD_LEN + body_len, this->cmd, FLAG_RESP | FLAG_RESP_OK | FLAG_REPORT, body_data, body_len); if (len < 0) { delete[] buff; @@ -283,6 +322,8 @@ namespace maix::protocol Bytes *Protocol::encode_resp_ok(uint8_t cmd, Bytes *body) { + if (!body) + return protocol::encode_resp_ok(cmd, nullptr, 0); return protocol::encode_resp_ok(cmd, body); } @@ -293,8 +334,8 @@ namespace maix::protocol Bytes *Protocol::encode_report(uint8_t cmd, uint8_t *body, int body_len) { - uint8_t *buff = new uint8_t[12 + body_len]; - int len = protocol::encode(buff, 12 + body_len, cmd, FLAG_RESP | FLAG_RESP_OK | FLAG_REPORT, body, body_len); + uint8_t *buff = new uint8_t[FRAME_OVERHEAD_LEN + body_len]; + int len = protocol::encode(buff, FRAME_OVERHEAD_LEN + body_len, cmd, FLAG_RESP | FLAG_RESP_OK | FLAG_REPORT, body, body_len); if (len < 0) { delete[] buff; @@ -306,6 +347,8 @@ namespace maix::protocol Bytes *Protocol::encode_report(uint8_t cmd, Bytes *body) { + if (!body) + return Protocol::encode_report(cmd, nullptr, 0); return Protocol::encode_report(cmd, body->data, body->size()); } @@ -344,7 +387,7 @@ namespace maix::protocol err::Err Protocol::push_data(const Bytes *new_data) { - return push_data((uint8_t *)&new_data[0], new_data->size()); + return push_data(new_data->data, new_data->size()); } MSG *Protocol::decode(uint8_t *new_data, size_t len) @@ -372,7 +415,9 @@ namespace maix::protocol MSG *Protocol::decode(const Bytes *new_data) { - return decode((uint8_t *)&new_data[0], new_data->size()); + if (!new_data) + return decode(nullptr, 0); + return decode(new_data->data, new_data->size()); } } // namespace maix::protocol diff --git a/docs/doc/convention/protocol.md b/docs/doc/convention/protocol.md index 3694c35f..8597d661 100644 --- a/docs/doc/convention/protocol.md +++ b/docs/doc/convention/protocol.md @@ -48,12 +48,12 @@ The master device can implement the protocol based on the chip and programming l | Hex Example | 0xBBACCAAA | 0x00000009 | 0x00 | 0x01 | hello | 0x442B | | Byte Stream (Hex) | AA CA AC BB | 09 00 00 00 | 00 | 01 | 68 65 6C 6C 6F | 2B 44 | -> **Note**: Multi-byte data uses little-endian (LE) encoding. For example, `data_len` is `0x00000006`, with `06` in the lowest byte, so it is sent first as `0x06`, followed by `0x00 0x00 0x00`. +> **Note**: Multi-byte data uses little-endian (LE) encoding. For example, `data_len` is `0x00000009`, with `09` in the lowest byte, so it is sent first as `0x09`, followed by `0x00 0x00 0x00`. > Strings are sent in order, e.g., `hello` is sent as `h`, then `e`, `l`, `l`, `o`. > In this example, the final data sent is: `AA CA AC BB 09 00 00 00 00 01 68 65 6C 6C 6F 2B 44`. - `header`: 4-byte header marking the start of a frame, fixed as `0xAA 0xCA 0xAC 0xBB`, sent starting with `0xAA`. -- `data len`: 4-byte data length, including the length of `flags`, `cmd`, `body`, and `CRC`. +- `data len`: 4-byte data length, including the length of `flags`, `cmd`, `body`, and `CRC`, excluding `header` and `data len` itself. - `flags`: 1-byte flag where each bit indicates: - MSB `is_resp`: Indicates if it is a response. `0` for request, `1` for response or active report (requires the third highest bit set to `1`). - Second highest bit `resp_ok`: @@ -89,7 +89,7 @@ Set the highest bit of `flags` (`is_resp`) to `1`. - For a successful response: Set `resp_ok` to `1`, and the `body` content is determined by `cmd`. An empty `body` is the simplest successful response. - For a failed response: - Set `resp_ok` to `0`. - - The first byte of `body` is the error code. Refer to [MaixCDK maix.err.Err](../../../components/basic/include/maix_err.hpp) for error codes. + - The first byte of `body` is the error code. Refer to [MaixCDK maix.err.Err](../../../components/basic/include/maix_err.hpp) for error codes. This error-code byte is included in both the `body` length and the frame `data_len`. - The following bytes in `body` contain the error message in `UTF-8` encoding, preferably in plain English for better compatibility. Each request must have a corresponding response, indicating either success or failure. Hereafter, `RESP_OK` and `RESP_ERR` are used to represent successful and failed responses, respectively. @@ -120,7 +120,7 @@ The MCU requests the list of applications from the Maix device. The steps are as `AA CA AC BB 04 00 00 00 01 F9 C9 77`. 2. The Maix device responds with `cmd` set to `0xF9` and `flags` set to `0xC1` (with `|0x80` for response and `|0x40` for success). The `body` contains the application list. If there are two applications, the actual byte stream is: - `AA CA AC BB 0A 00 00 00 C1 F9 02 66 61 63 65 00 66 61 63 65 00 F6 06`. + `AA CA AC BB 0F 00 00 00 C1 F9 02 66 61 63 65 00 66 61 63 65 00 E5 57`. --- diff --git a/docs/doc/dev/comm_protocol_internal.md b/docs/doc/dev/comm_protocol_internal.md index 510360f8..aaa519a2 100644 --- a/docs/doc/dev/comm_protocol_internal.md +++ b/docs/doc/dev/comm_protocol_internal.md @@ -8,5 +8,7 @@ Maix Comm Protocol 内部根据系统设置的通信方式(比如 uart), 用户需要使用串口时构建UART对象构造函数会先检查是否已经注册过(比如判断已经注册过的obj使用的UART编号和即将初始化的UART编号是否相同), 如果没有注册过就普通构造,已经注册过那就先回调设置的callback函数,callback 负责释放obj(注意 uart 里面 callback后UART不要再使用register_comm_callback传进来的obj了),然后再构造。 +协议帧的 `data_len` 包含 `flags` + `cmd` + `body` + `CRC`,不包含 `header` 和 `data_len` 自身。 +错误响应的错误码是 `body` 的第一个字节,后面才是错误字符串,所以错误码必须计入 `body` 长度、`data_len`、CRC 输入长度以及发送总长度。 diff --git a/docs/doc_zh/convention/protocol.md b/docs/doc_zh/convention/protocol.md index d548ca4e..5df19bb9 100644 --- a/docs/doc_zh/convention/protocol.md +++ b/docs/doc_zh/convention/protocol.md @@ -43,12 +43,12 @@ update: | 十六进制示例 | 0xBBACCAAA | 0x00000009 | 0x00 | 0x01 | hello | 0x442B | | 字节流(十六进制)| AA CA AC BB | 09 00 00 00 | 00 | 01 | 68 65 6c 6c 6F | 2B 44 | -> **注意**这里超过一个字节的数据均采用小端(LE)编码,比如这里 `data_len` 为 `0x00000006`,`06`在最低位,发送时需要先发送`0x06`再发送`0x00` `0x00` `0x00`。 +> **注意**这里超过一个字节的数据均采用小端(LE)编码,比如这里 `data_len` 为 `0x00000009`,`09`在最低位,发送时需要先发送`0x09`再发送`0x00` `0x00` `0x00`。 > 字符串就按照顺序发即可,比如`hello`,先发`h`再发`e` `l` `l` `o`。 > 这里的例子,最终发送数据: `AA CA AC BB 09 00 00 00 00 01 68 65 6c 6c 6F 2B 44`。 * `header`: 头,4 字节,用来标识一帧的开始,固定为 4 字节 `0xAA 0xCA 0xAC 0xBB`, 先发`0xAA` -* `data len`: 数据长度, 4 字节, 包含了 `flags` `cmd` + `body` + `CRC` 的长度。 +* `data len`: 数据长度, 4 字节, 包含了 `flags` + `cmd` + `body` + `CRC` 的长度,不包含 `header` 和 `data len` 自身。 * `flags`: 一个字节,各个位分别表示: * 最高位 `is_resp`: 是否是响应,`0`代表发送请求,`1`代表 响应 或者 主动上报(第三高位需要置`1`)。 * 第二高位 `resp_ok`: @@ -82,7 +82,7 @@ update: * 对于成功响应:`resp_ok` 设置为 `1`, `body` 内容由`cmd`决定, 其中最简单的成功响应即 `body` 为空。 * 对是失败响应: * `resp_ok`设置为 `0`。 - * `body` 第一个字节为错误码, 具体的错误码见[MaixCDK maix.err.Err](../../../components/basic/include/maix_err.hpp)。 + * `body` 第一个字节为错误码, 具体的错误码见[MaixCDK maix.err.Err](../../../components/basic/include/maix_err.hpp),这个错误码字节也计入 `body` 长度和帧头中的 `data_len`。 * `body` 后面的字节为错误字符串信息,`UTF-8` 编码,一般情况下建议用纯英文,提高兼容性。 每个请求均应有对应的响应,即执行成功或者失败,后文均用`RESP_OK`和`RESP_ERR`来代替执行成功和失败两种响应。 @@ -110,7 +110,7 @@ update: `AA CA AC BB 04 00 00 00 01 F9 C9 77`。 2. Maix 设备收到请求后,返回`cmd`为`0xF9`, `flags`为`0x01 | 0x80 | 0x40 => 0xC1`的响应(响应`flags`最高为`1`所以有`|0x80`, 成功响应第二高位为`1`所以有`|0x40`),`body`内容为应用列表,具体内容见下方协议说明。假如有两个 APP,实际字节流: -`AA CA AC BB 0A 00 00 00 C1 F9 02 66 61 63 65 00 66 61 63 65 00 F6 06`。 +`AA CA AC BB 0F 00 00 00 C1 F9 02 66 61 63 65 00 66 61 63 65 00 E5 57`。 ## 命令定义和系统命令 @@ -548,5 +548,3 @@ unsigned short crc16_IBM(const unsigned char *ptr,int len) } ``` - - diff --git a/docs/doc_zh/dev/comm_protocol_internal.md b/docs/doc_zh/dev/comm_protocol_internal.md index 510360f8..aaa519a2 100644 --- a/docs/doc_zh/dev/comm_protocol_internal.md +++ b/docs/doc_zh/dev/comm_protocol_internal.md @@ -8,5 +8,7 @@ Maix Comm Protocol 内部根据系统设置的通信方式(比如 uart), 用户需要使用串口时构建UART对象构造函数会先检查是否已经注册过(比如判断已经注册过的obj使用的UART编号和即将初始化的UART编号是否相同), 如果没有注册过就普通构造,已经注册过那就先回调设置的callback函数,callback 负责释放obj(注意 uart 里面 callback后UART不要再使用register_comm_callback传进来的obj了),然后再构造。 +协议帧的 `data_len` 包含 `flags` + `cmd` + `body` + `CRC`,不包含 `header` 和 `data_len` 自身。 +错误响应的错误码是 `body` 的第一个字节,后面才是错误字符串,所以错误码必须计入 `body` 长度、`data_len`、CRC 输入长度以及发送总长度。