From 4317deda4b7729e618514baf894264fb929840f4 Mon Sep 17 00:00:00 2001 From: stli Date: Sat, 11 Apr 2026 10:10:43 +0800 Subject: [PATCH] Complete typed COM decoding --- KeyBorad/KeyBorad/COM/Com_CdcDecode.cpp | 226 ++++++++++++++++++++++++ KeyBorad/KeyBorad/COM/Com_CdcDecode.h | 28 +++ docs/host_com_rebuild.md | 92 ++++++++++ 3 files changed, 346 insertions(+) create mode 100644 docs/host_com_rebuild.md diff --git a/KeyBorad/KeyBorad/COM/Com_CdcDecode.cpp b/KeyBorad/KeyBorad/COM/Com_CdcDecode.cpp index 8470afe..9bd8e2d 100644 --- a/KeyBorad/KeyBorad/COM/Com_CdcDecode.cpp +++ b/KeyBorad/KeyBorad/COM/Com_CdcDecode.cpp @@ -9,6 +9,39 @@ constexpr quint8 COM_CDC_CONST_PACKET_HEAD2 = 0x55; constexpr int COM_CDC_CONST_FRAME_OVERHEAD = 5; constexpr int COM_CDC_CONST_MAX_PAYLOAD_LENGTH = 64; +quint16 Com_Cdc_Func_ReadLe16(const QByteArray& ByteArray, int Offset) +{ + return static_cast( + static_cast(ByteArray.at(Offset)) | + (static_cast(static_cast(ByteArray.at(Offset + 1))) << 8)); +} + +quint32 Com_Cdc_Func_ReadLe32(const QByteArray& ByteArray, int Offset) +{ + quint32 Value = 0; + + for (int Index = 0; Index < 4; ++Index) + { + Value |= static_cast(static_cast(ByteArray.at(Offset + Index))) << + (Index * 8); + } + + return Value; +} + +quint64 Com_Cdc_Func_ReadLe64(const QByteArray& ByteArray, int Offset) +{ + quint64 Value = 0; + + for (int Index = 0; Index < 8; ++Index) + { + Value |= static_cast(static_cast(ByteArray.at(Offset + Index))) << + (Index * 8); + } + + return Value; +} + bool Com_Cdc_Func_IsKnownLengthValid(Packet_Type Type, quint8 DataLength) { switch (Type) @@ -48,6 +81,15 @@ bool Com_Cdc_Func_IsKnownLengthValid(Packet_Type Type, quint8 DataLength) } } +bool Com_Cdc_Func_IsExpectedPacket( + const Packet& PacketData, + Packet_Type Type, + quint8 ExpectedLength) +{ + return (PacketData.type == Type) && + (PacketData.data.size() == ExpectedLength); +} + bool Com_Cdc_Func_ParseFrameAtStart(const QByteArray& ByteArray, Packet* p_Packet) { if ((p_Packet == nullptr) || (ByteArray.size() < COM_CDC_CONST_FRAME_OVERHEAD)) @@ -196,3 +238,187 @@ bool Com_Cdc_Func_TryTakeFrame(QByteArray* p_Buffer, Packet* p_Packet) return true; } } + +bool Com_Cdc_Func_ParseHelloReq( + const Packet& PacketData, + Packet_HelloReq* p_HelloReq) +{ + if ((p_HelloReq == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_HelloReq, + Packet_len::Com_Len_HelloReq)) + { + return false; + } + + Packet_HelloReq HelloReq; + HelloReq.ProtocolVersion = static_cast(PacketData.data.at(0)); + *p_HelloReq = HelloReq; + return true; +} + +bool Com_Cdc_Func_ParseHelloRsp( + const Packet& PacketData, + Packet_HelloRsp* p_HelloRsp) +{ + if ((p_HelloRsp == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_HelloRsp, + Packet_len::Com_Len_HelloRsp)) + { + return false; + } + + Packet_HelloRsp HelloRsp; + HelloRsp.ProtocolVersion = static_cast(PacketData.data.at(0)); + HelloRsp.VendorId = Com_Cdc_Func_ReadLe16(PacketData.data, 1); + HelloRsp.ProductId = Com_Cdc_Func_ReadLe16(PacketData.data, 3); + HelloRsp.FirmwareMajor = static_cast(PacketData.data.at(5)); + HelloRsp.FirmwareMinor = static_cast(PacketData.data.at(6)); + HelloRsp.CapabilityFlags = Com_Cdc_Func_ReadLe16(PacketData.data, 7); + *p_HelloRsp = HelloRsp; + return true; +} + +bool Com_Cdc_Func_ParseBitmap( + const Packet& PacketData, + Packet_Bitmap* p_Bitmap) +{ + if ((p_Bitmap == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_Bitmap, + Packet_len::Com_Len_Bitmap)) + { + return false; + } + + Packet_Bitmap Bitmap; + Bitmap.UsageBitmap = PacketData.data; + *p_Bitmap = Bitmap; + return true; +} + +bool Com_Cdc_Func_ParseFunctionKeyEvent( + const Packet& PacketData, + Packet_FunctionKeyEvent* p_Event) +{ + if ((p_Event == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_FunctionKeyEvent, + Packet_len::Com_Len_FunctionKeyEvent)) + { + return false; + } + + Packet_FunctionKeyEvent EventData; + EventData.Usage = Com_Cdc_Func_ReadLe16(PacketData.data, 0); + EventData.Action = static_cast(PacketData.data.at(2)); + *p_Event = EventData; + return true; +} + +bool Com_Cdc_Func_ParseLedState( + const Packet& PacketData, + Packet_LedState* p_LedState) +{ + if ((p_LedState == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_LedState, + Packet_len::Com_Len_LedState)) + { + return false; + } + + Packet_LedState LedState; + LedState.LedMask = static_cast(PacketData.data.at(0)); + *p_LedState = LedState; + return true; +} + +bool Com_Cdc_Func_ParseTimeSync( + const Packet& PacketData, + Packet_TimeSync* p_TimeSync) +{ + if ((p_TimeSync == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_TimeSync, + Packet_len::Com_Len_TimeSync)) + { + return false; + } + + Packet_TimeSync TimeSync; + TimeSync.Version = static_cast(PacketData.data.at(0)); + TimeSync.Flags = static_cast(PacketData.data.at(1)); + TimeSync.TimezoneMin = Com_Cdc_Func_ReadLe16(PacketData.data, 2); + TimeSync.UtcMs = Com_Cdc_Func_ReadLe64(PacketData.data, 4); + TimeSync.AccuracyMs = Com_Cdc_Func_ReadLe32(PacketData.data, 12); + *p_TimeSync = TimeSync; + return true; +} + +bool Com_Cdc_Func_ParseThemeRgb( + const Packet& PacketData, + Packet_ThemeRgb* p_ThemeRgb) +{ + if ((p_ThemeRgb == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_ThemeRgb, + Packet_len::Com_Len_ThemeRgb)) + { + return false; + } + + Packet_ThemeRgb ThemeRgb; + ThemeRgb.Red = static_cast(PacketData.data.at(0)); + ThemeRgb.Green = static_cast(PacketData.data.at(1)); + ThemeRgb.Blue = static_cast(PacketData.data.at(2)); + *p_ThemeRgb = ThemeRgb; + return true; +} + +bool Com_Cdc_Func_ParseAck( + const Packet& PacketData, + Packet_Ack* p_Ack) +{ + if ((p_Ack == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_Ack, + Packet_len::Com_Len_Ack)) + { + return false; + } + + Packet_Ack Ack; + Ack.AckedType = static_cast(PacketData.data.at(0)); + *p_Ack = Ack; + return true; +} + +bool Com_Cdc_Func_ParseError( + const Packet& PacketData, + Packet_Error* p_Error) +{ + if ((p_Error == nullptr) || + !Com_Cdc_Func_IsExpectedPacket( + PacketData, + Com_Type_Error, + Packet_len::Com_Len_Error)) + { + return false; + } + + Packet_Error Error; + Error.ErrorType = static_cast(PacketData.data.at(0)); + Error.ErrorCode = static_cast(PacketData.data.at(1)); + *p_Error = Error; + return true; +} diff --git a/KeyBorad/KeyBorad/COM/Com_CdcDecode.h b/KeyBorad/KeyBorad/COM/Com_CdcDecode.h index 0b6f72e..cdcec95 100644 --- a/KeyBorad/KeyBorad/COM/Com_CdcDecode.h +++ b/KeyBorad/KeyBorad/COM/Com_CdcDecode.h @@ -6,3 +6,31 @@ bool Com_Cdc_Func_ParseFrame(const QByteArray& ByteArray, Packet* p_Packet); bool Com_Cdc_Func_TryTakeFrame(QByteArray* p_Buffer, Packet* p_Packet); + +bool Com_Cdc_Func_ParseHelloReq( + const Packet& PacketData, + Packet_HelloReq* p_HelloReq); +bool Com_Cdc_Func_ParseHelloRsp( + const Packet& PacketData, + Packet_HelloRsp* p_HelloRsp); +bool Com_Cdc_Func_ParseBitmap( + const Packet& PacketData, + Packet_Bitmap* p_Bitmap); +bool Com_Cdc_Func_ParseFunctionKeyEvent( + const Packet& PacketData, + Packet_FunctionKeyEvent* p_Event); +bool Com_Cdc_Func_ParseLedState( + const Packet& PacketData, + Packet_LedState* p_LedState); +bool Com_Cdc_Func_ParseTimeSync( + const Packet& PacketData, + Packet_TimeSync* p_TimeSync); +bool Com_Cdc_Func_ParseThemeRgb( + const Packet& PacketData, + Packet_ThemeRgb* p_ThemeRgb); +bool Com_Cdc_Func_ParseAck( + const Packet& PacketData, + Packet_Ack* p_Ack); +bool Com_Cdc_Func_ParseError( + const Packet& PacketData, + Packet_Error* p_Error); diff --git a/docs/host_com_rebuild.md b/docs/host_com_rebuild.md new file mode 100644 index 0000000..6189d4e --- /dev/null +++ b/docs/host_com_rebuild.md @@ -0,0 +1,92 @@ +# Host COM Rebuild + +## Goal + +Keep the COM layer small, explicit, and easy to teach. + +The COM layer should: + +- define packet types and payload structures +- build full wire frames +- parse full wire frames +- parse typed packet payloads + +The COM layer should not: + +- enumerate devices +- open ports +- own handshake state +- own UI state + +## Current file roles + +### `Com_Cdc.h` + +Owns the packet model: + +- packet types +- fixed payload lengths +- payload structs + +### `Com_CdcEncode.h/.cpp` + +Owns the write direction: + +- checksum +- full frame build +- typed payload encode + +### `Com_CdcDecode.h/.cpp` + +Owns the read direction: + +- full frame parse +- stream extraction +- typed payload decode + +## Completed nodes + +### Node 1: legacy frame encode baseline + +Already present before this step: + +- build full frame +- build all typed packets + +### Node 2: typed packet decode completion + +Files updated in this step: + +- `KeyBorad/KeyBorad/COM/Com_CdcDecode.h` +- `KeyBorad/KeyBorad/COM/Com_CdcDecode.cpp` + +Design notes: + +- keep decode logic flat and explicit +- one helper for little-endian reads +- one helper for type + length validation +- one decode function per packet type + +Implemented behavior: + +- parse frame header and checksum +- extract frames from a byte stream +- decode: + - `HelloReq` + - `HelloRsp` + - `Bitmap` + - `FunctionKeyEvent` + - `LedState` + - `TimeSync` + - `ThemeRgb` + - `Ack` + - `Error` + +## COM done criteria + +For the current project stage, COM is considered done when: + +1. every supported packet can be built +2. every supported packet can be parsed +3. stream extraction is stable +4. higher layers no longer need to know byte offsets