Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .codex
Empty file.
20 changes: 20 additions & 0 deletions core/include/librmcs/data/datas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ enum class DataId : uint8_t {
kUart3 = 14,

kImu = 15,

kI2c0 = 16,
};

struct CanDataView {
Expand Down Expand Up @@ -79,6 +81,20 @@ struct GyroscopeDataView {
int16_t z;
};

struct I2cDataView {
uint8_t slave_address;
std::span<const std::byte> payload;
bool has_register = false;
uint8_t reg_address = 0;
};

struct I2cReadConfigView {
uint8_t slave_address;
uint16_t read_length = 0;
bool has_register = false;
uint8_t reg_address = 0;
};

class DataCallback {
public:
DataCallback() = default;
Expand All @@ -100,6 +116,10 @@ class DataCallback {
virtual void accelerometer_receive_callback(const AccelerometerDataView& data) = 0;

virtual void gyroscope_receive_callback(const GyroscopeDataView& data) = 0;

virtual bool i2c_receive_callback(DataId id, const I2cDataView& data) = 0;

virtual void i2c_error_callback(DataId id, uint8_t slave_address) = 0;
};

} // namespace librmcs::data
85 changes: 85 additions & 0 deletions core/src/protocol/deserializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ coroutine::LifoTask<void> Deserializer::process_stream() {
case FieldId::kUart3: success = co_await process_uart_field(id); break;
case FieldId::kGpio: success = co_await process_gpio_field(id); break;
case FieldId::kImu: success = co_await process_imu_field(id); break;
case FieldId::kI2c0: success = co_await process_i2c_field(id); break;
default: break;
}
if (!success)
Expand Down Expand Up @@ -289,4 +290,88 @@ coroutine::LifoTask<bool> Deserializer::process_imu_field(FieldId) {
co_return true;
}

coroutine::LifoTask<bool> Deserializer::process_i2c_field(FieldId field_id) {
I2cHeader::PayloadEnum payload_type;
uint8_t slave_address = 0;
uint16_t data_length = 0;
bool has_register = false;
{
const auto* header_bytes = co_await peek_bytes(sizeof(I2cHeader));
if (!header_bytes) [[unlikely]]
co_return false;

auto header = I2cHeader::CRef{header_bytes};
payload_type = header.get<I2cHeader::PayloadType>();
has_register = header.get<I2cHeader::HasRegister>();
slave_address = header.get<I2cHeader::SlaveAddress>();
data_length = header.get<I2cHeader::DataLength>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

缺少 I2C data_length 上界校验。

data_length 直接用于 peek_bytes,但未像 UART 分支那样限制到内部缓冲能力;超大长度输入会把错误处理路径推迟到更深处,增加边界风险。

🔧 建议修改
     data_length = header.get<I2cHeader::DataLength>();
     consume_peeked();
 }
 
 if (slave_address > 0x7F) [[unlikely]]
     co_return false;
+
+if (data_length > sizeof(pending_bytes_buffer_)) [[unlikely]]
+    co_return false;

Also applies to: 329-334, 349-360

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/deserializer.cpp` at line 307, I2C 分支没有对 data_length(从
header.get<I2cHeader::DataLength>() 读取)做上界校验就传给
peek_bytes,可能导致超大长度把错误处理推迟并引发边界风险;请在使用 peek_bytes 或后续缓冲访问前参考 UART 分支的做法,对
data_length 做上界限制(例如 clamp 到内部缓冲容量或最大允许帧长),并在长度异常时直接返回/报错;修改点包含使用
header.get<I2cHeader::DataLength>() 的位置、传入 peek_bytes
的调用处以及与之对应的错误路径(同样修复类似位置的另两处出现处)。

consume_peeked();
}

if (slave_address > 0x7F) [[unlikely]]
co_return false;

switch (payload_type) {
case I2cHeader::PayloadEnum::kWrite:
case I2cHeader::PayloadEnum::kReadResult: {
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.has_register = has_register;

if (has_register) {
const auto* register_bytes = co_await peek_bytes(1);
if (!register_bytes) [[unlikely]]
co_return false;
data_view.reg_address = std::to_integer<uint8_t>(register_bytes[0]);
consume_peeked();
}

if (data_length) {
const auto* payload_bytes = co_await peek_bytes(data_length);
if (!payload_bytes) [[unlikely]]
co_return false;
data_view.payload = std::span<const std::byte>{payload_bytes, data_length};
consume_peeked();
} else {
data_view.payload = std::span<const std::byte>{};
}

if (payload_type == I2cHeader::PayloadEnum::kWrite) {
callback_.i2c_write_deserialized_callback(field_id, data_view);
} else {
callback_.i2c_read_result_deserialized_callback(field_id, data_view);
}
break;
}
case I2cHeader::PayloadEnum::kReadRequest: {
data::I2cReadConfigView data_view{};
data_view.slave_address = slave_address;
data_view.read_length = data_length;
data_view.has_register = has_register;

if (has_register) {
const auto* register_bytes = co_await peek_bytes(1);
if (!register_bytes) [[unlikely]]
co_return false;
data_view.reg_address = std::to_integer<uint8_t>(register_bytes[0]);
consume_peeked();
}

callback_.i2c_read_config_deserialized_callback(field_id, data_view);
break;
}
case I2cHeader::PayloadEnum::kError: {
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.payload = std::span<const std::byte>{};
data_view.has_register = false;
callback_.i2c_error_deserialized_callback(field_id, data_view);
break;
Comment on lines +363 to +369
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

kError 分支未消费额外字节会导致流错位。

当报文头里 has_registerdata_length 非零时,当前分支直接回调并返回成功,剩余字节会被当成下一帧头解析,导致后续解析链路错位。

🔧 建议修改
 case I2cHeader::PayloadEnum::kError: {
+    if (has_register || data_length != 0) [[unlikely]]
+        co_return false;
     data::I2cDataView data_view{};
     data_view.slave_address = slave_address;
     data_view.payload = std::span<const std::byte>{};
     data_view.has_register = false;
     callback_.i2c_error_deserialized_callback(field_id, data_view);
     break;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case I2cHeader::PayloadEnum::kError: {
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.payload = std::span<const std::byte>{};
data_view.has_register = false;
callback_.i2c_error_deserialized_callback(field_id, data_view);
break;
case I2cHeader::PayloadEnum::kError: {
if (has_register || data_length != 0) [[unlikely]]
co_return false;
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.payload = std::span<const std::byte>{};
data_view.has_register = false;
callback_.i2c_error_deserialized_callback(field_id, data_view);
break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/deserializer.cpp` around lines 363 - 369, The kError branch
in I2cHeader::PayloadEnum::kError currently returns without consuming optional
register or payload bytes, causing stream desynchronization; update the branch
in deserializer.cpp to inspect the header's has_register and data_length fields
and consume/skip the corresponding bytes from the input buffer/stream (e.g.,
read and discard the register byte if has_register is true and advance by
data_length bytes for payload) before constructing data::I2cDataView (with empty
payload) and invoking callback_.i2c_error_deserialized_callback(field_id,
data_view); if you already have a helper like consumeBytes/advanceCursor or
existing read routines, reuse them to avoid duplicating parsing logic.

}
default: co_return false;
}

co_return true;
}

} // namespace librmcs::core::protocol
12 changes: 12 additions & 0 deletions core/src/protocol/deserializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ class DeserializeCallback {

virtual void gyroscope_deserialized_callback(const data::GyroscopeDataView& data) = 0;

virtual void i2c_write_deserialized_callback(FieldId id, const data::I2cDataView& data) = 0;

virtual void
i2c_read_config_deserialized_callback(FieldId id, const data::I2cReadConfigView& data) = 0;

virtual void
i2c_read_result_deserialized_callback(FieldId id, const data::I2cDataView& data) = 0;

virtual void i2c_error_deserialized_callback(FieldId id, const data::I2cDataView& data) = 0;

virtual void error_callback() = 0;
};

Expand Down Expand Up @@ -115,6 +125,8 @@ class Deserializer : private coroutine::InlineLifoContext<1024> {

coroutine::LifoTask<bool> process_imu_field(FieldId field_id);

coroutine::LifoTask<bool> process_i2c_field(FieldId field_id);

// Await until at least `size` contiguous bytes are available at the current read position.
// Returns a pointer to a contiguous region of at least `size` bytes.
// (from input buffer or pending cache)
Expand Down
15 changes: 15 additions & 0 deletions core/src/protocol/protocol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,19 @@ struct ImuGyroscopePayload : utility::Bitfield<6> {
using Z = utility::BitfieldMember<32, 16, int16_t>;
};

struct I2cHeader : utility::Bitfield<3> {
enum class PayloadEnum : uint8_t {
kWrite = 0,
kReadRequest = 1,
kReadResult = 2,
kError = 3,
};

using PayloadType = utility::BitfieldMember<4, 2, PayloadEnum>;
using HasRegister = utility::BitfieldMember<6, 1>;
using ErrorFlag = utility::BitfieldMember<7, 1>;
using SlaveAddress = utility::BitfieldMember<8, 7>;
using DataLength = utility::BitfieldMember<15, 9>;
};

} // namespace librmcs::core::protocol
148 changes: 148 additions & 0 deletions core/src/protocol/serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,130 @@ class Serializer {
return SerializeResult::kSuccess;
}

SerializeResult write_i2c_write(FieldId field_id, const data::I2cDataView& view) noexcept {
const std::size_t required = required_i2c_size(
field_id, I2cHeader::PayloadEnum::kWrite, view.payload.size(), view.has_register);
LIBRMCS_VERIFY_LIKELY(required, SerializeResult::kInvalidArgument);
LIBRMCS_VERIFY_LIKELY(view.slave_address <= 0x7F, SerializeResult::kInvalidArgument);

auto dst = buffer_.allocate(required);
LIBRMCS_VERIFY_LIKELY(!dst.empty(), SerializeResult::kBadAlloc);
utility::assert_debug(dst.size() == required);
std::byte* cursor = dst.data();

write_field_header(cursor, field_id);

auto header = I2cHeader::Ref(cursor);
cursor += sizeof(I2cHeader);
header.set<I2cHeader::PayloadType>(I2cHeader::PayloadEnum::kWrite);
header.set<I2cHeader::HasRegister>(view.has_register);
header.set<I2cHeader::ErrorFlag>(false);
header.set<I2cHeader::SlaveAddress>(view.slave_address);
header.set<I2cHeader::DataLength>(static_cast<uint16_t>(view.payload.size()));

if (view.has_register)
*cursor++ = static_cast<std::byte>(view.reg_address);

if (!view.payload.empty()) {
std::memcpy(cursor, view.payload.data(), view.payload.size());
cursor += view.payload.size();
}

utility::assert_debug(cursor == dst.data() + dst.size());
return SerializeResult::kSuccess;
}

SerializeResult
write_i2c_read_config(FieldId field_id, const data::I2cReadConfigView& view) noexcept {
const std::size_t required = required_i2c_size(
field_id, I2cHeader::PayloadEnum::kReadRequest, view.read_length, view.has_register);
LIBRMCS_VERIFY_LIKELY(required, SerializeResult::kInvalidArgument);
LIBRMCS_VERIFY_LIKELY(view.slave_address <= 0x7F, SerializeResult::kInvalidArgument);

auto dst = buffer_.allocate(required);
LIBRMCS_VERIFY_LIKELY(!dst.empty(), SerializeResult::kBadAlloc);
utility::assert_debug(dst.size() == required);
std::byte* cursor = dst.data();

write_field_header(cursor, field_id);

auto header = I2cHeader::Ref(cursor);
cursor += sizeof(I2cHeader);
header.set<I2cHeader::PayloadType>(I2cHeader::PayloadEnum::kReadRequest);
header.set<I2cHeader::HasRegister>(view.has_register);
header.set<I2cHeader::ErrorFlag>(false);
header.set<I2cHeader::SlaveAddress>(view.slave_address);
header.set<I2cHeader::DataLength>(view.read_length);

if (view.has_register)
*cursor++ = static_cast<std::byte>(view.reg_address);

utility::assert_debug(cursor == dst.data() + dst.size());
return SerializeResult::kSuccess;
}

SerializeResult
write_i2c_read_result(FieldId field_id, const data::I2cDataView& view) noexcept {
const std::size_t required = required_i2c_size(
field_id, I2cHeader::PayloadEnum::kReadResult, view.payload.size(), view.has_register);
LIBRMCS_VERIFY_LIKELY(required, SerializeResult::kInvalidArgument);
LIBRMCS_VERIFY_LIKELY(view.slave_address <= 0x7F, SerializeResult::kInvalidArgument);

auto dst = buffer_.allocate(required);
LIBRMCS_VERIFY_LIKELY(!dst.empty(), SerializeResult::kBadAlloc);
utility::assert_debug(dst.size() == required);
std::byte* cursor = dst.data();

write_field_header(cursor, field_id);

auto header = I2cHeader::Ref(cursor);
cursor += sizeof(I2cHeader);
header.set<I2cHeader::PayloadType>(I2cHeader::PayloadEnum::kReadResult);
header.set<I2cHeader::HasRegister>(view.has_register);
header.set<I2cHeader::ErrorFlag>(false);
header.set<I2cHeader::SlaveAddress>(view.slave_address);
header.set<I2cHeader::DataLength>(static_cast<uint16_t>(view.payload.size()));

if (view.has_register)
*cursor++ = static_cast<std::byte>(view.reg_address);

if (!view.payload.empty()) {
std::memcpy(cursor, view.payload.data(), view.payload.size());
cursor += view.payload.size();
}

utility::assert_debug(cursor == dst.data() + dst.size());
return SerializeResult::kSuccess;
}

SerializeResult write_i2c_error(FieldId field_id, uint8_t slave_address) noexcept {
const std::size_t required =
required_i2c_size(field_id, I2cHeader::PayloadEnum::kError, 0, false);
LIBRMCS_VERIFY_LIKELY(required, SerializeResult::kInvalidArgument);
LIBRMCS_VERIFY_LIKELY(slave_address <= 0x7F, SerializeResult::kInvalidArgument);

auto dst = buffer_.allocate(required);
LIBRMCS_VERIFY_LIKELY(!dst.empty(), SerializeResult::kBadAlloc);
utility::assert_debug(dst.size() == required);
std::byte* cursor = dst.data();

write_field_header(cursor, field_id);

auto header = I2cHeader::Ref(cursor);
cursor += sizeof(I2cHeader);
header.set<I2cHeader::PayloadType>(I2cHeader::PayloadEnum::kError);
header.set<I2cHeader::HasRegister>(false);
header.set<I2cHeader::ErrorFlag>(true);
header.set<I2cHeader::SlaveAddress>(slave_address);
header.set<I2cHeader::DataLength>(0);

utility::assert_debug(cursor == dst.data() + dst.size());
return SerializeResult::kSuccess;
}

private:
static constexpr bool is_i2c_field_id(FieldId field_id) { return field_id == FieldId::kI2c0; }

static constexpr bool use_extended_field_header(FieldId field_id) {
utility::assert_debug(field_id != FieldId::kExtend);
return static_cast<std::uint8_t>(field_id) > 0xF;
Expand Down Expand Up @@ -437,6 +560,31 @@ class Serializer {
return total;
}

static std::size_t required_i2c_size(
FieldId field_id, I2cHeader::PayloadEnum payload, std::size_t data_len,
bool has_register) noexcept {
LIBRMCS_VERIFY_LIKELY(is_i2c_field_id(field_id), 0);
LIBRMCS_VERIFY_LIKELY(data_len <= ((1U << 9) - 1U), 0);
switch (payload) {
case I2cHeader::PayloadEnum::kWrite:
case I2cHeader::PayloadEnum::kReadRequest:
case I2cHeader::PayloadEnum::kReadResult:
case I2cHeader::PayloadEnum::kError: break;
default: return 0;
}

const std::size_t field_header_bytes = required_field_header_size(field_id);
const std::size_t i2c_header_bytes = sizeof(I2cHeader);
const std::size_t register_bytes = has_register ? 1 : 0;
const std::size_t payload_bytes =
payload == I2cHeader::PayloadEnum::kReadRequest ? 0 : data_len;
const std::size_t total =
(field_header_bytes + i2c_header_bytes - 1) + register_bytes + payload_bytes;
LIBRMCS_VERIFY_LIKELY(total <= kProtocolBufferSize, 0);

return total;
}

SerializeBuffer& buffer_;
};

Expand Down
6 changes: 6 additions & 0 deletions firmware/c_board/app/src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <device/usbd.h>
#include <dma.h>
#include <gpio.h>
#include <i2c.h>
#include <main.h>
#include <spi.h>
#include <tim.h>
Expand All @@ -12,6 +13,7 @@

#include "firmware/c_board/app/src/can/can.hpp"
#include "firmware/c_board/app/src/gpio/gpio.hpp"
#include "firmware/c_board/app/src/i2c/i2c.hpp"
#include "firmware/c_board/app/src/led/led.hpp"
#include "firmware/c_board/app/src/spi/bmi088/accel.hpp"
#include "firmware/c_board/app/src/spi/bmi088/gyro.hpp"
Expand Down Expand Up @@ -48,6 +50,8 @@ App::App() {
MX_SPI1_Init();
MX_CAN1_Init();
MX_CAN2_Init();
// c_board routes the protocol-facing logical I2C0 channel through STM32 I2C2.
MX_I2C2_Init();
MX_USART1_UART_Init();
MX_USART3_UART_Init();
MX_USART6_UART_Init();
Expand All @@ -61,6 +65,7 @@ App::App() {
uart::uart1.init();
uart::uart2.init();
uart::uart_dbus.init();
i2c::i2c0.init();
gpio::gpio.init();
spi::bmi088::accelerometer.init();
spi::bmi088::gyroscope.init();
Expand All @@ -79,6 +84,7 @@ App::App() {
can::can2->try_transmit();
usb::vendor->try_transmit();
spi::spi1->update();
i2c::i2c0->update();
usb::vendor->try_transmit();
uart::uart1->try_transmit();
usb::vendor->try_transmit();
Expand Down
Loading