From 387681fce3b8bcf31e57c1d311277c01e155f247 Mon Sep 17 00:00:00 2001 From: Erol Date: Mon, 25 Aug 2025 09:19:28 +0000 Subject: [PATCH 1/6] Initial commit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1ee5385..4b08085 100644 --- a/.gitignore +++ b/.gitignore @@ -360,3 +360,5 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd +/merge_cs_files.bat +/AllCodeMerged.txt -- Gitee From ea0f93b6f667c3e6552d2e550663f8c1c45ef066 Mon Sep 17 00:00:00 2001 From: Erol Date: Mon, 25 Aug 2025 09:19:28 +0000 Subject: [PATCH 2/6] Initial commit --- App.config | 6 + DeviceCommons(C++)/DeviceCommons(C++).cpp | 21 + DeviceCommons(C++)/DeviceCommons(C++).vcxproj | 138 ++++++ .../DeviceCommons(C++).vcxproj.filters | 27 ++ DeviceCommons(C++)/DeviceMessageBuilder.h | 161 +++++++ DeviceCommons.sln | 65 +++ .../DataHandling/Compression/Compressor.cs | 57 +++ .../DataHandling/DeviceMessageArrayPool.cs | 27 ++ .../DeviceMessageSerializerProvider.cs | 33 ++ .../DataHandling/DeviceMessageUtilities.cs | 71 +++ .../DataHandling/Formatters/HexConverter.cs | 8 + DeviceCommons/DeviceCommons.csproj | 13 + .../Abstractions/AbstractMessage.cs | 10 + .../Abstractions/AbstractMessageParser.cs | 48 +++ .../Abstractions/AbstractMessageSerializer.cs | 121 ++++++ .../Abstractions/IMessageParser.cs | 15 + .../Abstractions/IMessagePayload.cs | 7 + .../Abstractions/IMessageSerializer.cs | 23 + .../Builders/DeviceInfoBuilder.cs | 35 ++ .../Builders/DeviceInfoReadingBuilder.cs | 36 ++ .../Builders/DeviceMessageBuilder.cs | 202 +++++++++ .../Builders/IDeviceMessageBuilder.cs | 51 +++ .../DeviceMessages/Enums/CRCTypeEnum.cs | 10 + .../Enums/HeaderValueTypeEnum.cs | 8 + .../DeviceMessages/Enums/MarkEnum.cs | 25 ++ .../DeviceMessages/Enums/Reserve1Enum.cs | 8 + .../DeviceMessages/Enums/Reserve2Enum.cs | 8 + .../Enums/StateValueTypeEnum.cs | 15 + .../Enums/TimeStampFormatEnum.cs | 8 + .../Factories/DefaultStateFactory.cs | 18 + .../DeviceMessages/Factories/IStateFactory.cs | 10 + .../Factories/StateFactoryRegistry.cs | 31 ++ .../DeviceMessages/Models/V1/DeviceMessage.cs | 47 ++ .../Models/V1/DeviceMessageChild.cs | 44 ++ .../Models/V1/DeviceMessageHeader.cs | 30 ++ .../Models/V1/DeviceMessageInfo.cs | 21 + .../Models/V1/DeviceMessageInfoReading.cs | 25 ++ .../V1/DeviceMessageInfoReadingState.cs | 80 ++++ .../V1/DeviceMessageInfoReadingStates.cs | 25 ++ .../Models/V1/DeviceMessageInfoReadings.cs | 20 + .../DeviceMessages/Models/V1/IBase.cs | 8 + .../Models/V1/IDeviceMessage.cs | 12 + .../Models/V1/IDeviceMessageChild.cs | 9 + .../Models/V1/IDeviceMessageHeader.cs | 21 + .../Models/V1/IDeviceMessageInfo.cs | 16 + .../Models/V1/IDeviceMessageInfoReading.cs | 11 + .../V1/IDeviceMessageInfoReadingState.cs | 14 + .../V1/IDeviceMessageInfoReadingStates.cs | 8 + .../Models/V1/IDeviceMessageInfoReadings.cs | 9 + .../Serialization/DeviceMessageParser.cs | 43 ++ .../Serialization/DeviceMessageSerializer.cs | 35 ++ .../Serialization/IDeviceMessageParser.cs | 9 + .../Serialization/IDeviceMessageSerializer.cs | 9 + .../V1/Parsers/DeviceMessageChildParser.cs | 93 ++++ .../V1/Parsers/DeviceMessageHeaderParser.cs | 30 ++ .../V1/Parsers/DeviceMessageInfoParser.cs | 42 ++ .../Parsers/DeviceMessageInfoReadingParser.cs | 27 ++ .../DeviceMessageInfoReadingStateParser.cs | 48 +++ .../DeviceMessageInfoReadingStatesParser.cs | 49 +++ .../DeviceMessageInfoReadingsParser.cs | 42 ++ .../V1/Parsers/IDeviceMessageChildParser.cs | 9 + .../V1/Parsers/IDeviceMessageHeaderParser.cs | 10 + .../V1/Parsers/IDeviceMessageInfoParser.cs | 9 + .../IDeviceMessageInfoReadingParser.cs | 9 + .../IDeviceMessageInfoReadingStateParser.cs | 9 + .../IDeviceMessageInfoReadingStatesParser.cs | 9 + .../IDeviceMessageInfoReadingsParser.cs | 9 + .../Serialization/V1/ProcessVersionData.cs | 86 ++++ .../DeviceMessageChildSerializer.cs | 33 ++ .../DeviceMessageHeaderSerializer.cs | 35 ++ .../DeviceMessageInfoReadingSerializer.cs | 30 ++ ...DeviceMessageInfoReadingStateSerializer.cs | 66 +++ ...eviceMessageInfoReadingStatesSerializer.cs | 33 ++ .../DeviceMessageInfoReadingsSerializer.cs | 32 ++ .../DeviceMessageInfoSerializer.cs | 38 ++ .../IDeviceMessageChildSerializer.cs | 9 + .../IDeviceMessageHeaderSerializer.cs | 9 + .../IDeviceMessageInfoReadingSerializer.cs | 9 + ...DeviceMessageInfoReadingStateSerializer.cs | 9 + ...eviceMessageInfoReadingStatesSerializer.cs | 9 + .../IDeviceMessageInfoReadingsSerializer.cs | 9 + .../IDeviceMessageInfoSerializer.cs | 9 + .../Exceptions/InvalidMessageException.cs | 12 + DeviceCommons/Security/AesEncryptor.cs | 192 +++++++++ DeviceCommons/Security/CrcCalculator.cs | 84 ++++ TestProject1/BaseUnitTest.cs | 20 + TestProject1/BasicStructureUnitTest.cs | 170 ++++++++ TestProject1/BoundaryUnitTest.cs | 408 ++++++++++++++++++ TestProject1/BuildUnitTest.cs | 157 +++++++ TestProject1/ComprehensiveUnitTest.cs | 348 +++++++++++++++ TestProject1/TestProject1.csproj | 28 ++ 91 files changed, 4042 insertions(+) create mode 100644 App.config create mode 100644 DeviceCommons(C++)/DeviceCommons(C++).cpp create mode 100644 DeviceCommons(C++)/DeviceCommons(C++).vcxproj create mode 100644 DeviceCommons(C++)/DeviceCommons(C++).vcxproj.filters create mode 100644 DeviceCommons(C++)/DeviceMessageBuilder.h create mode 100644 DeviceCommons.sln create mode 100644 DeviceCommons/DataHandling/Compression/Compressor.cs create mode 100644 DeviceCommons/DataHandling/DeviceMessageArrayPool.cs create mode 100644 DeviceCommons/DataHandling/DeviceMessageSerializerProvider.cs create mode 100644 DeviceCommons/DataHandling/DeviceMessageUtilities.cs create mode 100644 DeviceCommons/DataHandling/Formatters/HexConverter.cs create mode 100644 DeviceCommons/DeviceCommons.csproj create mode 100644 DeviceCommons/DeviceMessages/Abstractions/AbstractMessage.cs create mode 100644 DeviceCommons/DeviceMessages/Abstractions/AbstractMessageParser.cs create mode 100644 DeviceCommons/DeviceMessages/Abstractions/AbstractMessageSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Abstractions/IMessageParser.cs create mode 100644 DeviceCommons/DeviceMessages/Abstractions/IMessagePayload.cs create mode 100644 DeviceCommons/DeviceMessages/Abstractions/IMessageSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Builders/DeviceInfoBuilder.cs create mode 100644 DeviceCommons/DeviceMessages/Builders/DeviceInfoReadingBuilder.cs create mode 100644 DeviceCommons/DeviceMessages/Builders/DeviceMessageBuilder.cs create mode 100644 DeviceCommons/DeviceMessages/Builders/IDeviceMessageBuilder.cs create mode 100644 DeviceCommons/DeviceMessages/Enums/CRCTypeEnum.cs create mode 100644 DeviceCommons/DeviceMessages/Enums/HeaderValueTypeEnum.cs create mode 100644 DeviceCommons/DeviceMessages/Enums/MarkEnum.cs create mode 100644 DeviceCommons/DeviceMessages/Enums/Reserve1Enum.cs create mode 100644 DeviceCommons/DeviceMessages/Enums/Reserve2Enum.cs create mode 100644 DeviceCommons/DeviceMessages/Enums/StateValueTypeEnum.cs create mode 100644 DeviceCommons/DeviceMessages/Enums/TimeStampFormatEnum.cs create mode 100644 DeviceCommons/DeviceMessages/Factories/DefaultStateFactory.cs create mode 100644 DeviceCommons/DeviceMessages/Factories/IStateFactory.cs create mode 100644 DeviceCommons/DeviceMessages/Factories/StateFactoryRegistry.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessage.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessageChild.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessageHeader.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfo.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReading.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingState.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingStates.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadings.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IBase.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessage.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageChild.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageHeader.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfo.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReading.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingState.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingStates.cs create mode 100644 DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadings.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/DeviceMessageParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/DeviceMessageSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/IDeviceMessageParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/IDeviceMessageSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageChildParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageHeaderParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStateParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStatesParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingsParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageChildParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageHeaderParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStateParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStatesParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingsParser.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/ProcessVersionData.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageChildSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageHeaderSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStateSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStatesSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingsSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageChildSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageHeaderSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStateSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStatesSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingsSerializer.cs create mode 100644 DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoSerializer.cs create mode 100644 DeviceCommons/Exceptions/InvalidMessageException.cs create mode 100644 DeviceCommons/Security/AesEncryptor.cs create mode 100644 DeviceCommons/Security/CrcCalculator.cs create mode 100644 TestProject1/BaseUnitTest.cs create mode 100644 TestProject1/BasicStructureUnitTest.cs create mode 100644 TestProject1/BoundaryUnitTest.cs create mode 100644 TestProject1/BuildUnitTest.cs create mode 100644 TestProject1/ComprehensiveUnitTest.cs create mode 100644 TestProject1/TestProject1.csproj diff --git a/App.config b/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/DeviceCommons(C++)/DeviceCommons(C++).cpp b/DeviceCommons(C++)/DeviceCommons(C++).cpp new file mode 100644 index 0000000..f4f7dd5 --- /dev/null +++ b/DeviceCommons(C++)/DeviceCommons(C++).cpp @@ -0,0 +1,21 @@ +#include "DeviceMessageBuilder.h" +#include + +int main() { + using namespace DeviceCommons; + + auto msg = DeviceMessageBuilder{} + .withVersion(2) + .withCRC(CRCType::CRC16) + .withMainDevice("MainDev", 0x94, { + Reading{1000, {State::makeString(1,"Online")}}, + Reading{2000, {State::makeString(2,"OK")}} + }) + .addChild("Child1", 0x95, { + Reading{3000, {State::makeString(1,"Temp"), State::makeString(2,"25.3")}} + }) + .buildHex(); + + std::cout << msg << std::endl; + return 0; +} \ No newline at end of file diff --git a/DeviceCommons(C++)/DeviceCommons(C++).vcxproj b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj new file mode 100644 index 0000000..7b77a62 --- /dev/null +++ b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj @@ -0,0 +1,138 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {1e257bea-3355-4c08-b114-d40a5db66d2f} + DeviceCommonsC + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/DeviceCommons(C++)/DeviceCommons(C++).vcxproj.filters b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj.filters new file mode 100644 index 0000000..0a66dc9 --- /dev/null +++ b/DeviceCommons(C++)/DeviceCommons(C++).vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/DeviceCommons(C++)/DeviceMessageBuilder.h b/DeviceCommons(C++)/DeviceMessageBuilder.h new file mode 100644 index 0000000..263a9a1 --- /dev/null +++ b/DeviceCommons(C++)/DeviceMessageBuilder.h @@ -0,0 +1,161 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace DeviceCommons { + + // ==================== ö ==================== + enum class CRCType : uint8_t { None = 0, CRC8 = 1, CRC16 = 2, CRC32 = 3 }; + enum class TimeStampFormat : uint8_t { MS = 0, S = 1 }; + enum class HeaderValueType : uint8_t { Standard = 0, Extend = 1 }; + enum class StateValueType : uint8_t { + Float32 = 1, Int32 = 2, String = 3, Bool = 4, + ShortFloat = 5, UInt16 = 6, Int16 = 7, + Timestamp = 8, Binary = 9, Double = 10 + }; + + // ==================== ==================== + inline std::string toHex(const std::vector& v) { + std::ostringstream oss; + for (auto b : v) oss << std::hex << std::setw(2) << std::setfill('0') << (int)b; + return oss.str(); + } + + inline void writeBE16(std::vector& dst, uint16_t v) { + dst.push_back(static_cast((v >> 8) & 0xFF)); + dst.push_back(static_cast(v & 0xFF)); + } + + // ============ ״̬ ============ + struct State { + uint8_t sid; + StateValueType type; + std::vector value; + + static State makeString(uint8_t id, const std::string& str) { + State s{ id, StateValueType::String, {} }; + s.value.push_back(static_cast(str.size())); + s.value.insert(s.value.end(), str.begin(), str.end()); + return s; + } + }; + + // ============ ============ + struct Reading { + int16_t timeOffset; + std::vector states; + + std::vector serialize() const { + std::vector out; + writeBE16(out, static_cast(timeOffset)); + out.push_back(static_cast(states.size())); + for (const auto& st : states) { + out.push_back(st.sid); + out.push_back(static_cast(st.type)); + out.insert(out.end(), st.value.begin(), st.value.end()); + } + return out; + } + }; + + // ============ 豸Ϣ ============ + struct DeviceInfo { + std::string did; + uint8_t deviceType; + std::vector readings; + + std::vector serialize() const { + std::vector out; + out.push_back(static_cast(did.size())); + out.insert(out.end(), did.begin(), did.end()); + out.push_back(deviceType); + out.push_back(static_cast(readings.size())); + for (const auto& r : readings) { + auto sr = r.serialize(); + out.insert(out.end(), sr.begin(), sr.end()); + } + return out; + } + }; + + // ==================== CRC-16/IBM ==================== + namespace { + uint16_t crc16(const std::vector& data) { + uint16_t crc = 0x0000; + for (uint8_t b : data) { + crc ^= uint16_t(b) << 8; + for (int i = 0; i < 8; ++i) + crc = (crc & 0x8000) ? (crc << 1) ^ 0x8005 : crc << 1; + crc &= 0xFFFF; + } + return crc; + } + } + + // ==================== Builder ==================== + class DeviceMessageBuilder { + uint8_t version_ = 1; + CRCType crcType_ = CRCType::CRC16; + TimeStampFormat tsFormat_ = TimeStampFormat::MS; + HeaderValueType valueType_ = HeaderValueType::Standard; + + DeviceInfo mainDevice_; + std::vector children_; + + public: + DeviceMessageBuilder& withVersion(uint8_t v) { version_ = v; return *this; } + DeviceMessageBuilder& withCRC(CRCType c) { crcType_ = c; return *this; } + + DeviceMessageBuilder& withMainDevice(const std::string& did, uint8_t type, + std::vector readings = {}) { + mainDevice_ = DeviceInfo{ did, type, std::move(readings) }; + return *this; + } + + DeviceMessageBuilder& addChild(const std::string& did, uint8_t type, + std::vector readings = {}) { + children_.push_back(DeviceInfo{ did, type, std::move(readings) }); + return *this; + } + + std::vector buildBytes() const { + std::vector out; + + // 1. Header (4 bytes) + out.push_back(0xC0); out.push_back(0xBF); + out.push_back(version_); + uint8_t mark = (static_cast(crcType_) << 4) | + (static_cast(valueType_) << 1) | + static_cast(tsFormat_); + out.push_back(mark); + + // 2. Main Device + auto main = mainDevice_.serialize(); + out.insert(out.end(), main.begin(), main.end()); + + // 3. Child Devices + out.push_back(static_cast(children_.size())); + for (const auto& ch : children_) { + auto c = ch.serialize(); + out.insert(out.end(), c.begin(), c.end()); + } + + // 4. CRC16 + if (crcType_ == CRCType::CRC16) { + uint16_t crc = crc16(out); + out.push_back(static_cast(crc & 0xFF)); // low + out.push_back(static_cast((crc >> 8) & 0xFF)); // high + } + return out; + } + + std::string buildHex() const { + return toHex(buildBytes()); + } + }; + +} // namespace DeviceCommons \ No newline at end of file diff --git a/DeviceCommons.sln b/DeviceCommons.sln new file mode 100644 index 0000000..5a7b566 --- /dev/null +++ b/DeviceCommons.sln @@ -0,0 +1,65 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35931.197 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCommons", "DeviceCommons\DeviceCommons.csproj", "{8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "TestProject1\TestProject1.csproj", "{5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DeviceCommons(C++)", "DeviceCommons(C++)\DeviceCommons(C++).vcxproj", "{1E257BEA-3355-4C08-B114-D40A5DB66D2F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x64.ActiveCfg = Debug|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x64.Build.0 = Debug|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x86.ActiveCfg = Debug|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Debug|x86.Build.0 = Debug|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|Any CPU.Build.0 = Release|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x64.ActiveCfg = Release|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x64.Build.0 = Release|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x86.ActiveCfg = Release|Any CPU + {8D3C75A1-DB89-4EAF-A1B5-460E63466D1F}.Release|x86.Build.0 = Release|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x64.ActiveCfg = Debug|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x64.Build.0 = Debug|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x86.ActiveCfg = Debug|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Debug|x86.Build.0 = Debug|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|Any CPU.Build.0 = Release|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x64.ActiveCfg = Release|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x64.Build.0 = Release|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x86.ActiveCfg = Release|Any CPU + {5AB61AE6-42B3-4CA9-BD2A-BF7B34891247}.Release|x86.Build.0 = Release|Any CPU + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|Any CPU.Build.0 = Debug|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x64.ActiveCfg = Debug|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x64.Build.0 = Debug|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x86.ActiveCfg = Debug|Win32 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Debug|x86.Build.0 = Debug|Win32 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|Any CPU.ActiveCfg = Release|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|Any CPU.Build.0 = Release|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x64.ActiveCfg = Release|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x64.Build.0 = Release|x64 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x86.ActiveCfg = Release|Win32 + {1E257BEA-3355-4C08-B114-D40A5DB66D2F}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {ED6E20DB-4EE1-4410-8B29-43312EDA8F02} + EndGlobalSection +EndGlobal diff --git a/DeviceCommons/DataHandling/Compression/Compressor.cs b/DeviceCommons/DataHandling/Compression/Compressor.cs new file mode 100644 index 0000000..b079b35 --- /dev/null +++ b/DeviceCommons/DataHandling/Compression/Compressor.cs @@ -0,0 +1,57 @@ +using System.IO.Compression; + +namespace DeviceCommons.DataHandling.Compression +{ + public class Compressor + { + public static byte[] Compress(ReadOnlySpan data) + { + return Compress(data.ToArray()); + } + + public static byte[] Compress(byte[] data) + { + using var compressedStream = new MemoryStream(); + using var gzipStream = new GZipStream(compressedStream, CompressionLevel.Optimal); + gzipStream.Write(data, 0, data.Length); + return compressedStream.ToArray(); + } + + public static byte[] Decompress(byte[] compressedData) + { + using (var compressedStream = new MemoryStream(compressedData)) + { + using (var decompressedStream = new MemoryStream()) + { + using (var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress)) + { + gzipStream.CopyTo(decompressedStream); + } + return decompressedStream.ToArray(); + } + } + } + + public static async ValueTask CompressAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default) + { + using var compressedStream = new MemoryStream(); + using (var gzipStream = new GZipStream(compressedStream, CompressionLevel.Optimal, true)) + { + await gzipStream.WriteAsync(data, cancellationToken).ConfigureAwait(false); + await gzipStream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + return compressedStream.ToArray(); + } + + public static async ValueTask DecompressAsync(ReadOnlyMemory compressedData, CancellationToken cancellationToken = default) + { + using var compressedStream = new MemoryStream(compressedData.ToArray()); + using var decompressedStream = new MemoryStream(); + using (var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress, true)) + { + await gzipStream.CopyToAsync(decompressedStream, cancellationToken).ConfigureAwait(false); + } + return decompressedStream.ToArray(); + } + } +} diff --git a/DeviceCommons/DataHandling/DeviceMessageArrayPool.cs b/DeviceCommons/DataHandling/DeviceMessageArrayPool.cs new file mode 100644 index 0000000..48ce3b4 --- /dev/null +++ b/DeviceCommons/DataHandling/DeviceMessageArrayPool.cs @@ -0,0 +1,27 @@ +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DataHandling +{ + /// + /// 提供设备消息处理过程中使用的内存池和数组池资源 + /// 用于优化内存分配,减少垃圾回收压力 + /// + public class DeviceMessageArrayPool + { + /// 共享字节内存池实例 + internal static readonly MemoryPool MemoryPool = MemoryPool.Shared; + + /// 共享字节数组池实例 + internal static readonly ArrayPool ByteArrayPool = ArrayPool.Shared; + + /// 设备消息信息对象数组池 + internal static readonly ArrayPool Info_ArrayPool = ArrayPool.Shared; + + /// 设备消息读数信息对象数组池 + internal static readonly ArrayPool InfoReading_ArrayPool = ArrayPool.Shared; + + /// 设备消息读数状态对象数组池 + internal static readonly ArrayPool InfoReadingState_ArrayPool = ArrayPool.Shared; + } +} diff --git a/DeviceCommons/DataHandling/DeviceMessageSerializerProvider.cs b/DeviceCommons/DataHandling/DeviceMessageSerializerProvider.cs new file mode 100644 index 0000000..1296e35 --- /dev/null +++ b/DeviceCommons/DataHandling/DeviceMessageSerializerProvider.cs @@ -0,0 +1,33 @@ +using DeviceCommons.DeviceMessages.Serialization; +using DeviceCommons.DeviceMessages.Serialization.V1.Parsers; +using DeviceCommons.DeviceMessages.Serialization.V1.Serializers; + +namespace DeviceCommons.DataHandling +{ + public static class DeviceMessageSerializerProvider + { + public static readonly IDeviceMessageSerializer MessageSer = new DeviceMessageSerializer(); + public static readonly IDeviceMessageParser MessagePar = new DeviceMessageParser(); + + public static readonly IDeviceMessageHeaderSerializer HeaderSer = new DeviceMessageHeaderSerializer(); + public static readonly IDeviceMessageHeaderParser HeaderPar = new DeviceMessageHeaderParser(); + + public static readonly IDeviceMessageInfoSerializer InfoSer = new DeviceMessageInfoSerializer(); + public static readonly IDeviceMessageInfoParser InfoPar = new DeviceMessageInfoParser(); + + public static readonly IDeviceMessageInfoReadingsSerializer InfoReadingsSer = new DeviceMessageInfoReadingsSerializer(); + public static readonly IDeviceMessageInfoReadingsParser InfoReadingsPar = new DeviceMessageInfoReadingsParser(); + + public static readonly IDeviceMessageInfoReadingSerializer InfoReadingSer = new DeviceMessageInfoReadingSerializer(); + public static readonly IDeviceMessageInfoReadingParser InfoReadingPar = new DeviceMessageInfoReadingParser(); + + public static readonly IDeviceMessageInfoReadingStatesSerializer InfoReadingStatesSer = new DeviceMessageInfoReadingStatesSerializer(); + public static readonly IDeviceMessageInfoReadingStatesParser InfoReadingStatesPar = new DeviceMessageInfoReadingStatesParser(); + + public static readonly IDeviceMessageInfoReadingStateSerializer InfoReadingStateSer = new DeviceMessageInfoReadingStateSerializer(); + public static readonly IDeviceMessageInfoReadingStateParser InfoReadingStatePar = new DeviceMessageInfoReadingStateParser(); + + public static readonly IDeviceMessageChildSerializer ChildSer = new DeviceMessageChildSerializer(); + public static readonly IDeviceMessageChildParser ChildPar = new DeviceMessageChildParser(); + } +} diff --git a/DeviceCommons/DataHandling/DeviceMessageUtilities.cs b/DeviceCommons/DataHandling/DeviceMessageUtilities.cs new file mode 100644 index 0000000..daf10fb --- /dev/null +++ b/DeviceCommons/DataHandling/DeviceMessageUtilities.cs @@ -0,0 +1,71 @@ +using DeviceCommons.DeviceMessages.Enums; + +namespace DeviceCommons.DataHandling +{ + public class DeviceMessageUtilities + { + public static void CheckBuffer(ReadOnlySpan buffer, int startIndex, int requiredLength) + { + ArgumentNullException.ThrowIfNull(nameof(buffer)); + + if ((uint)startIndex >= (uint)buffer.Length) + throw new ArgumentOutOfRangeException(nameof(startIndex)); + + if (buffer.Length - startIndex < requiredLength) + throw new ArgumentException("Insufficient buffer length"); + + if (buffer.IsEmpty || startIndex < 0 || startIndex >= buffer.Length) + throw new ArgumentException("Invalid data or startIndex"); + } + + public static int GetValueLength(StateValueTypeEnum valueType, ReadOnlySpan data, int startIndex = 0) + { + switch (valueType) + { + case StateValueTypeEnum.Float32: + case StateValueTypeEnum.Int32: + return 4; + case StateValueTypeEnum.String: + if (data == null || data.Length <= startIndex) + throw new FormatException("Invalid data format for string length field."); + return data[startIndex]; + case StateValueTypeEnum.Bool: + return 1; + case StateValueTypeEnum.UInt16: + case StateValueTypeEnum.Int16: + return 2; + case StateValueTypeEnum.Binary: + if (data == null || data.Length < startIndex + 2) + throw new ArgumentException("Invalid data for binary length"); + return BitConverter.ToUInt16(data.ToArray(), startIndex); + case StateValueTypeEnum.Timestamp: + return 8; + case StateValueTypeEnum.Double: + return 8; + default: + throw new ArgumentOutOfRangeException(nameof(valueType), valueType, null); + } + } + + public static int GetValueLength(ReadOnlySpan data, int startIndex) + { + CheckBuffer(data, startIndex, 0); + if (data[startIndex] > 10) + throw new ArgumentException("Insufficient buffer length"); + + + var valueType = (StateValueTypeEnum)data[startIndex]; + switch (valueType) + { + case StateValueTypeEnum.String: + CheckBuffer(data, startIndex, 1); + return GetValueLength(valueType, data, startIndex + 1) + 1; + case StateValueTypeEnum.Binary: + CheckBuffer(data, startIndex, 2); + return GetValueLength(valueType, data, startIndex + 1) + 2; + default: + return GetValueLength(valueType,null); + } + } + } +} diff --git a/DeviceCommons/DataHandling/Formatters/HexConverter.cs b/DeviceCommons/DataHandling/Formatters/HexConverter.cs new file mode 100644 index 0000000..a0f955d --- /dev/null +++ b/DeviceCommons/DataHandling/Formatters/HexConverter.cs @@ -0,0 +1,8 @@ +namespace DeviceCommons.DataHandling.Formatters +{ + public static class HexConverter + { + public static byte[] FromHexString(this string hex) => Convert.FromHexString(hex); + public static string ToHexString(this ReadOnlySpan bytes) => Convert.ToHexString(bytes); + } +} diff --git a/DeviceCommons/DeviceCommons.csproj b/DeviceCommons/DeviceCommons.csproj new file mode 100644 index 0000000..407846a --- /dev/null +++ b/DeviceCommons/DeviceCommons.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/DeviceCommons/DeviceMessages/Abstractions/AbstractMessage.cs b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessage.cs new file mode 100644 index 0000000..14a6d2a --- /dev/null +++ b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessage.cs @@ -0,0 +1,10 @@ +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Abstractions +{ + public abstract class AbstractMessage where T : IBase + { + public T Model { get; set; } + protected AbstractMessage(T t) => Model = t; + } +} diff --git a/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageParser.cs b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageParser.cs new file mode 100644 index 0000000..996cbf8 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageParser.cs @@ -0,0 +1,48 @@ +using DeviceCommons.DataHandling.Compression; +using DeviceCommons.DataHandling.Formatters; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Abstractions +{ + public abstract class AbstractMessageParser : AbstractMessage, IMessageParser where T : IBase + { + protected AbstractMessageParser(T t) : base(t) + { + } + + public virtual Func? DecryptFunc { get; set; } + public abstract T Parser(ReadOnlySpan bytes); + + public virtual T Parser(string data) + { + IsEncryptOrCompress(data, out string dataTemp, out bool isEncrypt, out bool isCompress); + if (isEncrypt) + { + if (DecryptFunc == null) throw new Exception("解密方法未定义"); + dataTemp = DecryptFunc(dataTemp); + } + var bytes = dataTemp.FromHexString(); + + return Parser(isCompress ? Compressor.Decompress(bytes) : bytes); + } + + public virtual async Task ParserAsync(byte[] bytes) => await Task.Run(() => Parser(bytes)); + + public virtual async Task ParserAsync(string data) => await Task.Run(() => Parser(data)); + + public static void IsEncryptOrCompress(string sourceData, out string data, out bool isEncrypt, out bool isCompress) + { + var decSplit = sourceData.Split('|'); + data = decSplit[0]; + isEncrypt = false; + isCompress = false; + if (decSplit.Length > 1) + { + var states = decSplit[0].Split(','); + data = decSplit[1]; + isEncrypt = states[0].ToLower().Equals("enc"); + isCompress = states[1].ToLower().Equals("gzip"); + } + } + } +} diff --git a/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageSerializer.cs b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageSerializer.cs new file mode 100644 index 0000000..47fa85a --- /dev/null +++ b/DeviceCommons/DeviceMessages/Abstractions/AbstractMessageSerializer.cs @@ -0,0 +1,121 @@ +using DeviceCommons.DataHandling.Compression; +using DeviceCommons.DataHandling.Formatters; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Abstractions +{ + public abstract class AbstractMessageSerializer : AbstractMessage, IMessageSerializer where T : IBase + { + protected AbstractMessageSerializer(T t) : base(t) + { + } + + public Func? EncryptFunc { get; set; } + + public virtual void Serializer( + ArrayBufferWriter writer, T message + ) + { + Model = message; + Serializer(writer); + } + + public abstract void Serializer( + ArrayBufferWriter writer + ); + + public string Serializer( + ArrayBufferWriter writer, T message, bool isCompress + ) => + Serializer(writer, message, false, isCompress); + + public string Serializer( + ArrayBufferWriter writer, bool isCompress + ) => + Serializer(writer, false, isCompress); + + public virtual string Serializer(ArrayBufferWriter writer, T message, bool isEncrypt = false, bool isCompress = false) + { + string header = isEncrypt ? "enc," : "dec,"; + header += isCompress ? "gzip|" : "raw|"; + Serializer(writer, message); + ReadOnlySpan bytes = writer.WrittenSpan; + if (isCompress) bytes = Compressor.Compress(writer.GetSpan()); + string hex = bytes.ToHexString().ToUpper(); + if (isEncrypt) + { + if (EncryptFunc == null) + throw new InvalidOperationException("Encryption function is not defined. Please configure it via SetEncryptFunc."); + hex = EncryptFunc(hex); + } + return header + hex; + } + + public string Serializer(ArrayBufferWriter writer, bool isEncrypt = false, bool isCompress = false) + { + string header = isEncrypt ? "enc," : "dec,"; + header += isCompress ? "gzip|" : "raw|"; + Serializer(writer); + ReadOnlySpan bytes = writer.GetSpan(); + if (isCompress) bytes = Compressor.Compress(bytes); + string hex = bytes.ToHexString(); + if (isEncrypt) + { + if (EncryptFunc == null) throw new InvalidOperationException("加密方法未定义,请先通过SetEncryptFunc配置"); + hex = EncryptFunc(hex); + } + return header + hex; + } + + public virtual async Task SerializerAsync( + ArrayBufferWriter writer, T message + ) => + await Task.Run(() => Serializer(writer, message)); + + public virtual async Task SerializerAsync( + ArrayBufferWriter writer + ) => + await Task.Run(() => Serializer(writer)); + + public virtual async Task SerializerAsync( + ArrayBufferWriter writer, T message, bool isCompress + ) => + await Task.Run(() => Serializer(writer, message, isCompress)); + + public virtual async Task SerializerAsync( + ArrayBufferWriter writer, bool isCompress + ) => + await Task.Run(() => Serializer(writer, isCompress)); + + public virtual async Task SerializerAsync( + ArrayBufferWriter writer, T message, bool isEncrypt, bool isCompress + ) => + await Task.Run(() => Serializer(writer, message, isEncrypt, isCompress)); + + public virtual async Task SerializerAsync( + ArrayBufferWriter writer, bool isEncrypt, bool isCompress + ) => + await Task.Run(() => Serializer(writer, isEncrypt, isCompress)); + + protected virtual void SerializeWithHeader(ArrayBufferWriter writer, int headerLength, + Action> writeHeaderAction, Action> serializeContentAction) + { + ArgumentNullException.ThrowIfNull(serializeContentAction); + + serializeContentAction?.Invoke(writer); + + int existingLength = writer.WrittenCount; + byte[] existingData = existingLength > 0 ? writer.WrittenSpan.ToArray() : []; + + writer.Clear(); + + writeHeaderAction?.Invoke(writer); + + if (existingLength > 0) + { + writer.Write(existingData); + } + } + } +} diff --git a/DeviceCommons/DeviceMessages/Abstractions/IMessageParser.cs b/DeviceCommons/DeviceMessages/Abstractions/IMessageParser.cs new file mode 100644 index 0000000..31d69f9 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Abstractions/IMessageParser.cs @@ -0,0 +1,15 @@ +namespace DeviceCommons.DeviceMessages.Abstractions +{ + public interface IMessageParser : IMessagePayload + { + Func? DecryptFunc { get; set; } + + T Parser(ReadOnlySpan bytes); + + T Parser(string data); + + Task ParserAsync(byte[] bytes); + + Task ParserAsync(string data); + } +} diff --git a/DeviceCommons/DeviceMessages/Abstractions/IMessagePayload.cs b/DeviceCommons/DeviceMessages/Abstractions/IMessagePayload.cs new file mode 100644 index 0000000..c47ec21 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Abstractions/IMessagePayload.cs @@ -0,0 +1,7 @@ +namespace DeviceCommons.DeviceMessages.Abstractions +{ + public interface IMessagePayload + { + T Model { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Abstractions/IMessageSerializer.cs b/DeviceCommons/DeviceMessages/Abstractions/IMessageSerializer.cs new file mode 100644 index 0000000..4c2751e --- /dev/null +++ b/DeviceCommons/DeviceMessages/Abstractions/IMessageSerializer.cs @@ -0,0 +1,23 @@ +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Abstractions +{ + public interface IMessageSerializer : IMessagePayload where T : IBase + { + Func? EncryptFunc { get; set; } + + void Serializer(ArrayBufferWriter writer); + + void Serializer(ArrayBufferWriter writer, T message); + + string Serializer(ArrayBufferWriter writer, T message, bool isCompress); + + string Serializer(ArrayBufferWriter writer, T message, bool isEncrypt = false, bool isCompress = false); + + Task SerializerAsync(ArrayBufferWriter writer, T message); + Task SerializerAsync(ArrayBufferWriter writer, T message, bool isCompress); + + Task SerializerAsync(ArrayBufferWriter writer, T message, bool isEncrypt, bool isCompress); + } +} diff --git a/DeviceCommons/DeviceMessages/Builders/DeviceInfoBuilder.cs b/DeviceCommons/DeviceMessages/Builders/DeviceInfoBuilder.cs new file mode 100644 index 0000000..78faba0 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Builders/DeviceInfoBuilder.cs @@ -0,0 +1,35 @@ +using DeviceCommons.DeviceMessages.Models; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Builders +{ + public class DeviceInfoBuilder + { + private readonly DeviceMessageInfo _device; + private readonly List _readings = new(); + + public DeviceInfoBuilder(DeviceMessageInfo device) => _device = device; + + public DeviceInfoBuilder AddReading(short timeOffset, Action config) + { + var reading = new DeviceMessageInfoReading + { + TimeOffset = timeOffset + }; + + var builder = new DeviceInfoReadingBuilder(reading, _device.DeviceType); + config(builder); + _readings.Add(builder.Build()); + return this; + } + + public DeviceMessageInfo Build() + { + _device.Reading = new DeviceMessageInfoReadings + { + ReadingArray = _readings.ToArray() + }; + return _device; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Builders/DeviceInfoReadingBuilder.cs b/DeviceCommons/DeviceMessages/Builders/DeviceInfoReadingBuilder.cs new file mode 100644 index 0000000..c2c49e6 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Builders/DeviceInfoReadingBuilder.cs @@ -0,0 +1,36 @@ +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Factories; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Builders +{ + public class DeviceInfoReadingBuilder + { + private readonly DeviceMessageInfoReading _reading; + private readonly List _states = new(); + private readonly byte _deviceType; + + public DeviceInfoReadingBuilder(DeviceMessageInfoReading reading, byte deviceType) + { + _reading = reading; + _deviceType = deviceType; + } + + public DeviceInfoReadingBuilder AddState(byte sid, object value, StateValueTypeEnum? valueType = null) + { + var factory = StateFactoryRegistry.GetFactory(_deviceType); + var state = factory.CreateState(sid, value, valueType); + _states.Add(state); + return this; + } + + public DeviceMessageInfoReading Build() + { + _reading.State = new DeviceMessageInfoReadingStates + { + StateArray = [.. _states] + }; + return _reading; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Builders/DeviceMessageBuilder.cs b/DeviceCommons/DeviceMessages/Builders/DeviceMessageBuilder.cs new file mode 100644 index 0000000..6e23ca2 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Builders/DeviceMessageBuilder.cs @@ -0,0 +1,202 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; +using DeviceCommons.Security; +using System.Buffers; +using System.Collections.Frozen; + +namespace DeviceCommons.DeviceMessages.Builders +{ + public class DeviceMessageBuilder : IDeviceMessageBuilder + { + private readonly DeviceMessage _message = new(); + private DeviceInfoBuilder? _currentDeviceBuilder; + + public static IDeviceMessageBuilder Create() => new DeviceMessageBuilder(); + + public IDeviceMessageBuilder WithHeader( + byte version = 0x01, + CRCTypeEnum crcType = CRCTypeEnum.CRC16, + TimeStampFormatEnum timeFormat = TimeStampFormatEnum.MS, + HeaderValueTypeEnum valueType = HeaderValueTypeEnum.Standard) + { + _message.Header = new DeviceMessageHeader + { + Version = version, + CRCType = crcType, + TimeStampFormat = timeFormat, + ValueType = valueType + }; + return this; + } + + public IDeviceMessageBuilder WithVersion(byte version) + { + _message.Header ??= new DeviceMessageHeader(); + _message.Header.Version = version; + return this; + } + + public IDeviceMessageBuilder WithMainDevice(string did, byte deviceType) + { + return WithMainDevice(did, deviceType, _ => { }); + } + + public IDeviceMessageBuilder WithMainDevice(string did, byte deviceType, Action config) + { + var device = new DeviceMessageInfo + { + DID = did, + DeviceType = deviceType + }; + + _currentDeviceBuilder = new DeviceInfoBuilder(device); + config(_currentDeviceBuilder); + _message.MainDevice = _currentDeviceBuilder.Build(); + return this; + } + + public IDeviceMessageBuilder WithChildDevice(string did, byte deviceType, Action config) + { + if (_message.MainDevice == null) + throw new InvalidOperationException("Main device must be set before adding child devices"); + + var device = new DeviceMessageInfo + { + DID = did, + DeviceType = deviceType + }; + + var builder = new DeviceInfoBuilder(device); + config(builder); + + _message.ChildDevice ??= new DeviceMessageChild(); + _message.ChildDevice.AddOrUpdateChild(builder.Build()); + + return this; + } + + public IDeviceMessageBuilder WithEncryption( + Func? encryptFunc = null, + Func? decryptFunc = null) + { + DeviceMessageSerializerProvider.MessagePar.DecryptFunc = decryptFunc; + DeviceMessageSerializerProvider.MessageSer.EncryptFunc = encryptFunc; + return this; + } + + public IDeviceMessageBuilder WithAesEncryption(string password) + { + var aes = new AesEncryptor(); + return WithEncryption( + plainText => aes.Encrypt(plainText, password), + cipherText => aes.Decrypt(cipherText, password) + ); + } + + public IDeviceMessageBuilder AddReading(short timeOffset, Action config) + { + if (_currentDeviceBuilder == null) + throw new InvalidOperationException("No active device configuration"); + + _currentDeviceBuilder.AddReading(timeOffset, config); + return this; + } + + public IDeviceMessageBuilder AddReading(short timeOffset, byte sid, T value) + where T : notnull + { + var type = _valueTypeMap.TryGetValue(typeof(T), out var t) + ? t + : (value is byte[]? StateValueTypeEnum.Binary : StateValueTypeEnum.String); + + AddReading(timeOffset, (sid, value, type)); + return this; + } + + public IDeviceMessageBuilder AddReading(short timeOffset, byte sid, string value) => + AddReading(timeOffset, (sid, value, StateValueTypeEnum.String)); + + public IDeviceMessageBuilder AddReading(short timeOffset, byte sid, byte[] value) => + AddReading(timeOffset, (sid, value, StateValueTypeEnum.Binary)); + + public IDeviceMessageBuilder AddReading(short timeOffset, params (byte sid, object? value, StateValueTypeEnum? type)[] states) + { + if (_currentDeviceBuilder == null) + throw new InvalidOperationException("No active device configuration"); + + _currentDeviceBuilder.AddReading(timeOffset, reading => + { + foreach (var (sid, value, type) in states) + { + reading.AddState(sid, value, type ?? InferType(value)); // 如果调用者没给 type,就推断 + } + }); + + return this; + } + + public DeviceMessage Build() + { + if (_message.MainDevice == null) + throw new InvalidOperationException("Main device is required"); + + return _message; + } + + public byte[] BuildBytes() + { + var arrayBufferWriter = new ArrayBufferWriter(); + DeviceMessageSerializerProvider.MessageSer.Serializer(arrayBufferWriter, Build()); + return arrayBufferWriter.WrittenSpan.ToArray(); + } + + public string BuildHex(bool compress = false, bool encrypt = false) + { + var arrayBufferWriter = new ArrayBufferWriter(); + return DeviceMessageSerializerProvider.MessageSer.Serializer(arrayBufferWriter, Build(), encrypt, compress); + } + + public Task BuildBytesAsync() => Task.Run(() => BuildBytes()); + + public Task BuildHexAsync(bool compress = false, bool encrypt = false) => + Task.Run(() => BuildHex(compress, encrypt)); + + public IDeviceMessageBuilder WithEncryptFunc(Func encryptFunc) + { + DeviceMessageSerializerProvider.MessageSer.EncryptFunc = encryptFunc; + return this; + } + + public IDeviceMessageBuilder WithDecryptFunc(Func decryptFunc) + { + DeviceMessageSerializerProvider.MessagePar.DecryptFunc = decryptFunc; + return this; + } + + private static StateValueTypeEnum InferType(object value) => value switch + { + float => StateValueTypeEnum.Float32, + double => StateValueTypeEnum.Double, + int => StateValueTypeEnum.Int32, + short => StateValueTypeEnum.Int16, + ushort => StateValueTypeEnum.UInt16, + bool => StateValueTypeEnum.Bool, + ulong => StateValueTypeEnum.Timestamp, + byte[] => StateValueTypeEnum.Binary, + _ => StateValueTypeEnum.String + }; + + private static readonly FrozenDictionary _valueTypeMap = + new Dictionary + { + [typeof(float)] = StateValueTypeEnum.Float32, + [typeof(int)] = StateValueTypeEnum.Int32, + [typeof(short)] = StateValueTypeEnum.Int16, + [typeof(ushort)] = StateValueTypeEnum.UInt16, + [typeof(bool)] = StateValueTypeEnum.Bool, + [typeof(double)] = StateValueTypeEnum.Double, + [typeof(ulong)] = StateValueTypeEnum.Timestamp, + }.ToFrozenDictionary(); + } +} diff --git a/DeviceCommons/DeviceMessages/Builders/IDeviceMessageBuilder.cs b/DeviceCommons/DeviceMessages/Builders/IDeviceMessageBuilder.cs new file mode 100644 index 0000000..0a8fee1 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Builders/IDeviceMessageBuilder.cs @@ -0,0 +1,51 @@ +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Builders +{ + public interface IDeviceMessageBuilder + { + IDeviceMessageBuilder WithHeader( + byte version = 0x01, + CRCTypeEnum crcType = CRCTypeEnum.CRC16, + TimeStampFormatEnum timeFormat = TimeStampFormatEnum.MS, + HeaderValueTypeEnum valueType = HeaderValueTypeEnum.Standard); + + IDeviceMessageBuilder WithMainDevice(string did, byte deviceType); + + IDeviceMessageBuilder WithMainDevice(string did, byte deviceType, Action config); + + IDeviceMessageBuilder WithChildDevice(string did, byte deviceType, Action config); + + IDeviceMessageBuilder WithEncryptFunc(Func encryptFunc); + + IDeviceMessageBuilder WithDecryptFunc(Func decryptFunc); + + IDeviceMessageBuilder WithAesEncryption(string password); + + IDeviceMessageBuilder WithVersion(byte version); + + + IDeviceMessageBuilder AddReading(short timeOffset, byte sid, string value); + + IDeviceMessageBuilder AddReading(short timeOffset, byte sid, byte[] value); + + IDeviceMessageBuilder AddReading(short timeOffset, byte sid, T value); + + IDeviceMessageBuilder AddReading(short timeOffset, Action config); + + IDeviceMessageBuilder AddReading(short timeOffset, params (byte sid, object value, StateValueTypeEnum? type)[] states); + + + DeviceMessage Build(); + + byte[] BuildBytes(); + + string BuildHex(bool compress = false, bool encrypt = false); + + // 添加异步方法 + Task BuildBytesAsync(); + + Task BuildHexAsync(bool compress = false, bool encrypt = false); + } +} diff --git a/DeviceCommons/DeviceMessages/Enums/CRCTypeEnum.cs b/DeviceCommons/DeviceMessages/Enums/CRCTypeEnum.cs new file mode 100644 index 0000000..39817ee --- /dev/null +++ b/DeviceCommons/DeviceMessages/Enums/CRCTypeEnum.cs @@ -0,0 +1,10 @@ +namespace DeviceCommons.DeviceMessages.Enums +{ + public enum CRCTypeEnum : byte + { + None = 0, + CRC8 = 1, + CRC16 = 2, + CRC32 = 3 + } +} diff --git a/DeviceCommons/DeviceMessages/Enums/HeaderValueTypeEnum.cs b/DeviceCommons/DeviceMessages/Enums/HeaderValueTypeEnum.cs new file mode 100644 index 0000000..11cff64 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Enums/HeaderValueTypeEnum.cs @@ -0,0 +1,8 @@ +namespace DeviceCommons.DeviceMessages.Enums +{ + public enum HeaderValueTypeEnum : byte + { + Standard = 0, + Extend = 1, + } +} diff --git a/DeviceCommons/DeviceMessages/Enums/MarkEnum.cs b/DeviceCommons/DeviceMessages/Enums/MarkEnum.cs new file mode 100644 index 0000000..398b793 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Enums/MarkEnum.cs @@ -0,0 +1,25 @@ +namespace DeviceCommons.DeviceMessages.Enums +{ + [Flags] + public enum MarkEnum : byte + { + TimeStampMS = 0 << 0, + TimeStampS = 1 << 0, + + ValueStandard = 0 << 1, + ValueExtend = 1 << 1, + + Reserve2Close = 0 << 2, + Reserve2Open = 1 << 2, + + Reserve3Close = 0 << 3, + Reserve3Open = 1 << 3, + + CRCMask = 0b1111_0000, + CRCNone = 0 << 4, + CRC8 = 1 << 4, + CRC16 = 2 << 4, + CRC32 = 3 << 4, + CRC64 = 4 << 4, + } +} diff --git a/DeviceCommons/DeviceMessages/Enums/Reserve1Enum.cs b/DeviceCommons/DeviceMessages/Enums/Reserve1Enum.cs new file mode 100644 index 0000000..f76f067 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Enums/Reserve1Enum.cs @@ -0,0 +1,8 @@ +namespace DeviceCommons.DeviceMessages.Enums +{ + public enum Reserve1Enum : byte + { + Close = 0, + Open = 1 + } +} diff --git a/DeviceCommons/DeviceMessages/Enums/Reserve2Enum.cs b/DeviceCommons/DeviceMessages/Enums/Reserve2Enum.cs new file mode 100644 index 0000000..410e30d --- /dev/null +++ b/DeviceCommons/DeviceMessages/Enums/Reserve2Enum.cs @@ -0,0 +1,8 @@ +namespace DeviceCommons.DeviceMessages.Enums +{ + public enum Reserve2Enum : byte + { + Close = 0, + Open = 1 + } +} diff --git a/DeviceCommons/DeviceMessages/Enums/StateValueTypeEnum.cs b/DeviceCommons/DeviceMessages/Enums/StateValueTypeEnum.cs new file mode 100644 index 0000000..8ea17d2 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Enums/StateValueTypeEnum.cs @@ -0,0 +1,15 @@ +namespace DeviceCommons.DeviceMessages.Enums +{ + public enum StateValueTypeEnum : byte + { + Float32 = 1, + Int32 = 2, + String = 3, + Bool = 4, + UInt16 = 6, + Int16 = 7, + Timestamp = 8, + Binary = 9, + Double = 10, + } +} diff --git a/DeviceCommons/DeviceMessages/Enums/TimeStampFormatEnum.cs b/DeviceCommons/DeviceMessages/Enums/TimeStampFormatEnum.cs new file mode 100644 index 0000000..9ea5584 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Enums/TimeStampFormatEnum.cs @@ -0,0 +1,8 @@ +namespace DeviceCommons.DeviceMessages.Enums +{ + public enum TimeStampFormatEnum : byte + { + MS = 0, + S = 1, + } +} diff --git a/DeviceCommons/DeviceMessages/Factories/DefaultStateFactory.cs b/DeviceCommons/DeviceMessages/Factories/DefaultStateFactory.cs new file mode 100644 index 0000000..b9a66cd --- /dev/null +++ b/DeviceCommons/DeviceMessages/Factories/DefaultStateFactory.cs @@ -0,0 +1,18 @@ +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Factories +{ + public class DefaultStateFactory : IStateFactory + { + public IDeviceMessageInfoReadingState CreateState(byte sid, object value, StateValueTypeEnum? valueType = null) + { + return new DeviceMessageInfoReadingState + { + SID = sid, + ValueType = valueType ?? StateValueTypeEnum.String, + ValueText = value + }; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Factories/IStateFactory.cs b/DeviceCommons/DeviceMessages/Factories/IStateFactory.cs new file mode 100644 index 0000000..54bcd0e --- /dev/null +++ b/DeviceCommons/DeviceMessages/Factories/IStateFactory.cs @@ -0,0 +1,10 @@ +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Factories +{ + public interface IStateFactory + { + IDeviceMessageInfoReadingState CreateState(byte sid, object value, StateValueTypeEnum? valueType = null); + } +} diff --git a/DeviceCommons/DeviceMessages/Factories/StateFactoryRegistry.cs b/DeviceCommons/DeviceMessages/Factories/StateFactoryRegistry.cs new file mode 100644 index 0000000..fd5a3de --- /dev/null +++ b/DeviceCommons/DeviceMessages/Factories/StateFactoryRegistry.cs @@ -0,0 +1,31 @@ +using System.Collections.Concurrent; + +namespace DeviceCommons.DeviceMessages.Factories +{ + public static class StateFactoryRegistry + { + private static readonly ConcurrentDictionary> _factoryCreators = new(); + private static readonly ConcurrentDictionary _factoryCache = new(); + private static readonly IStateFactory _defaultFactory = new DefaultStateFactory(); + + public static void RegisterFactory(byte deviceType, Func factoryCreator) + { + _factoryCreators[deviceType] = factoryCreator; + } + + public static IStateFactory GetFactory(byte deviceType) + { + if (_factoryCache.TryGetValue(deviceType, out var cachedFactory)) + return cachedFactory; + + if (_factoryCreators.TryGetValue(deviceType, out var creator)) + { + var factory = creator(); + _factoryCache[deviceType] = factory; + return factory; + } + + return _defaultFactory; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessage.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessage.cs new file mode 100644 index 0000000..31f337a --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessage.cs @@ -0,0 +1,47 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessage : IDeviceMessage + { + public IDeviceMessageHeader? Header { get; set; } = new DeviceMessageHeader(); + + public IDeviceMessageInfo? MainDevice { get; set; } = new DeviceMessageInfo(); + + public IDeviceMessageChild? ChildDevice { get; set; } = new DeviceMessageChild(); + + public void Dispose() + { + DisposeMainDevice(); + DisposeChildDevices(); + GC.SuppressFinalize(this); + } + + private void DisposeMainDevice() + { + if (MainDevice?.Reading?.ReadingArray == null) return; + foreach (var reading in MainDevice.Reading.ReadingArray) DisposeReadingReadings(MainDevice); + } + + private void DisposeChildDevices() + { + if (ChildDevice?.ChildArray == null) return; + foreach (var child in ChildDevice.ChildArray) DisposeReadingReadings(child); + } + private static void DisposeReadingReadings(IDeviceMessageInfo? info) + { + if (info?.Reading?.ReadingArray == null) return; + foreach (var reading in info.Reading.ReadingArray) DisposeReadingStates(reading); + info?.Reading?.Dispose(); + } + + private static void DisposeReadingStates(IDeviceMessageInfoReading? reading) + { + if (reading?.State?.StateArray == null) return; + foreach (var state in reading.State.StateArray) state?.Dispose(); + } + + ~DeviceMessage() + { + Dispose(); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageChild.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageChild.cs new file mode 100644 index 0000000..7761cb3 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageChild.cs @@ -0,0 +1,44 @@ +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessageChild : IDeviceMessageChild + { + private readonly Dictionary _children = []; + + public DeviceMessageChild() + { + } + + public byte Count => (byte)_children.Count; + + public IDeviceMessageInfo[]? ChildArray + { + get => [.. _children.Values.OrderBy(i => i.DID)]; + set + { + _children.Clear(); + if (value != null) + { + foreach (var dev in value) + { + if (dev != null) + { + if (!string.IsNullOrEmpty(dev.DID)) + _children[dev?.DID ?? ""] = dev ?? new DeviceMessageInfo(); + } + else throw new ArgumentNullException("初始化后再赋值"); + } + } + } + } + + public void AddOrUpdateChild(IDeviceMessageInfo child) + { + if (string.IsNullOrEmpty(child.DID)) + throw new ArgumentException("DID不能为空"); + + _children[child?.DID ?? ""] = child ?? new DeviceMessageInfo(); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageHeader.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageHeader.cs new file mode 100644 index 0000000..1b8d738 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageHeader.cs @@ -0,0 +1,30 @@ +using DeviceCommons.DeviceMessages.Enums; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessageHeader : IDeviceMessageHeader + { + public DeviceMessageHeader() + { + } + + public byte[] Header { get; set; } = new byte[] { 0xC0, 0xBF }; + + public byte Version { get; set; } = 0x01; + + public TimeStampFormatEnum TimeStampFormat { get; set; } = TimeStampFormatEnum.MS; + + public HeaderValueTypeEnum ValueType { get; set; } = HeaderValueTypeEnum.Standard; + + public Reserve1Enum Reserve1 { get; set; } = Reserve1Enum.Close; + + public Reserve2Enum Reserve2 { get; set; } = Reserve2Enum.Close; + + public CRCTypeEnum CRCType { get; set; } = CRCTypeEnum.CRC16; + + public byte Mark + { + get => (byte)((int)CRCType << 4 | (int)Reserve2 << 3 | (int)Reserve1 << 2 | (int)ValueType << 1 | (int)TimeStampFormat); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfo.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfo.cs new file mode 100644 index 0000000..dc57a73 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfo.cs @@ -0,0 +1,21 @@ +using System.Text; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessageInfo : IDeviceMessageInfo + { + public DeviceMessageInfo() + { + } + + public byte Length { get => (byte)(DIDBytes?.Length ?? 0); } + + public byte[]? DIDBytes { get => Encoding.UTF8.GetBytes(DID ?? ""); set => DID = Encoding.UTF8.GetString(value ?? []); } + + public string? DID { get; set; } + + public byte DeviceType { get; set; } = 0x00; + + public IDeviceMessageInfoReadings? Reading { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReading.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReading.cs new file mode 100644 index 0000000..2aab269 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReading.cs @@ -0,0 +1,25 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessageInfoReading : IDeviceMessageInfoReading + { + public DeviceMessageInfoReading() + { + } + + public byte[] Offset + { + get + { + byte[] bytes = new byte[2]; + bytes[0] = (byte)(TimeOffset >> 8); + bytes[1] = (byte)TimeOffset; + return bytes; + } + set => TimeOffset = (short)(value[0] << 8 | value[1]); + } + + public short TimeOffset { get; set; } = 0; + + public IDeviceMessageInfoReadingStates? State { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingState.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingState.cs new file mode 100644 index 0000000..7938c0b --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingState.cs @@ -0,0 +1,80 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Enums; +using System.Text; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessageInfoReadingState : IDeviceMessageInfoReadingState + { + public byte SID { get; set; } + public byte Type + { + get => (byte)ValueType; + set + { + ValueType = (StateValueTypeEnum)value; + if (ValueType != StateValueTypeEnum.String && + ValueType != StateValueTypeEnum.Binary) + { + Value = new byte[DeviceMessageUtilities.GetValueLength(ValueType,null)]; + } + else + { + Value = []; + } + } + } + + public byte[] Value { get; set; } = Array.Empty(); + + public object? ValueText + { + get + { + return (StateValueTypeEnum)Type switch + { + StateValueTypeEnum.Float32 => BitConverter.ToSingle(Value, 0), + StateValueTypeEnum.Int32 => BitConverter.ToInt32(Value, 0), + StateValueTypeEnum.Bool => BitConverter.ToBoolean(Value, 0), + StateValueTypeEnum.UInt16 => BitConverter.ToUInt16(Value, 0), + StateValueTypeEnum.Int16 => BitConverter.ToInt16(Value, 0), + StateValueTypeEnum.Timestamp => BitConverter.ToUInt64(Value, 0), + StateValueTypeEnum.Double => BitConverter.ToDouble(Value, 0), + StateValueTypeEnum.Binary => Encoding.UTF8.GetString(Value), + StateValueTypeEnum.String => Encoding.UTF8.GetString(Value), + _ => null, + }; + } + set + { + Value = (StateValueTypeEnum)Type switch + { + StateValueTypeEnum.Float32 => BitConverter.GetBytes(float.Parse(value?.ToString() ?? "0")), + StateValueTypeEnum.Int32 => BitConverter.GetBytes(int.Parse(value?.ToString() ?? "0")), + StateValueTypeEnum.Bool => BitConverter.GetBytes(bool.Parse(value?.ToString() ?? "0")), + StateValueTypeEnum.UInt16 => BitConverter.GetBytes(ushort.Parse(value?.ToString() ?? "0")), + StateValueTypeEnum.Int16 => BitConverter.GetBytes(short.Parse(value?.ToString() ?? "0")), + StateValueTypeEnum.Timestamp => BitConverter.GetBytes(ulong.Parse(value?.ToString() ?? "0")), + StateValueTypeEnum.Double => BitConverter.GetBytes(double.Parse(value?.ToString() ?? "0")), + StateValueTypeEnum.Binary => Encoding.UTF8.GetBytes(value?.ToString() ?? ""), + StateValueTypeEnum.String => Encoding.UTF8.GetBytes(value?.ToString() ?? ""), + _ => [], + }; + } + } + public StateValueTypeEnum ValueType { get; set; } = StateValueTypeEnum.String; + + public string? Metadata { get; set; } + + public void Dispose() + { + if (Value != null) + { + Array.Clear(Value, 0, Value.Length); + DeviceMessageArrayPool.ByteArrayPool.Return(Value); + Value = []; + } + GC.SuppressFinalize(this); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingStates.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingStates.cs new file mode 100644 index 0000000..3843f74 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadingStates.cs @@ -0,0 +1,25 @@ +using DeviceCommons.DataHandling; +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessageInfoReadingStates : IDeviceMessageInfoReadingStates + { + + public byte Count => (byte)(StateArray?.Length ?? 0); + + public IDeviceMessageInfoReadingState[]? StateArray { get; set; } + public void Dispose() + { + if (StateArray != null) + { + foreach (var state in StateArray) + { + state?.Dispose(); + } + Array.Clear(StateArray, 0, StateArray.Length); + DeviceMessageArrayPool.InfoReadingState_ArrayPool.Return(StateArray); + StateArray = []; + } + GC.SuppressFinalize(this); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadings.cs b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadings.cs new file mode 100644 index 0000000..192c10c --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/DeviceMessageInfoReadings.cs @@ -0,0 +1,20 @@ +using DeviceCommons.DataHandling; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public class DeviceMessageInfoReadings : IDeviceMessageInfoReadings + { + public byte Count { get => (byte)(ReadingArray?.Length ?? 0); } + public IDeviceMessageInfoReading[]? ReadingArray { get; set; } + public void Dispose() + { + if (ReadingArray != null) + { + Array.Clear(ReadingArray, 0, ReadingArray.Length); + DeviceMessageArrayPool.InfoReading_ArrayPool.Return(ReadingArray); + ReadingArray = []; + } + GC.SuppressFinalize(this); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IBase.cs b/DeviceCommons/DeviceMessages/Models/V1/IBase.cs new file mode 100644 index 0000000..4594aab --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IBase.cs @@ -0,0 +1,8 @@ +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IBase + { + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessage.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessage.cs new file mode 100644 index 0000000..7a41925 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessage.cs @@ -0,0 +1,12 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessage : IBase, IDisposable + { + + IDeviceMessageHeader? Header { get; set; } + + IDeviceMessageInfo? MainDevice { get; set; } + + IDeviceMessageChild? ChildDevice { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageChild.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageChild.cs new file mode 100644 index 0000000..cf8ca9c --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageChild.cs @@ -0,0 +1,9 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessageChild : IBase + { + byte Count { get; } + IDeviceMessageInfo[]? ChildArray { get; set; } + void AddOrUpdateChild(IDeviceMessageInfo child); + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageHeader.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageHeader.cs new file mode 100644 index 0000000..3ef83e3 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageHeader.cs @@ -0,0 +1,21 @@ +using DeviceCommons.DeviceMessages.Enums; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessageHeader : IBase + { + byte[] Header { get; set; } + + byte Version { get; set; } + byte Mark { get; } + TimeStampFormatEnum TimeStampFormat { get; set; } + + HeaderValueTypeEnum ValueType { get; set; } + + Reserve1Enum Reserve1 { get; set; } + + Reserve2Enum Reserve2 { get; set; } + + CRCTypeEnum CRCType { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfo.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfo.cs new file mode 100644 index 0000000..d3b6946 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfo.cs @@ -0,0 +1,16 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessageInfo : IBase + { + + byte Length { get; } + + byte[]? DIDBytes { get; set; } + + string? DID { get; set; } + + byte DeviceType { get; set; } + + IDeviceMessageInfoReadings? Reading { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReading.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReading.cs new file mode 100644 index 0000000..0590c01 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReading.cs @@ -0,0 +1,11 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessageInfoReading : IBase + { + byte[] Offset { get; set; } + + short TimeOffset { get; set; } + + IDeviceMessageInfoReadingStates? State { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingState.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingState.cs new file mode 100644 index 0000000..68fae03 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingState.cs @@ -0,0 +1,14 @@ +using DeviceCommons.DeviceMessages.Enums; + +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessageInfoReadingState : IBase, IDisposable + { + byte SID { get; set; } + byte[] Value { get; set; } + object? ValueText { get; set; } + byte Type { get; set; } + StateValueTypeEnum ValueType { get; set; } + string? Metadata { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingStates.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingStates.cs new file mode 100644 index 0000000..77a3a73 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadingStates.cs @@ -0,0 +1,8 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessageInfoReadingStates : IBase, IDisposable + { + byte Count { get; } + IDeviceMessageInfoReadingState[]? StateArray { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadings.cs b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadings.cs new file mode 100644 index 0000000..818ac5d --- /dev/null +++ b/DeviceCommons/DeviceMessages/Models/V1/IDeviceMessageInfoReadings.cs @@ -0,0 +1,9 @@ +namespace DeviceCommons.DeviceMessages.Models.V1 +{ + public interface IDeviceMessageInfoReadings : IBase, IDisposable + { + public byte Count { get; } + + public IDeviceMessageInfoReading[]? ReadingArray { get; set; } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/DeviceMessageParser.cs b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageParser.cs new file mode 100644 index 0000000..a9887c8 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageParser.cs @@ -0,0 +1,43 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using DeviceCommons.Exceptions; + +namespace DeviceCommons.DeviceMessages.Serialization +{ + public class DeviceMessageParser : AbstractMessageParser, IDeviceMessageParser + { + public DeviceMessageParser() : base(new DeviceMessage()) + { + } + + public override IDeviceMessage Parser(ReadOnlySpan bytes) + { + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + if (bytes.Length < 4) throw new ArgumentException($"最小长度4字节,实际收到{bytes.Length}字节"); + try + { + + byte[] headerBytes = new byte[4]; + headerBytes = bytes[..4].ToArray(); + + Model.Header = DeviceMessageSerializerProvider.HeaderPar.Parser(headerBytes); + if (Model.Header == null) throw new InvalidOperationException("未初始化消息头"); + switch (Model.Header.Version) + { + case 0x01: + _ = new V1.ProcessVersionData(Model, bytes); + break; + default: + _ = new V1.ProcessVersionData(Model, bytes); + break; + } + } + catch (Exception ex) + { + throw new InvalidMessageException("消息解析失败", ex); + } + return Model; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/DeviceMessageSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageSerializer.cs new file mode 100644 index 0000000..a237015 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/DeviceMessageSerializer.cs @@ -0,0 +1,35 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; +using DeviceCommons.DeviceMessages.Serialization.V1.Serializers; +using DeviceCommons.Security; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization +{ + public class DeviceMessageSerializer : AbstractMessageSerializer, IDeviceMessageSerializer + { + public DeviceMessageSerializer() : base(new DeviceMessage()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + public override void Serializer(ArrayBufferWriter writer, IDeviceMessage model) + { + _ = new DeviceMessageSerializer(); + + new DeviceMessageChildSerializer().Serializer(writer, model.ChildDevice ?? new DeviceMessageChild()); + new DeviceMessageInfoSerializer().Serializer(writer, model.MainDevice ?? new DeviceMessageInfo()); + new DeviceMessageHeaderSerializer().Serializer(writer, model.Header ?? new DeviceMessageHeader()); + + byte[] bytes = writer.WrittenSpan.ToArray(); + + if (model.Header?.CRCType != CRCTypeEnum.None) + { + writer.Write(CrcCalculator.CalculateCrcBytes(model.Header.CRCType, writer.WrittenSpan.ToArray())); + } + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageParser.cs b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageParser.cs new file mode 100644 index 0000000..a99bae9 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageParser.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization +{ + public interface IDeviceMessageParser : IMessageParser + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageSerializer.cs new file mode 100644 index 0000000..cc9e128 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/IDeviceMessageSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization +{ + public interface IDeviceMessageSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageChildParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageChildParser.cs new file mode 100644 index 0000000..bc88c1a --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageChildParser.cs @@ -0,0 +1,93 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Text; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public class DeviceMessageChildParser : AbstractMessageParser, IDeviceMessageChildParser + { + public DeviceMessageChildParser() : base(new DeviceMessageChild()) + { + } + + public override IDeviceMessageChild Parser(ReadOnlySpan bytes) + { + IDeviceMessageChild model = new DeviceMessageChild(); + byte childCount = bytes[0]; + List childList = new List(); + int currentIndex = 1; + + for (int i = 0; i < childCount; i++) + { + byte didLength = bytes[currentIndex]; + currentIndex++; + + string deviceId = ""; + if (didLength > 0) + { + deviceId = Encoding.UTF8.GetString(bytes.ToArray(), currentIndex, didLength); + currentIndex += didLength; + } + + byte deviceType = bytes[currentIndex]; + currentIndex++; + + int readingsDataLength = CalculateReadingsDataLength(bytes, currentIndex); + + if (readingsDataLength > 0) + { + var childDevice = new DeviceMessageInfo + { + DID = deviceId, + DeviceType = deviceType, + Reading = DeviceMessageSerializerProvider.InfoReadingsPar.Parser(bytes.Slice(currentIndex, readingsDataLength)) + }; + + childList.Add(childDevice); + + currentIndex += readingsDataLength; + } + else + { + childList.Add(new DeviceMessageInfo + { + DID = deviceId, + DeviceType = deviceType, + Reading = new DeviceMessageInfoReadings() + }); + } + } + + model.ChildArray = childList.ToArray(); + return model; + } + + public static int CalculateReadingsDataLength(ReadOnlySpan bytes, int index) + { + int startIndex = index; + if (startIndex >= bytes.Length) return 0; + + byte readingCount = bytes[index]; + index++; + + for (int i = 0; i < readingCount; i++) + { + index += 2; + + byte stateCount = bytes[index]; + index++; + + for (int j = 0; j < stateCount; j++) + { + _ = bytes[index + 1]; + index += 2; + + int valueLength = DeviceMessageUtilities.GetValueLength(bytes, index - 1); + index += valueLength; + } + } + return index - startIndex; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageHeaderParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageHeaderParser.cs new file mode 100644 index 0000000..db8b64b --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageHeaderParser.cs @@ -0,0 +1,30 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public class DeviceMessageHeaderParser : AbstractMessageParser, IDeviceMessageHeaderParser + { + public DeviceMessageHeaderParser() : base(new DeviceMessageHeader()) + { + + } + + public override IDeviceMessageHeader Parser(ReadOnlySpan bytes) + { + IDeviceMessageHeader model = new DeviceMessageHeader(); + + model.Header = bytes[2..].ToArray(); + + model.Version = bytes[2]; + model.TimeStampFormat = (TimeStampFormatEnum)(bytes[3] >> 0 & 0x01); + model.ValueType = (HeaderValueTypeEnum)(bytes[3] >> 1 & 0x01); + model.Reserve1 = (Reserve1Enum)(bytes[3] >> 2 & 0x01); + model.Reserve2 = (Reserve2Enum)(bytes[3] >> 3 & 0x01); + model.CRCType = (CRCTypeEnum)(bytes[3] >> 4 & 0x07); + + return model; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoParser.cs new file mode 100644 index 0000000..e4f8228 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoParser.cs @@ -0,0 +1,42 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public class DeviceMessageInfoParser : AbstractMessageParser, IDeviceMessageInfoParser + { + public DeviceMessageInfoParser() : base(new DeviceMessageInfo()) + { + + } + + public override IDeviceMessageInfo Parser(ReadOnlySpan bytes) + { + IDeviceMessageInfo model = new DeviceMessageInfo(); + + byte didLength = bytes[0]; + int currentIndex = 1; + + + if (didLength > 0) + { + model.DIDBytes = bytes.Slice(currentIndex, didLength).ToArray(); + currentIndex += didLength; + } + else model.DIDBytes = []; + + model.DeviceType = bytes[currentIndex]; + currentIndex++; + + + int readingsDataLength = bytes.Length - currentIndex; + if (readingsDataLength > 0) + { + model.Reading ??= DeviceMessageSerializerProvider.InfoReadingsPar.Parser(bytes.Slice(currentIndex, readingsDataLength)); + } + else model.Reading = new DeviceMessageInfoReadings(); + return model; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingParser.cs new file mode 100644 index 0000000..049dd77 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingParser.cs @@ -0,0 +1,27 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public class DeviceMessageInfoReadingParser : AbstractMessageParser, IDeviceMessageInfoReadingParser + { + public DeviceMessageInfoReadingParser() : base(new DeviceMessageInfoReading()) + { + } + + public override IDeviceMessageInfoReading Parser(ReadOnlySpan bytes) + { + if (bytes.Length < 2) + throw new ArgumentException("字节长度不足,至少需要2字节", nameof(bytes)); + + IDeviceMessageInfoReading model = new DeviceMessageInfoReading(); + + model.Offset = bytes[..2].ToArray(); + + model.State = DeviceMessageSerializerProvider.InfoReadingStatesPar.Parser(bytes[2..]); + + return model; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStateParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStateParser.cs new file mode 100644 index 0000000..e9654c7 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStateParser.cs @@ -0,0 +1,48 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public class DeviceMessageInfoReadingStateParser : AbstractMessageParser, IDeviceMessageInfoReadingStateParser + { + public DeviceMessageInfoReadingStateParser() : base(new DeviceMessageInfoReadingState()) + { + } + + public override IDeviceMessageInfoReadingState Parser(ReadOnlySpan bytes) + { + if (bytes.Length < 2) + throw new ArgumentException("Insufficient data for state parsing"); + + byte sid = bytes[0]; + StateValueTypeEnum valueType = (StateValueTypeEnum)bytes[1]; + int dataStartIndex = 2; + int valueLength; + + if (valueType == StateValueTypeEnum.String || valueType == StateValueTypeEnum.Binary) + { + int lengthFieldSize = valueType == StateValueTypeEnum.String ? 1 : 2; + if (bytes.Length < dataStartIndex + lengthFieldSize) + throw new ArgumentException("Insufficient data for length field"); + + valueLength = DeviceMessageUtilities.GetValueLength(valueType, bytes[dataStartIndex..]); + dataStartIndex += lengthFieldSize; + } + else valueLength = DeviceMessageUtilities.GetValueLength(valueType, null); + + if (bytes.Length < dataStartIndex + valueLength) + throw new ArgumentException("Insufficient data for value"); + + var model = new DeviceMessageInfoReadingState + { + SID = sid, + Type = (byte)valueType, + Value = bytes.Slice(dataStartIndex, valueLength).ToArray() + }; + + return model; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStatesParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStatesParser.cs new file mode 100644 index 0000000..261c81a --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingStatesParser.cs @@ -0,0 +1,49 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public class DeviceMessageInfoReadingStatesParser : AbstractMessageParser, IDeviceMessageInfoReadingStatesParser + { + public DeviceMessageInfoReadingStatesParser() : base(new DeviceMessageInfoReadingStates()) + { + } + + public override IDeviceMessageInfoReadingStates Parser(ReadOnlySpan bytes) + { + if (bytes == null || bytes.Length == 0) + throw new ArgumentNullException(nameof(bytes)); + + IDeviceMessageInfoReadingStates model = new DeviceMessageInfoReadingStates(); + + byte stateCount = bytes[0]; + + int currentIndex = 1; + + model.StateArray = DeviceMessageArrayPool.InfoReadingState_ArrayPool.Rent(stateCount); + + for (var i = 0; i < stateCount; i++) + { + byte sid = bytes[currentIndex]; + + byte type = bytes[currentIndex + 1]; + + int valueLength = 2 + DeviceMessageUtilities.GetValueLength(bytes, currentIndex + 1); + + byte[] stateData = bytes.Slice(currentIndex, valueLength).ToArray(); + + model.StateArray[i] = DeviceMessageSerializerProvider.InfoReadingStatePar.Parser(stateData); + + currentIndex += valueLength; + + if (currentIndex > bytes.Length) + throw new ArgumentException("字节数组不包含完整的状态数据"); + } + + model.StateArray = model.StateArray.AsSpan()[..stateCount].ToArray(); + + return model; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingsParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingsParser.cs new file mode 100644 index 0000000..1f96126 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/DeviceMessageInfoReadingsParser.cs @@ -0,0 +1,42 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public class DeviceMessageInfoReadingsParser : AbstractMessageParser, IDeviceMessageInfoReadingsParser + { + public DeviceMessageInfoReadingsParser() : base(new DeviceMessageInfoReadings()) + { + } + public override IDeviceMessageInfoReadings Parser(ReadOnlySpan bytes) + { + IDeviceMessageInfoReadings model = new DeviceMessageInfoReadings(); + byte readingCount = bytes[0]; + model.ReadingArray = DeviceMessageArrayPool.InfoReading_ArrayPool.Rent(readingCount); + int currentIndex = 1; + for (int i = 0; i < readingCount; i++) + { + + byte stateCount = bytes[currentIndex + 2]; + int stateDataLength = 1; + int stateIndex = currentIndex + 3; + for (int j = 0; j < stateCount; j++) + { + byte type = bytes[stateIndex + 1]; + int valueLength = DeviceMessageUtilities.GetValueLength(bytes, stateIndex + 1); + stateDataLength += 2 + valueLength; + stateIndex += 2 + valueLength; + } + int totalReadingLength = 2 + stateDataLength; + model.ReadingArray[i] = + DeviceMessageSerializerProvider.InfoReadingPar.Parser( + bytes.Slice(currentIndex, totalReadingLength + )); + currentIndex += totalReadingLength; + } + model.ReadingArray = model.ReadingArray.AsSpan().Slice(0, readingCount).ToArray(); + return model; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageChildParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageChildParser.cs new file mode 100644 index 0000000..ff536fa --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageChildParser.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public interface IDeviceMessageChildParser : IMessageParser + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageHeaderParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageHeaderParser.cs new file mode 100644 index 0000000..1bf0d6e --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageHeaderParser.cs @@ -0,0 +1,10 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public interface IDeviceMessageHeaderParser : IMessageParser + { + + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoParser.cs new file mode 100644 index 0000000..b0715df --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoParser.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public interface IDeviceMessageInfoParser : IMessageParser + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingParser.cs new file mode 100644 index 0000000..63355ad --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingParser.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public interface IDeviceMessageInfoReadingParser : IMessageParser + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStateParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStateParser.cs new file mode 100644 index 0000000..90b3373 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStateParser.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public interface IDeviceMessageInfoReadingStateParser : IMessageParser + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStatesParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStatesParser.cs new file mode 100644 index 0000000..745fe5f --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingStatesParser.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public interface IDeviceMessageInfoReadingStatesParser : IMessageParser + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingsParser.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingsParser.cs new file mode 100644 index 0000000..f0c1d1a --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Parsers/IDeviceMessageInfoReadingsParser.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Parsers +{ + public interface IDeviceMessageInfoReadingsParser : IMessageParser + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/ProcessVersionData.cs b/DeviceCommons/DeviceMessages/Serialization/V1/ProcessVersionData.cs new file mode 100644 index 0000000..bc46bfb --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/ProcessVersionData.cs @@ -0,0 +1,86 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; +using DeviceCommons.Exceptions; +using DeviceCommons.Security; + +namespace DeviceCommons.DeviceMessages.Serialization.V1 +{ + public class ProcessVersionData + { + public ProcessVersionData(IDeviceMessage model, ReadOnlySpan bytes) + { + if (model == null) throw new ArgumentNullException("model"); + if (model.Header == null) throw new ArgumentNullException("model.Header"); + + int crcLength = CrcCalculator.GetCrcLength(model.Header?.CRCType ?? CRCTypeEnum.CRC16); + + if (crcLength > 0) + { + + byte[] receivedCrc = bytes.Slice(bytes.Length - crcLength).ToArray(); + byte[] dataForCrc = bytes[..(bytes.Length - crcLength)].ToArray(); + + byte[] calculatedCrc = CrcCalculator.CalculateCrcBytes(model.Header.CRCType, dataForCrc); + + + if (!receivedCrc.SequenceEqual(calculatedCrc)) + { + string received = BitConverter.ToString(receivedCrc); + string calculated = BitConverter.ToString(calculatedCrc); + throw new InvalidMessageException($"CRC validation failed. Expected 0x{calculated}, but received 0x{received}."); + } + } + + int index = 4; + int mainDeviceLength = CalculateMainDeviceDataLength(bytes, index); + if (mainDeviceLength > 0) + { + byte[] mainDeviceData = bytes.Slice(index, mainDeviceLength).ToArray(); + model.MainDevice = DeviceMessageSerializerProvider.InfoPar.Parser(mainDeviceData); + index += mainDeviceLength; + } + + int childDeviceLength = bytes.Length - index - crcLength; + if (childDeviceLength < 0) throw new ArgumentException("Invalid data length for child devices"); + + if (childDeviceLength > 0) + { + byte[] childDeviceData = bytes.Slice(index, childDeviceLength).ToArray(); + model.ChildDevice = DeviceMessageSerializerProvider.ChildPar.Parser(childDeviceData); + index += childDeviceLength; + } + } + + private static int CalculateMainDeviceDataLength(ReadOnlySpan bytes, int startIndex) + { + int index = startIndex; + if (index >= bytes.Length) return 0; + byte didLength = bytes[index++]; + index += didLength; + if (index >= bytes.Length) return index - startIndex; + index++; + if (index >= bytes.Length) return index - startIndex; + byte readingCount = bytes[index++]; + for (int i = 0; i < readingCount; i++) + { + if (index + 2 > bytes.Length) return bytes.Length - startIndex; + index += 2; + if (index >= bytes.Length) break; + byte stateCount = bytes[index++]; + for (int j = 0; j < stateCount; j++) + { + if (index + 2 > bytes.Length) + return bytes.Length - startIndex; + byte sid = bytes[index++]; + byte type = bytes[index++]; + int valueLength = DeviceMessageUtilities.GetValueLength(bytes, index - 1); + if (index + valueLength > bytes.Length) + return bytes.Length - startIndex; + index += valueLength; + } + } + return index - startIndex; + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageChildSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageChildSerializer.cs new file mode 100644 index 0000000..78a8848 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageChildSerializer.cs @@ -0,0 +1,33 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public class DeviceMessageChildSerializer : AbstractMessageSerializer, IDeviceMessageChildSerializer + { + public DeviceMessageChildSerializer() : base(new DeviceMessageChild()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + + public override void Serializer(ArrayBufferWriter writer, IDeviceMessageChild model) + { + ArgumentNullException.ThrowIfNull(model); + + SerializeWithHeader( + writer, + headerLength: 1, + writeHeaderAction: w => w.Write([model?.Count ?? 0]), + serializeContentAction: w => + { + foreach (var child in model.ChildArray ?? []) + { + new DeviceMessageInfoSerializer().Serializer(writer, child); + } + }); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageHeaderSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageHeaderSerializer.cs new file mode 100644 index 0000000..43d5c41 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageHeaderSerializer.cs @@ -0,0 +1,35 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public class DeviceMessageHeaderSerializer : AbstractMessageSerializer, IDeviceMessageHeaderSerializer + { + public DeviceMessageHeaderSerializer() : base(new DeviceMessageHeader()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + private readonly static int HEADERLENGTH = 4; + public override void Serializer(ArrayBufferWriter writer, IDeviceMessageHeader model) + { + int existingLength = writer.WrittenCount; + + using var pool = DeviceMessageArrayPool.MemoryPool.Rent(existingLength + HEADERLENGTH); + + writer.WrittenMemory.ToArray().CopyTo(pool.Memory); + ReadOnlySpan existingData = pool.Memory.Span[..existingLength]; + + writer.Clear(); + + writer.Write(model.Header); + writer.Write([model.Version]); + writer.Write([model.Mark]); + + if (existingLength > 0) writer.Write(existingData); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingSerializer.cs new file mode 100644 index 0000000..9e4978e --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingSerializer.cs @@ -0,0 +1,30 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public class DeviceMessageInfoReadingSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingSerializer + { + public DeviceMessageInfoReadingSerializer() : base(new DeviceMessageInfoReading()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReading model) + { + ArgumentNullException.ThrowIfNull(model); + if (model.Offset == null) throw new ArgumentException("Offset cannot be null", nameof(model)); + if (model.Offset.Length != 2) throw new ArgumentOutOfRangeException(nameof(model), "Offset must be exactly 2 bytes long"); + SerializeWithHeader( + writer, + headerLength: 2, + writeHeaderAction: w => w.Write(model?.Offset), + serializeContentAction: w => + { + new DeviceMessageInfoReadingStatesSerializer().Serializer(writer, model.State ?? new DeviceMessageInfoReadingStates()); + }); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStateSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStateSerializer.cs new file mode 100644 index 0000000..2a798e7 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStateSerializer.cs @@ -0,0 +1,66 @@ +using DeviceCommons.DataHandling; +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public class DeviceMessageInfoReadingStateSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingStateSerializer + { + public DeviceMessageInfoReadingStateSerializer() : base(new DeviceMessageInfoReadingState()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReadingState model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + if (model.Value == null) throw new ArgumentException("Value cannot be null", nameof(model)); + + StateValueTypeEnum valueType = (StateValueTypeEnum)model.Type; + int valueLength = model.Value?.Length ?? 0; + + if (valueType != StateValueTypeEnum.String && valueType != StateValueTypeEnum.Binary) + { + int expectedLength = DeviceMessageUtilities.GetValueLength(valueType, null); + if (valueLength != expectedLength) + throw new ArgumentException($"值长度不符合类型要求。期望长度: {expectedLength}, 实际长度: {valueLength}"); + } + + int totalLength = 2; + if (valueType == StateValueTypeEnum.String) totalLength += 1; + else if (valueType == StateValueTypeEnum.Binary) totalLength += 2; + totalLength += valueLength; + + int existingLength = writer.WrittenCount; + + using var pool = DeviceMessageArrayPool.MemoryPool.Rent(existingLength + 1); + + writer.WrittenMemory.ToArray().CopyTo(pool.Memory); + ReadOnlySpan existingData = pool.Memory.Span[..existingLength]; + + + writer.Clear(); + + writer.Write([model.SID]); + writer.Write([model.Type]); + + switch (valueType) + { + case StateValueTypeEnum.String: + if (valueLength > byte.MaxValue) throw new ArgumentException($"字符串长度超出1字节范围: {valueLength}"); + writer.Write([(byte)valueLength]); + break; + + case StateValueTypeEnum.Binary: + writer.Write(BitConverter.GetBytes((ushort)valueLength)); + break; + } + writer.Write(model.Value); + + if (existingLength > 0) writer.Write(existingData); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStatesSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStatesSerializer.cs new file mode 100644 index 0000000..765e7dd --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingStatesSerializer.cs @@ -0,0 +1,33 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public class DeviceMessageInfoReadingStatesSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingStatesSerializer + { + public DeviceMessageInfoReadingStatesSerializer() : base(new DeviceMessageInfoReadingStates()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + + public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReadingStates model) + { + ArgumentNullException.ThrowIfNull(model); + + SerializeWithHeader( + writer, + headerLength: 1, + writeHeaderAction: w => w.Write([model?.Count ?? 0]), + serializeContentAction: w => + { + foreach (var state in model.StateArray ?? []) + { + new DeviceMessageInfoReadingStateSerializer().Serializer(w, state); + } + }); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingsSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingsSerializer.cs new file mode 100644 index 0000000..3661c1e --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoReadingsSerializer.cs @@ -0,0 +1,32 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public class DeviceMessageInfoReadingsSerializer : AbstractMessageSerializer, IDeviceMessageInfoReadingsSerializer + { + public DeviceMessageInfoReadingsSerializer() : base(new DeviceMessageInfoReadings()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfoReadings model) + { + ArgumentNullException.ThrowIfNull(model); + + SerializeWithHeader( + writer, + headerLength: 1, + writeHeaderAction: w => w.Write([model?.Count ?? 0]), + serializeContentAction: w => + { + foreach (var reading in model.ReadingArray ?? []) + { + new DeviceMessageInfoReadingSerializer().Serializer(writer, reading); + } + }); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoSerializer.cs new file mode 100644 index 0000000..70122ae --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/DeviceMessageInfoSerializer.cs @@ -0,0 +1,38 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; +using System.Buffers; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public class DeviceMessageInfoSerializer : AbstractMessageSerializer, IDeviceMessageInfoSerializer + { + public DeviceMessageInfoSerializer() : base(new DeviceMessageInfo()) + { + } + + public override void Serializer(ArrayBufferWriter writer) => Serializer(writer, Model); + + public override void Serializer(ArrayBufferWriter writer, IDeviceMessageInfo model) + { + if (string.IsNullOrEmpty(model.DID)) + throw new ArgumentNullException(nameof(model.DID), "设备ID不能为空"); + + if (model.Reading == null) + throw new ArgumentNullException(nameof(model.Reading), "读数信息不能为空"); + + SerializeWithHeader( + writer, + headerLength: model.Length + 2, + writeHeaderAction: w => + { + w.Write([model.Length]); + w.Write(model.DIDBytes); + w.Write([model.DeviceType]); + }, + serializeContentAction: w => + { + new DeviceMessageInfoReadingsSerializer().Serializer(w, model.Reading); + }); + } + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageChildSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageChildSerializer.cs new file mode 100644 index 0000000..0f503be --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageChildSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public interface IDeviceMessageChildSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageHeaderSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageHeaderSerializer.cs new file mode 100644 index 0000000..57f1a94 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageHeaderSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public interface IDeviceMessageHeaderSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingSerializer.cs new file mode 100644 index 0000000..c827406 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public interface IDeviceMessageInfoReadingSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStateSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStateSerializer.cs new file mode 100644 index 0000000..b976f58 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStateSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public interface IDeviceMessageInfoReadingStateSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStatesSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStatesSerializer.cs new file mode 100644 index 0000000..79738c6 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingStatesSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public interface IDeviceMessageInfoReadingStatesSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingsSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingsSerializer.cs new file mode 100644 index 0000000..7d962ef --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoReadingsSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public interface IDeviceMessageInfoReadingsSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoSerializer.cs b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoSerializer.cs new file mode 100644 index 0000000..7b12a71 --- /dev/null +++ b/DeviceCommons/DeviceMessages/Serialization/V1/Serializers/IDeviceMessageInfoSerializer.cs @@ -0,0 +1,9 @@ +using DeviceCommons.DeviceMessages.Abstractions; +using DeviceCommons.DeviceMessages.Models.V1; + +namespace DeviceCommons.DeviceMessages.Serialization.V1.Serializers +{ + public interface IDeviceMessageInfoSerializer : IMessageSerializer + { + } +} diff --git a/DeviceCommons/Exceptions/InvalidMessageException.cs b/DeviceCommons/Exceptions/InvalidMessageException.cs new file mode 100644 index 0000000..c3280e0 --- /dev/null +++ b/DeviceCommons/Exceptions/InvalidMessageException.cs @@ -0,0 +1,12 @@ +namespace DeviceCommons.Exceptions +{ + public class InvalidMessageException : Exception + { + public InvalidMessageException() { } + + public InvalidMessageException(string? message) : base(message) { } + + public InvalidMessageException(string? message, Exception? innerException) + : base(message, innerException) { } + } +} diff --git a/DeviceCommons/Security/AesEncryptor.cs b/DeviceCommons/Security/AesEncryptor.cs new file mode 100644 index 0000000..f81509d --- /dev/null +++ b/DeviceCommons/Security/AesEncryptor.cs @@ -0,0 +1,192 @@ +using System.Security.Cryptography; +using System.Text; + +namespace DeviceCommons.Security +{ + public class AesEncryptor + { + private static readonly int DefaultKeySize = 256; + private static readonly int DefaultDerivationIterations = 60000; + private static readonly int DefaultSaltSize = 16; + private static readonly int DefaultIvSize = 16; + private readonly int keySize; + private readonly int derivationIterations; + private readonly int saltSize; + private readonly int ivSize; + + public AesEncryptor() + : this(DefaultKeySize, DefaultDerivationIterations, DefaultSaltSize, DefaultIvSize) + { + } + + public AesEncryptor(int keySize, int derivationIterations, int saltSize, int ivSize) + { + if (keySize != 128 && keySize != 192 && keySize != 256) + throw new ArgumentOutOfRangeException(nameof(keySize), "Key size must be 128, 192, or 256 bits."); + if (derivationIterations <= 0) + throw new ArgumentOutOfRangeException(nameof(derivationIterations), "Derivation iterations must be greater than zero."); + if (saltSize <= 0) + throw new ArgumentOutOfRangeException(nameof(saltSize), "Salt size must be greater than zero."); + if (ivSize <= 0) + throw new ArgumentOutOfRangeException(nameof(ivSize), "IV size must be greater than zero."); + + this.keySize = keySize; + this.derivationIterations = derivationIterations; + this.saltSize = saltSize; + this.ivSize = ivSize; + } + + public string Encrypt(string plainText, string passPhrase) + { + if (string.IsNullOrEmpty(plainText)) throw new ArgumentNullException(nameof(plainText), "Plain text cannot be empty"); + if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase), "Passphrase cannot be empty"); + + var salt = GenerateRandomBytes(saltSize); + var iv = GenerateRandomBytes(ivSize); + + var keyBytes = DeriveKeyBytes(passPhrase, salt); + + using (var symmetricKey = CreateAesSymmetricKey(keyBytes, iv)) + using (var memoryStream = new MemoryStream()) + { + + memoryStream.Write(salt, 0, salt.Length); + memoryStream.Write(iv, 0, iv.Length); + + using (var cryptoStream = new CryptoStream(memoryStream, + symmetricKey.CreateEncryptor(), + CryptoStreamMode.Write)) + using (var writer = new StreamWriter(cryptoStream, Encoding.UTF8)) + { + writer.Write(plainText); + writer.Flush(); + } + + return Convert.ToBase64String(memoryStream.ToArray()); + } + } + + public string Decrypt(string cipherText, string passPhrase) + { + if (string.IsNullOrEmpty(cipherText)) throw new ArgumentNullException(nameof(cipherText), "Cipher text cannot be empty"); + if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase), "Passphrase cannot be empty"); + + try + { + var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); + + if (cipherTextBytesWithSaltAndIv.Length < saltSize + ivSize) + throw new ArgumentException($"Invalid cipher text format. Expected at least {saltSize + ivSize} bytes, but got {cipherTextBytesWithSaltAndIv.Length} bytes."); + + var salt = cipherTextBytesWithSaltAndIv.Take(saltSize).ToArray(); + var iv = cipherTextBytesWithSaltAndIv.Skip(saltSize).Take(ivSize).ToArray(); + var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(saltSize + ivSize).ToArray(); + + var keyBytes = DeriveKeyBytes(passPhrase, salt); + + using (var symmetricKey = CreateAesSymmetricKey(keyBytes, iv)) + using (var memoryStream = new MemoryStream(cipherTextBytes)) + using (var cryptoStream = new CryptoStream(memoryStream, symmetricKey.CreateDecryptor(), CryptoStreamMode.Read)) + using (var reader = new StreamReader(cryptoStream, Encoding.UTF8)) + { + return reader.ReadToEnd(); + } + } + catch (CryptographicException ex) + { + throw new InvalidOperationException("Invalid passphrase or corrupted data.", ex); + } + catch (FormatException ex) + { + throw new ArgumentException("Invalid base64 string.", nameof(cipherText), ex); + } + } + + public async ValueTask EncryptAsync(string plainText, string passPhrase, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(plainText)) throw new ArgumentNullException(nameof(plainText)); + if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase)); + + var salt = GenerateRandomBytes(saltSize); + var iv = GenerateRandomBytes(ivSize); + var keyBytes = DeriveKeyBytes(passPhrase, salt); + + using var symmetricKey = CreateAesSymmetricKey(keyBytes, iv); + using (var memoryStream = new MemoryStream()) + { + await memoryStream.WriteAsync(salt, cancellationToken).ConfigureAwait(false); + await memoryStream.WriteAsync(iv, cancellationToken).ConfigureAwait(false); + + using (var cryptoStream = new CryptoStream(memoryStream, symmetricKey.CreateEncryptor(), CryptoStreamMode.Write)) + using (var writer = new StreamWriter(cryptoStream, Encoding.UTF8)) + { + await writer.WriteAsync(plainText).ConfigureAwait(false); + await writer.FlushAsync().ConfigureAwait(false); + } + + return Convert.ToBase64String(memoryStream.ToArray()); + } + } + + public async ValueTask DecryptAsync(string cipherText, string passPhrase, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(cipherText)) throw new ArgumentNullException(nameof(cipherText)); + if (string.IsNullOrEmpty(passPhrase)) throw new ArgumentNullException(nameof(passPhrase)); + + try + { + var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); + + if (cipherTextBytesWithSaltAndIv.Length < saltSize + ivSize) + throw new ArgumentException("Invalid cipher text format"); + + var salt = cipherTextBytesWithSaltAndIv.Take(saltSize).ToArray(); + var iv = cipherTextBytesWithSaltAndIv.Skip(saltSize).Take(ivSize).ToArray(); + var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(saltSize + ivSize).ToArray(); + + var keyBytes = DeriveKeyBytes(passPhrase, salt); + + using var symmetricKey = CreateAesSymmetricKey(keyBytes, iv); + using var memoryStream = new MemoryStream(cipherTextBytes); + using var cryptoStream = new CryptoStream(memoryStream, symmetricKey.CreateDecryptor(), CryptoStreamMode.Read); + using var reader = new StreamReader(cryptoStream, Encoding.UTF8); + + return await reader.ReadToEndAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + throw new InvalidOperationException("Decryption failed", ex); + } + } + + private byte[] DeriveKeyBytes(string passPhrase, byte[] salt) + { + using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(passPhrase))) + + using (var pbkdf2 = new Rfc2898DeriveBytes(passPhrase, salt, derivationIterations)) + { + return pbkdf2.GetBytes(keySize / 8); + } + } + + private Aes CreateAesSymmetricKey(byte[] keyBytes, byte[] iv) + { + var aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + aes.Key = keyBytes; + aes.IV = iv; + return aes; + } + + private byte[] GenerateRandomBytes(int size) + { + var randomBytes = new byte[size]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomBytes); + } + return randomBytes; + } + } +} diff --git a/DeviceCommons/Security/CrcCalculator.cs b/DeviceCommons/Security/CrcCalculator.cs new file mode 100644 index 0000000..2baa8a9 --- /dev/null +++ b/DeviceCommons/Security/CrcCalculator.cs @@ -0,0 +1,84 @@ +using DeviceCommons.DeviceMessages.Enums; + +namespace DeviceCommons.Security +{ + public class CrcCalculator + { + public static int GetCrcLength(CRCTypeEnum crcType) + { + return crcType switch + { + CRCTypeEnum.CRC8 => 1, + CRCTypeEnum.CRC16 => 2, + CRCTypeEnum.CRC32 => 4, + _ => 0 + }; + } + + public static byte[] CalculateCrcBytes(CRCTypeEnum CRCType, byte[] data) + { + if (CRCType == CRCTypeEnum.None) + return []; + + return CRCType switch + { + CRCTypeEnum.CRC8 => new byte[] { CrcCalculator.ComputeCrc8(data) }, + CRCTypeEnum.CRC16 => BitConverter.GetBytes(CrcCalculator.ComputeCrc16(data)), + CRCTypeEnum.CRC32 => BitConverter.GetBytes(CrcCalculator.ComputeCrc32(data)), + _ => [] + }; + + } + + public static byte ComputeCrc8(ReadOnlySpan data) + { + byte crc = 0; + foreach (byte b in data) + { + crc ^= b; + for (int i = 0; i < 8; i++) + { + if ((crc & 0x80) != 0) + crc = (byte)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc; + } + + public static ushort ComputeCrc16(ReadOnlySpan data) + { + ushort crc = 0x0000; + foreach (byte b in data) + { + crc ^= (ushort)(b << 8); + for (int i = 0; i < 8; i++) + { + if ((crc & 0x8000) != 0) + crc = (ushort)((crc << 1) ^ 0x8005); + else + crc <<= 1; + } + } + return crc; + } + + public static uint ComputeCrc32(ReadOnlySpan data) + { + uint crc = 0xFFFFFFFF; + foreach (byte b in data) + { + crc ^= b; + for (int i = 0; i < 8; i++) + { + if ((crc & 1) != 0) + crc = (crc >> 1) ^ 0xEDB88320; + else + crc >>= 1; + } + } + return crc ^ 0xFFFFFFFF; + } + } +} diff --git a/TestProject1/BaseUnitTest.cs b/TestProject1/BaseUnitTest.cs new file mode 100644 index 0000000..e0fa523 --- /dev/null +++ b/TestProject1/BaseUnitTest.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1 +{ + public abstract class BaseUnitTest + { + protected void LogWrite(string message) + { + Debug.WriteLine(""); + Debug.WriteLine("-----------------------------------------------------------------------"); + Debug.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} =>"); + Debug.WriteLine($"{message}"); + } + } +} diff --git a/TestProject1/BasicStructureUnitTest.cs b/TestProject1/BasicStructureUnitTest.cs new file mode 100644 index 0000000..fb8fd3b --- /dev/null +++ b/TestProject1/BasicStructureUnitTest.cs @@ -0,0 +1,170 @@ +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; +using DeviceCommons.DeviceMessages.Serialization; +using DeviceCommons.DeviceMessages.Serialization.V1.Parsers; +using DeviceCommons.DeviceMessages.Serialization.V1.Serializers; +using System.Buffers; + +namespace TestProject1 +{ + public class BasicStructureUnitTest : BaseUnitTest + { + private readonly ArrayBufferWriter BufferWriter = new ArrayBufferWriter(); + [Fact] + public void Test1() => State(BufferWriter, 1, StateValueTypeEnum.String, "Value"); + + [Fact] + public void Test2() => States(BufferWriter, 2); + + [Fact] + public void Test3() => Reading(BufferWriter, 1000); + + [Fact] + public void Test4() => Readings(BufferWriter, 3); + + [Fact] + public void Test5() => Info(BufferWriter, "Device", 1); + + [Fact] + public void Test6() => Child(BufferWriter, 2); + + [Fact] + public void Test7() => Header(BufferWriter); + + [Fact] + public void Test8() => Message(); + + private IDeviceMessageInfoReadingState State(ArrayBufferWriter _BufferWriter, byte sid, StateValueTypeEnum type, object val) + { + IDeviceMessageInfoReadingStateSerializer stateSerializer = new DeviceMessageInfoReadingStateSerializer(); + stateSerializer.Model.SID = sid; + stateSerializer.Model.ValueType = type; + stateSerializer.Model.ValueText = val; + stateSerializer.Serializer(_BufferWriter); + byte[] bytes = _BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + + IDeviceMessageInfoReadingStateParser stateParser = new DeviceMessageInfoReadingStateParser(); + _ = stateParser.Parser(bytes); + + + return stateSerializer.Model; + } + + private IDeviceMessageInfoReadingStates States(ArrayBufferWriter _BufferWriter, byte count) + { + IDeviceMessageInfoReadingStatesSerializer statesSerializer = new DeviceMessageInfoReadingStatesSerializer(); + statesSerializer.Model.StateArray = new IDeviceMessageInfoReadingState[count]; + for (byte i = 0; i < count; i++) + { + statesSerializer.Model.StateArray[i] = State(_BufferWriter, (byte)(i + 1), StateValueTypeEnum.String, "Value"); + } + statesSerializer.Serializer(_BufferWriter); + byte[] bytes = _BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + + _ = new DeviceMessageInfoReadingStatesParser().Parser(bytes); + + return statesSerializer.Model; + } + + private IDeviceMessageInfoReading Reading(ArrayBufferWriter _BufferWriter, short timeOffset) + { + IDeviceMessageInfoReadingSerializer readingSerializer = new DeviceMessageInfoReadingSerializer(); + readingSerializer.Model.TimeOffset = timeOffset; + readingSerializer.Model.State = States(_BufferWriter, 4); + readingSerializer.Serializer(_BufferWriter); + byte[] bytes = _BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + + _ = new DeviceMessageInfoReadingParser().Parser(bytes); + + return readingSerializer.Model; + } + + private IDeviceMessageInfoReadings Readings(ArrayBufferWriter _BufferWriter, byte count) + { + IDeviceMessageInfoReadingsSerializer readingsSerializer = new DeviceMessageInfoReadingsSerializer(); + readingsSerializer.Model.ReadingArray = new IDeviceMessageInfoReading[count]; + for (byte i = 0; i < count; i++) + { + readingsSerializer.Model.ReadingArray[i] = Reading(_BufferWriter, (short)((i + 1) * 100)); + } + readingsSerializer.Serializer(_BufferWriter); + byte[] bytes = _BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + + _ = new DeviceMessageInfoReadingsParser().Parser(bytes); + + return readingsSerializer.Model; + } + + private IDeviceMessageInfo Info(ArrayBufferWriter _BufferWriter, string did, byte type) + { + IDeviceMessageInfoSerializer infoSerializer = new DeviceMessageInfoSerializer(); + infoSerializer.Model.DID = did; + infoSerializer.Model.DeviceType = type; + infoSerializer.Model.Reading = Readings(_BufferWriter, 3); + infoSerializer.Serializer(_BufferWriter); + byte[] bytes = _BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + + _ = new DeviceMessageInfoParser().Parser(bytes); + + return infoSerializer.Model; + } + + private IDeviceMessageChild Child(ArrayBufferWriter _BufferWriter, byte count) + { + IDeviceMessageChildSerializer childSerializer = new DeviceMessageChildSerializer(); + IDeviceMessageInfo[] childs = new IDeviceMessageInfo[count]; + for (byte i = 0; i < count; i++) + { + childs[i] = Info(_BufferWriter, $"Device{(i + 1).ToString().PadLeft(2, '0')}", 148); + } + childSerializer.Model.ChildArray = childs; + childSerializer.Serializer(_BufferWriter); + byte[] bytes = _BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + _ = new DeviceMessageChildParser().Parser(bytes); + + return childSerializer.Model; + } + + private IDeviceMessageHeader Header(ArrayBufferWriter _BufferWriter) + { + IDeviceMessageHeaderSerializer headerSerializer = new DeviceMessageHeaderSerializer(); + headerSerializer.Serializer(_BufferWriter); + byte[] bytes = _BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + + _ = new DeviceMessageHeaderParser().Parser(bytes); + + return headerSerializer.Model; + } + + private IDeviceMessage Message() + { + IDeviceMessageSerializer messageSerializer = new DeviceMessageSerializer(); + messageSerializer.Model.ChildDevice = Child(BufferWriter, 2); + messageSerializer.Model.MainDevice = Info(BufferWriter, "Device", 1); + messageSerializer.Model.Header = Header(BufferWriter); + + messageSerializer.Serializer(BufferWriter); + byte[] bytes = BufferWriter.WrittenSpan.ToArray(); + + LogWrite(Convert.ToHexString(bytes)); + + _ = new DeviceMessageParser().Parser(bytes); + + return messageSerializer.Model; + } + } +} \ No newline at end of file diff --git a/TestProject1/BoundaryUnitTest.cs b/TestProject1/BoundaryUnitTest.cs new file mode 100644 index 0000000..750d6cd --- /dev/null +++ b/TestProject1/BoundaryUnitTest.cs @@ -0,0 +1,408 @@ +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; +using DeviceCommons.DeviceMessages.Serialization; +using DeviceCommons.DeviceMessages.Serialization.V1.Parsers; +using DeviceCommons.DeviceMessages.Serialization.V1.Serializers; +using DeviceCommons.Exceptions; +using System.Buffers; + +namespace TestProject1 +{ + public class BoundaryUnitTest + { + private readonly ArrayBufferWriter BufferWriter = new ArrayBufferWriter(); + + [Fact] + public void TestEmptyData_ShouldThrowException() + { + // Arrange + var parser = new DeviceMessageParser(); + var emptyData = Array.Empty(); + + // Act & Assert + Assert.Throws(() => parser.Parser(emptyData)); + } + + [Fact] + public void TestMinimalValidMessage_ShouldParseSuccessfully() + { + // Arrange + var parser = new DeviceMessageParser(); + var serializer = new DeviceMessageSerializer(); + + // 创建最小有效消息 + var message = new DeviceMessage + { + Header = new DeviceMessageHeader + { + Version = 0x01, + CRCType = CRCTypeEnum.None + }, + MainDevice = new DeviceMessageInfo + { + DID = "test", + DeviceType = 1, + Reading = new DeviceMessageInfoReadings() + } + }; + + // 序列化 + var writer = new ArrayBufferWriter(); + serializer.Serializer(writer, message); + var bytes = writer.WrittenSpan.ToArray(); + + // Act + var result = parser.Parser(bytes); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.Header); + Assert.NotNull(result.MainDevice); + Assert.Equal("test", result.MainDevice.DID); + } + + [Fact] + public void TestMaxStringLength_ShouldHandleCorrectly() + { + // Arrange + var stateParser = new DeviceMessageInfoReadingStateParser(); + var stateSerializer = new DeviceMessageInfoReadingStateSerializer(); + + // 创建最大长度的字符串 + var maxString = new string('A', byte.MaxValue); + var state = new DeviceMessageInfoReadingState + { + SID = 1, + ValueType = StateValueTypeEnum.String, + ValueText = maxString + }; + + // 序列化 + var writer = new ArrayBufferWriter(); + stateSerializer.Serializer(writer, state); + var bytes = writer.WrittenSpan.ToArray(); + + // Act + var result = stateParser.Parser(bytes); + + // Assert + Assert.NotNull(result); + Assert.Equal(maxString, result.ValueText); + } + + [Fact] + public void TestExceedMaxStringLength_ShouldThrowException() + { + // Arrange + var stateSerializer = new DeviceMessageInfoReadingStateSerializer(); + + // 创建超过最大长度的字符串 + var exceedString = new string('A', byte.MaxValue + 1); + var state = new DeviceMessageInfoReadingState + { + SID = 1, + ValueType = StateValueTypeEnum.String, + ValueText = exceedString + }; + + // Act & Assert + var writer = new ArrayBufferWriter(); + Assert.Throws(() => stateSerializer.Serializer(writer, state)); + } + + [Fact] + public void TestInvalidValueType_ShouldHandleGracefully() + { + // Arrange + var stateParser = new DeviceMessageInfoReadingStateParser(); + + // 创建无效类型的数据 + var invalidData = new byte[] { 1, 255, 0 }; // SID=1, Type=255(无效), ValueLength=0 + + // Act & Assert + Assert.Throws(() => stateParser.Parser(invalidData)); + } + + [Fact] + public void TestCRCValidation_WithMismatch_ShouldThrowException() + { + // Arrange + var parser = new DeviceMessageParser(); + var serializer = new DeviceMessageSerializer(); + + // 创建带CRC的消息 + var message = new DeviceMessage + { + Header = new DeviceMessageHeader + { + Version = 0x01, + CRCType = CRCTypeEnum.CRC16 + }, + MainDevice = new DeviceMessageInfo + { + DID = "test", + DeviceType = 1, + Reading = new DeviceMessageInfoReadings() + } + }; + + // 序列化 + var writer = new ArrayBufferWriter(); + serializer.Serializer(writer, message); + var bytes = writer.WrittenSpan.ToArray(); + + // 修改数据以破坏CRC + bytes[bytes.Length - 1] ^= 0xFF; // 修改最后一个字节 + + // Act & Assert + Assert.Throws(() => parser.Parser(bytes)); + } + + [Fact] + public void TestLargeNumberOfReadings_ShouldHandleCorrectly() + { + // Arrange + var readingsParser = new DeviceMessageInfoReadingsParser(); + var readingsSerializer = new DeviceMessageInfoReadingsSerializer(); + + // 创建大量读数 + var readings = new DeviceMessageInfoReadings + { + ReadingArray = new IDeviceMessageInfoReading[byte.MaxValue] + }; + + for (int i = 0; i < byte.MaxValue; i++) + { + readings.ReadingArray[i] = new DeviceMessageInfoReading + { + TimeOffset = (short)i, + State = new DeviceMessageInfoReadingStates + { + StateArray = new IDeviceMessageInfoReadingState[1] + { + new DeviceMessageInfoReadingState + { + SID = 1, + ValueType = StateValueTypeEnum.Int32, + ValueText = i + } + } + } + }; + } + + // 序列化 + var writer = new ArrayBufferWriter(); + readingsSerializer.Serializer(writer, readings); + var bytes = writer.WrittenSpan.ToArray(); + + // Act + var result = readingsParser.Parser(bytes); + + // Assert + Assert.NotNull(result); + Assert.Equal(byte.MaxValue, result.Count); + } + + [Fact] + public void TestLargeNumberOfStates_ShouldHandleCorrectly() + { + // Arrange + var statesParser = new DeviceMessageInfoReadingStatesParser(); + var statesSerializer = new DeviceMessageInfoReadingStatesSerializer(); + + // 创建大量状态 + var states = new DeviceMessageInfoReadingStates + { + StateArray = new IDeviceMessageInfoReadingState[byte.MaxValue] + }; + + for (int i = 0; i < byte.MaxValue; i++) + { + states.StateArray[i] = new DeviceMessageInfoReadingState + { + SID = (byte)i, + ValueType = StateValueTypeEnum.Int32, + ValueText = i + }; + } + + // 序列化 + var writer = new ArrayBufferWriter(); + statesSerializer.Serializer(writer, states); + var bytes = writer.WrittenSpan.ToArray(); + + // Act + var result = statesParser.Parser(bytes); + + // Assert + Assert.NotNull(result); + Assert.Equal(byte.MaxValue, result.Count); + } + + [Fact] + public void TestEmptyDeviceID_ShouldHandleCorrectly() + { + // Arrange + var infoParser = new DeviceMessageInfoParser(); + var infoSerializer = new DeviceMessageInfoSerializer(); + + var deviceInfo = new DeviceMessageInfo + { + DID = "", // 空设备ID + DeviceType = 1, + Reading = new DeviceMessageInfoReadings() + }; + + // 序列化 + var writer = new ArrayBufferWriter(); + infoSerializer.Serializer(writer, deviceInfo); + var bytes = writer.WrittenSpan.ToArray(); + + // Act + var result = infoParser.Parser(bytes); + + // Assert + Assert.NotNull(result); + Assert.Equal("", result.DID); + Assert.Equal(0, result.Length); + } + + [Fact] + public void TestLongDeviceID_ShouldHandleCorrectly() + { + // Arrange + var infoParser = new DeviceMessageInfoParser(); + var infoSerializer = new DeviceMessageInfoSerializer(); + + // 创建长设备ID + var longDeviceId = new string('A', byte.MaxValue); + var deviceInfo = new DeviceMessageInfo + { + DID = longDeviceId, + DeviceType = 1, + Reading = new DeviceMessageInfoReadings() + }; + + // 序列化 + var writer = new ArrayBufferWriter(); + infoSerializer.Serializer(writer, deviceInfo); + var bytes = writer.WrittenSpan.ToArray(); + + // Act + var result = infoParser.Parser(bytes); + + // Assert + Assert.NotNull(result); + Assert.Equal(longDeviceId, result.DID); + Assert.Equal(byte.MaxValue, result.Length); + } + + [Fact] + public void TestMalformedData_ShouldThrowException() + { + // Arrange + var parser = new DeviceMessageParser(); + + // 创建格式错误的数据 + var malformedData = new byte[] { 0xC0, 0xBF, 0x01, 0x20 }; // 只有头部,缺少必要数据 + + // Act & Assert + Assert.Throws(() => parser.Parser(malformedData)); + } + + [Fact] + public void TestInsufficientBuffer_ShouldThrowException() + { + // Arrange + var stateParser = new DeviceMessageInfoReadingStateParser(); + + // 创建不足的数据 + var insufficientData = new byte[] { 1, (byte)StateValueTypeEnum.String }; // 只有SID和类型,缺少长度和值 + + // Act & Assert + Assert.Throws(() => stateParser.Parser(insufficientData)); + } + + [Fact] + public void TestAllValueTypes_ShouldHandleCorrectly() + { + // Arrange + var stateParser = new DeviceMessageInfoReadingStateParser(); + var stateSerializer = new DeviceMessageInfoReadingStateSerializer(); + + // 测试所有值类型 + var valueTypes = Enum.GetValues(typeof(StateValueTypeEnum)).Cast(); + + foreach (var valueType in valueTypes) + { + var state = new DeviceMessageInfoReadingState + { + SID = 1, + ValueType = valueType + }; + + // 设置合适的值 + switch (valueType) + { + case StateValueTypeEnum.Float32: + state.ValueText = 3.14f; + break; + case StateValueTypeEnum.Int32: + state.ValueText = 42; + break; + case StateValueTypeEnum.String: + state.ValueText = "test"; + break; + case StateValueTypeEnum.Bool: + state.ValueText = true; + break; + case StateValueTypeEnum.UInt16: + state.ValueText = (ushort)123; + break; + case StateValueTypeEnum.Int16: + state.ValueText = (short)-123; + break; + case StateValueTypeEnum.Timestamp: + state.ValueText = (ulong)1234567890; + break; + case StateValueTypeEnum.Binary: + state.ValueText = "binary data"; + break; + case StateValueTypeEnum.Double: + state.ValueText = 3.14159265358979; + break; + } + + // 序列化 + var writer = new ArrayBufferWriter(); + stateSerializer.Serializer(writer, state); + var bytes = writer.WrittenSpan.ToArray(); + + // Act + var result = stateParser.Parser(bytes); + + // Assert + Assert.NotNull(result); + Assert.Equal(valueType, result.ValueType); + } + } + + [Fact] + public void TestNullValues_ShouldHandleGracefully() + { + // Arrange + var infoSerializer = new DeviceMessageInfoSerializer(); + var deviceInfo = new DeviceMessageInfo + { + DID = null, // null设备ID + DeviceType = 1, + Reading = null // null读数 + }; + + // Act & Assert + var writer = new ArrayBufferWriter(); + Assert.Throws(() => infoSerializer.Serializer(writer, deviceInfo)); + } + } +} diff --git a/TestProject1/BuildUnitTest.cs b/TestProject1/BuildUnitTest.cs new file mode 100644 index 0000000..74614d8 --- /dev/null +++ b/TestProject1/BuildUnitTest.cs @@ -0,0 +1,157 @@ +using BenchmarkDotNet.Attributes; +using DeviceCommons.DeviceMessages.Builders; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Serialization; +using System.Diagnostics; +using System.Text; + +namespace TestProject1 +{ + public class BuildUnitTest : BaseUnitTest + { + private static readonly IDeviceMessageParser PAR = new DeviceMessageParser(); + private static readonly Stopwatch SW = new Stopwatch(); + [Fact] + public void Test1() + { + IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create().WithMainDevice("Device", 148); + var builder = deviceMessage.BuildHex().ToUpper(); + LogWrite(builder); + PAR.Parser(builder); + } + + [Fact] + public void Test2() + { + IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create() + .WithHeader(version: 2, crcType: CRCTypeEnum.CRC8) + .WithMainDevice("Device", 148); + var builder = deviceMessage.BuildHex().ToUpper(); + LogWrite(builder); + PAR.Parser(builder); + } + + [Fact] + public void Test3() + { + IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create() + .WithHeader(version: 2, crcType: CRCTypeEnum.CRC8) + .WithMainDevice("Device", 148, + config: rs => + { + rs.AddReading(260 * 1, config: r => + { + for (int i = 1; i <= 3; i++) + { + r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + } + }); + rs.AddReading(260 * 2, config: r => + { + for (int i = 1; i <= 3; i++) + { + r.AddState(9, Encoding.UTF8.GetBytes("Binary"), StateValueTypeEnum.Binary); + } + }); + } + ); + var model = deviceMessage.Build(); + var builder = deviceMessage.BuildHex().ToUpper(); + LogWrite(builder); + PAR.Parser(builder); + } + + [Fact] + public void Test4() + { + Stopwatch sw = Stopwatch.StartNew(); + IDeviceMessageBuilder deviceMessage = DeviceMessageBuilder.Create() + .WithHeader(version: 2, crcType: CRCTypeEnum.CRC16) + .WithMainDevice("Device", 148, + config: rs => + { + rs.AddReading(260 * 1, config: r => + { + for (int i = 1; i <= 3; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + rs.AddReading(260 * 2, config: r => + { + for (int i = 1; i <= 3; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + } + ) + .WithChildDevice("Device1", 149, (rs) => + { + rs.AddReading(260 * 1, config: r => + { + for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + rs.AddReading(260 * 2, config: r => + { + for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + rs.AddReading(260 * 3, config: r => + { + for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + }) + .WithChildDevice("Device2", 149, (rs) => + { + rs.AddReading(260 * 1, config: r => + { + for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + rs.AddReading(260 * 2, config: r => + { + for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + rs.AddReading(260 * 3, config: r => + { + for (int i = 1; i <= 4; i++) r.AddState((byte)i, $"State{i}", StateValueTypeEnum.String); + }); + }); + var model1 = deviceMessage.Build(); + var bytes = deviceMessage.BuildBytes(); + sw.Stop(); + _ = sw.ElapsedMilliseconds; + var builder = deviceMessage.BuildHex().ToUpper(); + LogWrite(builder); + } + + + [Theory] + [InlineData(new byte[] { 0x01, 0x02, 0x03 })] + public void Test5(byte[] builder) + { + Stopwatch sw = Stopwatch.StartNew(); + sw.Restart(); + PAR.Parser(builder); + sw.Stop(); + _ = sw.ElapsedMilliseconds; + } + + [Fact] + public void Test6() + { + string hex = "DEC,RAW|c0bf0220074d61696e446576940203e8010103064f6e6c696e6507d0010203024f4b01064368696c643195010bb80201030454656d7002030432352e33302d"; + SW.Restart(); + PAR.Parser(hex); + SW.Stop(); + _ = SW.ElapsedMilliseconds; + } + + [Fact] + public void Test7() + { + var msg = DeviceMessageBuilder.Create() + .WithMainDevice("D001", 0x01) + .AddReading(100, + (1, 25.3f, null), // 自动推断 Float32 + (2, "Running", null), // 自动推断 String + (3, true, null), // 自动推断 Bool + (4, (short)-5, StateValueTypeEnum.Int16)) // 手动指定 + .BuildHex(); + } + + } +} diff --git a/TestProject1/ComprehensiveUnitTest.cs b/TestProject1/ComprehensiveUnitTest.cs new file mode 100644 index 0000000..f477776 --- /dev/null +++ b/TestProject1/ComprehensiveUnitTest.cs @@ -0,0 +1,348 @@ +using DeviceCommons.DeviceMessages.Builders; +using DeviceCommons.DeviceMessages.Enums; +using DeviceCommons.DeviceMessages.Models.V1; +using DeviceCommons.DeviceMessages.Serialization; +using DeviceCommons.Exceptions; +using System.Buffers; +using System.Text; +using Xunit; +using Xunit.Abstractions; + +namespace DeviceCommons.Tests +{ + public class ComprehensiveUnitTest + { + private readonly ITestOutputHelper _output; + private readonly IDeviceMessageParser _parser; + private readonly IDeviceMessageSerializer _serializer; + + public ComprehensiveUnitTest(ITestOutputHelper output) + { + _output = output; + _parser = new DeviceMessageParser(); + _serializer = new DeviceMessageSerializer(); + } + + [Fact] + public void FullMessageRoundTrip_ShouldWorkCorrectly() + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC16) + .WithMainDevice("MainDevice001", 0x01, config => + { + config.AddReading(100, reading => + { + reading.AddState(1, 25.5f, StateValueTypeEnum.Float32); + reading.AddState(2, "正常", StateValueTypeEnum.String); + reading.AddState(3, true, StateValueTypeEnum.Bool); + }); + config.AddReading(200, reading => + { + reading.AddState(1, 26.1f, StateValueTypeEnum.Float32); + reading.AddState(4, 123, StateValueTypeEnum.Int32); + }); + }) + .WithChildDevice("ChildDevice001", 0x02, config => + { + config.AddReading(50, reading => + { + reading.AddState(1, 22.3f, StateValueTypeEnum.Float32); + reading.AddState(5, 456, StateValueTypeEnum.Int16); + }); + }); + + var originalMessage = builder.Build(); + + // Act - Serialize + var arrayBufferWriter = new ArrayBufferWriter(); + _serializer.Serializer(arrayBufferWriter, originalMessage); + var bytes = arrayBufferWriter.WrittenSpan.ToArray(); + + // Act - Deserialize + var parsedMessage = _parser.Parser(bytes); + + // Assert + Assert.NotNull(parsedMessage); + Assert.NotNull(parsedMessage.Header); + Assert.NotNull(parsedMessage.MainDevice); + Assert.NotNull(parsedMessage.ChildDevice); + + Assert.Equal(originalMessage.Header.Version, parsedMessage.Header.Version); + Assert.Equal(originalMessage.Header.CRCType, parsedMessage.Header.CRCType); + Assert.Equal(originalMessage.MainDevice.DID, parsedMessage.MainDevice.DID); + Assert.Equal(originalMessage.MainDevice.DeviceType, parsedMessage.MainDevice.DeviceType); + Assert.Equal(originalMessage.ChildDevice.Count, parsedMessage.ChildDevice.Count); + + _output.WriteLine($"Original: {originalMessage.MainDevice.DID}"); + _output.WriteLine($"Parsed: {parsedMessage.MainDevice.DID}"); + _output.WriteLine($"Bytes length: {bytes.Length}"); + } + + [Fact] + public void MessageWithAllValueTypes_ShouldSerializeCorrectly() + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithMainDevice("TestDevice", 0x01, config => + { + config.AddReading(0, reading => + { + // Test all supported value types + reading.AddState(1, 3.14f, StateValueTypeEnum.Float32); + reading.AddState(2, 42, StateValueTypeEnum.Int32); + reading.AddState(3, "Test String", StateValueTypeEnum.String); + reading.AddState(4, true, StateValueTypeEnum.Bool); + reading.AddState(6, (ushort)123, StateValueTypeEnum.UInt16); + reading.AddState(7, (short)-123, StateValueTypeEnum.Int16); + reading.AddState(8, (ulong)1234567890, StateValueTypeEnum.Timestamp); + reading.AddState(9, Encoding.UTF8.GetBytes("Binary"), StateValueTypeEnum.Binary); + reading.AddState(10, 3.1415926535, StateValueTypeEnum.Double); + }); + }); + + var message = builder.Build(); + + // Act + var arrayBufferWriter = new ArrayBufferWriter(); + _serializer.Serializer(arrayBufferWriter, message); + var bytes = arrayBufferWriter.WrittenSpan.ToArray(); + + var parsedMessage = _parser.Parser(bytes); + + // Assert + Assert.NotNull(parsedMessage); + var reading = parsedMessage.MainDevice.Reading.ReadingArray[0]; + Assert.Equal(9, reading.State.Count); + + _output.WriteLine($"Value types test completed successfully"); + _output.WriteLine($"Serialized size: {bytes.Length} bytes"); + } + + [Fact] + public void EmptyMessage_ShouldHandleGracefully() + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithHeader() + .WithMainDevice("", 0x00); // Empty device + + var message = builder.Build(); + + // Act + var arrayBufferWriter = new ArrayBufferWriter(); + _serializer.Serializer(arrayBufferWriter, message); + var bytes = arrayBufferWriter.WrittenSpan.ToArray(); + + var parsedMessage = _parser.Parser(bytes); + + // Assert + Assert.NotNull(parsedMessage); + Assert.Equal(string.Empty, parsedMessage.MainDevice.DID); + Assert.Equal(0, parsedMessage.MainDevice.DeviceType); + } + + [Fact] + public void MessageWithMultipleChildren_ShouldHandleCorrectly() + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithMainDevice("Main", 0x01) + .WithChildDevice("Child1", 0x02, config => + { + config.AddReading(100, reading => + { + reading.AddState(1, "Child1-Reading1", StateValueTypeEnum.String); + }); + }) + .WithChildDevice("Child2", 0x03, config => + { + config.AddReading(200, reading => + { + reading.AddState(1, "Child2-Reading1", StateValueTypeEnum.String); + }); + }) + .WithChildDevice("Child3", 0x04, config => + { + config.AddReading(300, reading => + { + reading.AddState(1, "Child3-Reading1", StateValueTypeEnum.String); + }); + }); + + var message = builder.Build(); + + // Act + var arrayBufferWriter = new ArrayBufferWriter(); + _serializer.Serializer(arrayBufferWriter, message); + var bytes = arrayBufferWriter.WrittenSpan.ToArray(); + + var parsedMessage = _parser.Parser(bytes); + + // Assert + Assert.NotNull(parsedMessage); + Assert.NotNull(parsedMessage.ChildDevice); + Assert.Equal(3, parsedMessage.ChildDevice.Count); + Assert.Equal("Child1", parsedMessage.ChildDevice.ChildArray[2].DID); + Assert.Equal("Child2", parsedMessage.ChildDevice.ChildArray[1].DID); + Assert.Equal("Child3", parsedMessage.ChildDevice.ChildArray[0].DID); + } + + [Fact] + public void MessageWithCRC_ShouldValidateCorrectly() + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithHeader(crcType: CRCTypeEnum.CRC16) + .WithMainDevice("TestDevice", 0x01, config => + { + config.AddReading(0, reading => + { + reading.AddState(1, "Test Value", StateValueTypeEnum.String); + }); + }); + + var message = builder.Build(); + + // Act - Serialize with CRC + var arrayBufferWriter = new ArrayBufferWriter(); + _serializer.Serializer(arrayBufferWriter, message); + var bytes = arrayBufferWriter.WrittenSpan.ToArray(); + + // Should not throw when CRC is valid + var parsedMessage = _parser.Parser(bytes); + + // Corrupt the CRC and verify it throws + bytes[bytes.Length - 1] ^= 0xFF; // Flip last byte to corrupt CRC + + // Assert + Assert.ThrowsAny(() => _parser.Parser(bytes)); + } + + [Theory] + [InlineData(CRCTypeEnum.None)] + [InlineData(CRCTypeEnum.CRC8)] + [InlineData(CRCTypeEnum.CRC16)] + [InlineData(CRCTypeEnum.CRC32)] + public void DifferentCRCTypes_ShouldWorkCorrectly(CRCTypeEnum crcType) + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithHeader(crcType: crcType) + .WithMainDevice("TestDevice", 0x01, config => + { + config.AddReading(0, reading => + { + reading.AddState(1, "Test Data", StateValueTypeEnum.String); + }); + }); + + var message = builder.Build(); + + // Act + var arrayBufferWriter = new ArrayBufferWriter(); + _serializer.Serializer(arrayBufferWriter, message); + var bytes = arrayBufferWriter.WrittenSpan.ToArray(); + + // Should not throw for any valid CRC type + var parsedMessage = _parser.Parser(bytes); + + // Assert + Assert.NotNull(parsedMessage); + Assert.Equal(crcType, parsedMessage.Header.CRCType); + } + + [Fact] + public void LargeMessage_ShouldHandleCorrectly() + { + // Arrange - Create a message with many readings and states + var builder = DeviceMessageBuilder.Create() + .WithMainDevice("LargeDevice", 0x01, config => + { + for (int i = 0; i < 50; i++) + { + config.AddReading((short)(i * 10), reading => + { + for (int j = 0; j < 10; j++) + { + reading.AddState((byte)j, $"Value{i}-{j}", StateValueTypeEnum.String); + } + }); + } + }); + + var message = builder.Build(); + + // Act + var arrayBufferWriter = new ArrayBufferWriter(); + _serializer.Serializer(arrayBufferWriter, message); + var bytes = arrayBufferWriter.WrittenSpan.ToArray(); + + var parsedMessage = _parser.Parser(bytes); + + // Assert + Assert.NotNull(parsedMessage); + Assert.Equal(50, parsedMessage.MainDevice.Reading.Count); + _output.WriteLine($"Large message size: {bytes.Length} bytes"); + } + + [Fact] + public void HexSerialization_ShouldWorkCorrectly() + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithMainDevice("HexTest", 0x01, config => + { + config.AddReading(100, reading => + { + reading.AddState(1, "Test Value", StateValueTypeEnum.String); + }); + }); + + // Act - Serialize to hex + var hexString = builder.BuildHex(); + + // Act - Parse from hex + var parsedMessage = _parser.Parser(hexString); + + // Assert + Assert.NotNull(parsedMessage); + Assert.Equal("HexTest", parsedMessage.MainDevice.DID); + _output.WriteLine($"Hex string: {hexString}"); + } + + [Fact] + public void Compression_ShouldReduceSize() + { + // Arrange + var builder = DeviceMessageBuilder.Create() + .WithMainDevice("CompressTest", 0x01, config => + { + for (int i = 0; i < 100; i++) + { + config.AddReading((short)(i * 10), reading => + { + for (int j = 0; j < 5; j++) + { + reading.AddState((byte)j, $"Long value data {i}-{j} that should compress well", StateValueTypeEnum.String); + } + }); + } + }); + + // Act - Serialize without compression + var normalHex = builder.BuildHex(compress: false); + + // Act - Serialize with compression + var compressedHex = builder.BuildHex(compress: true); + + // Assert + Assert.True(compressedHex.Length < normalHex.Length, + $"Compressed size ({compressedHex.Length}) should be smaller than normal size ({normalHex.Length})"); + + _output.WriteLine($"Normal: {normalHex.Length} chars"); + _output.WriteLine($"Compressed: {compressedHex.Length} chars"); + _output.WriteLine($"Ratio: {(double)compressedHex.Length / normalHex.Length:P}"); + } + } +} \ No newline at end of file diff --git a/TestProject1/TestProject1.csproj b/TestProject1/TestProject1.csproj new file mode 100644 index 0000000..18c97ee --- /dev/null +++ b/TestProject1/TestProject1.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + -- Gitee From c4199d4ed676f865c90f6daa5cf4ccb3e561e616 Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 26 Aug 2025 00:56:26 +0000 Subject: [PATCH 3/6] add README.md. Signed-off-by: Erol --- README.md | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..482162c --- /dev/null +++ b/README.md @@ -0,0 +1,389 @@ +# DeviceCommons + +#### 介绍 +DeviceCommons 是一个专为 IoT 场景设计的高性能设备消息处理库,提供完整的序列化/反序列化解决方案。支持多种数据类型、压缩加密、CRC校验,以及灵活的扩展结构,特别适合资源受限的嵌入式设备和高效的后端数据处理。 + +#### 消息整体结构 +DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组成: + + +``` ++-----------------------------------------------+ +| 设备消息 (DeviceMessage) | +| +-------------------------------------------+ | +| | 消息头 (Header) | | +| +-------------------------------------------+ | +| | 主设备 (MainDevice) | | +| | +---------------------------------------+ | | +| | | 设备信息 (DeviceInfo) | | | +| | | +-----------------------------------+ | | | +| | | | 读数集合 | | | | +| | | | +-------------------------------+ | | | | +| | | | | 读数数组 | | | | | +| | | | | +---------------------------+ | | | | | +| | | | | | 状态数组 | | | | | | +| | | | | | +-----------------------+ | | | | | | +| | | | | | | 状态值 | | | | | | | +| | | | | | +-----------------------+ | | | | | | +| | | | | +---------------------------+ | | | | | +| | | | +-------------------------------+ | | | | +| | | +-----------------------------------+ | | | +| | +---------------------------------------+ | | +| +-------------------------------------------+ | +| | 子设备集合 (ChildDevices) | | +| | +---------------------------------------+ | | +| | | 子设备信息数组 | | | +| | | +-----------------------------------+ | | | +| | | | 设备信息 (DeviceInfo) | | | | +| | | | (与主设备结构相同) | | | | +| | | +-----------------------------------+ | | | +| | +---------------------------------------+ | | +| +-------------------------------------------+ | ++-----------------------------------------------+ +``` + +#### 详细组件说明 + **1. 消息头 (DeviceMessageHeader)** +消息头包含协议的元数据信息: + +| 字段 | 类型 | 长度 | 描述 | +|-------------------|--------|-----|--------------------------------------| +| Header | byte[] | 2字节 | 固定头标识 (0xC0, 0xBF) | +| Version | byte | 1字节 | 协议版本号 | +| Mark | byte | 1字节 | 标志位组合字段 | +| └ TimeStampFormat | 位0 | 1位 | 时间戳格式 (0:毫秒, 1:秒) | +| └ ValueType | 位1 | 1位 | 值类型格式 (0:标准, 1:扩展) | +| └ Reserve1 | 位2 | 1位 | 保留位1 | +| └ Reserve2 | 位3 | 1位 | 保留位2 | +| └ CRCType | 位4-7 | 4位 | CRC校验类型 (0:无,1:CRC8,2:CRC16,3:CRC32) | + + **2. 设备信息 (DeviceMessageInfo)** +设备信息描述单个设备的基本属性和数据: +| 字段 | 类型 | 长度 | 描述 | +|------------|---------------------------|-----|-----------| +| Length | byte | 1字节 | 设备ID长度 | +| DIDBytes | byte[] | 变长 | 设备ID的字节表示 | +| DID | string | 变长 | 设备ID字符串 | +| DeviceType | byte | 1字节 | 设备类型标识 | +| Reading | DeviceMessageInfoReadings | 变长 | 设备读数集合 | + + **3. 读数集合 (DeviceMessageInfoReadings)** +读数集合包含设备的多个读数记录: +| 字段 | 类型 | 长度 | 描述 | +|--------------|----------------------------|-----|------| +| Count | byte | 1字节 | 读数数量 | +| ReadingArray | DeviceMessageInfoReading[] | 变长 | 读数数组 | + + **4. 单个读数 (DeviceMessageInfoReading)** +单个读数表示设备在特定时间点的数据记录: +| 字段 | 类型 | 长度 | 描述 | +|--------------|----------------------------|-----|------| +| Offset | byte[] | 2字节 | 时间偏移量 (大端序) | +| TimeOffset | short | 2字节 | 时间偏移量 (数值形式) | +| State | DeviceMessageInfoReadingStates | 变长 | 状态数据集合 | + + **5. 状态集合 (DeviceMessageInfoReadingStates)** +状态集合包含单个读数中的多个状态值: +| 字段 | 类型 | 长度 | 描述 | +|--------------|----------------------------|-----|------| +| Count | byte | 1字节 | 状态数量 | +| StateArray | DeviceMessageInfoReadingState[] | 变长 | 状态数组 | + + **6. 单个状态 (DeviceMessageInfoReadingState)** +单个状态表示设备的一个特定测量值或状态: +| 字段 | 类型 | 长度 | 描述 | +|-----------|--------------------|-----|----------| +| SID | byte | 1字节 | 状态标识符 | +| Type | byte | 1字节 | 值类型标识 | +| ValueType | StateValueTypeEnum | 1字节 | 值类型枚举 | +| Value | byte[] | 变长 | 值的字节表示 | +| ValueText | object | 变长 | 值的对象表示 | +| Metadata | string | 变长 | 元数据 (可选) | + +7. 子设备集合 (DeviceMessageChild) +子设备集合包含多个子设备信息: +| 字段 | 类型 | 长度 | 描述 | +|--------------|----------------------------|-----|------| +| Count | byte | 1字节 | 子设备数量 | +| ChildArray | DeviceMessageInfo[] | 变长 | 子设备数组 | + + **值类型支持** +DeviceCommons 支持多种数据类型: +| 值类型 | 枚举值 | 长度 | 描述 | +|-----------|-----|-----|------------------| +| Float32 | 1 | 4字节 | 单精度浮点数 | +| Int32 | 2 | 4字节 | 32位整数 | +| String | 3 | 变长 | 字符串 (长度前缀:1字节) | +| Bool | 4 | 1字节 | 布尔值 | +| UInt16 | 6 | 2字节 | 16位无符号整数 | +| Int16 | 7 | 2字节 | 16位有符号整数 | +| Timestamp | 8 | 8字节 | 时间戳 (64位) | +| Binary | 9 | 变长 | 二进制数据 (长度前缀:2字节) | +| Double | 10 | 8字节 | 双精度浮点数 | + +#### 消息序列化格式 +二进制格式布局 + +``` ++---------------------------------------------------+ +| 消息头 (4字节) | +| +-----------------------------------------------+ | +| | 固定头 (0xC0, 0xBF) (2字节) | | +| | 版本号 (1字节) | | +| | 标志位 (1字节) | | +| +-----------------------------------------------+ | +| 主设备数据 (变长) | +| +-----------------------------------------------+ | +| | 设备ID长度 (1字节) | | +| | 设备ID (变长) | | +| | 设备类型 (1字节) | | +| | 读数集合 (变长) | | +| | +-------------------------------------------+ | | +| | | 读数数量 (1字节) | | | +| | | 读数数组 (变长) | | | +| | | +---------------------------------------+ | | | +| | | | 时间偏移 (2字节) | | | | +| | | | 状态集合 (变长) | | | | +| | | | +-----------------------------------+ | | | | +| | | | | 状态数量 (1字节) | | | | | +| | | | | 状态数组 (变长) | | | | | +| | | | | +-------------------------------+ | | | | | +| | | | | | 状态ID (1字节) | | | | | | +| | | | | | 值类型 (1字节) | | | | | | +| | | | | | 值数据 (变长) | | | | | | +| | | | | +-------------------------------+ | | | | | +| | | | +-----------------------------------+ | | | | +| | | +---------------------------------------+ | | | +| | +-------------------------------------------+ | | +| +-----------------------------------------------+ | +| 子设备集合 (变长) | +| +-----------------------------------------------+ | +| | 子设备数量 (1字节) | | +| | 子设备数组 (变长) | | +| | +-------------------------------------------+ | | +| | | 设备信息 (与主设备结构相同) | | | +| | +-------------------------------------------+ | | +| +-----------------------------------------------+ | +| CRC校验码 (变长,取决于CRC类型) | ++---------------------------------------------------+ +``` + +#### 十六进制字符串格式 +当使用十六进制字符串格式时,消息可以包含压缩和加密信息: + +``` +[加密状态],[压缩状态]|[十六进制数据] +``` + +示例: + +``` +dec,raw|C0BF01054D61696E... // 未加密未压缩 +enc,gzip|1F8B080000000000... // 加密并压缩 +``` + + + +#### 软件架构 +DeviceCommons 采用分层架构设计: + + **1.核心层 (Core)** + +- 基础数据类型定义 (Enums) +- 消息模型 (Models) +- 工具类和扩展方法 + + + **2.序列化层 (Serialization)** + +- 解析器体系 (Parsers) +- 序列化器体系 (Serializers) +- 支持版本化管理 + + + **3.安全层 (Security)** + +- CRC 校验计算 +- AES 加密/解密 +- 可扩展的加密接口 + + + **4.工具层 (Utilities)** + +- 内存池和对象池 +- 压缩/解压缩 +- 十六进制转换 + + + **5.构建层 (Builders)** + +- 流畅接口构建复杂消息 +- 工厂模式创建特定类型对象 + + +#### 安装教程 + + **1. 通过 NuGet 安装** + +``` +dotnet add package DeviceCommons +``` + + **2. 手动编译安装** + +``` +git clone https://gitee.com/ruan-yong/device-commons.git +cd DeviceCommons +dotnet build +``` + + **3. 项目引用** +- 在您的项目中添加对 DeviceCommons.dll 的引用 + + +#### 使用说明 + + **1. 基本用法** + +``` +// 创建简单的设备消息 +var message = DeviceMessageBuilder.Create() + .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC16) + .WithMainDevice("device-001", 0x01, config => + { + config.AddReading(100, reading => + { + reading.AddState(1, 25.5f, StateValueTypeEnum.Float32); + reading.AddState(2, "正常运行", StateValueTypeEnum.String); + reading.AddState(3, true, StateValueTypeEnum.Bool); + }); + }) + .Build(); + +// 序列化为字节数组 +var bytes = message.BuildBytes(); + +// 序列化为十六进制字符串(带压缩和加密) +var hexString = message.BuildHex(compress: true, encrypt: true); +``` + + **2. 高级功能** + +``` +// 使用AES加密 +var message = DeviceMessageBuilder.Create() + .WithAesEncryption("your-secret-password") + .WithMainDevice("secure-device", 0x01) + .BuildHex(compress: true, encrypt: true); + +// 自定义加密算法 +var message = DeviceMessageBuilder.Create() + .WithEncryptFunc(myEncryptFunction) + .WithDecryptFunc(myDecryptFunction) + .WithMainDevice("custom-crypto-device", 0x01) + .BuildHex(encrypt: true); + +// 注册自定义设备类型处理 +StateFactoryRegistry.RegisterFactory(0x99, () => new CustomStateFactory()); +``` + + **3. 解析消息** + +``` +// 从字节数组解析 +var parser = new DeviceMessageParser(); +var message = parser.Parser(bytes); + +// 从十六进制字符串解析(支持自动解压和解密) +var message = parser.Parser(hexString); + +// 访问解析后的数据 +var deviceId = message.MainDevice.DID; +var temperature = message.MainDevice.Reading.ReadingArray[0].State.StateArray[0].ValueText; +``` + +#### 典型消息示例 +简单温度传感器消息 + +``` +var message = DeviceMessageBuilder.Create() + .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC16) + .WithMainDevice("temp-sensor-001", 0x20, config => + { + config.AddReading(0, reading => + { + reading.AddState(1, 25.3f, StateValueTypeEnum.Float32); // 温度 + reading.AddState(2, 45.0f, StateValueTypeEnum.Float32); // 湿度 + reading.AddState(3, true, StateValueTypeEnum.Bool); // 在线状态 + }); + }) + .Build(); +``` +复杂多设备消息 + +``` +var message = DeviceMessageBuilder.Create() + .WithHeader(version: 0x01, crcType: CRCTypeEnum.CRC32) + .WithMainDevice("gateway-001", 0x10, config => + { + config.AddReading(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), reading => + { + reading.AddState(1, "正常运行", StateValueTypeEnum.String); + reading.AddState(2, 85.5f, StateValueTypeEnum.Float32); // CPU使用率 + }); + }) + .WithChildDevice("sensor-001", 0x20, config => + { + config.AddReading(100, reading => + { + reading.AddState(1, 23.5f, StateValueTypeEnum.Float32); // 温度 + reading.AddState(2, 1024.5f, StateValueTypeEnum.Float32); // 气压 + }); + }) + .WithChildDevice("sensor-002", 0x20, config => + { + config.AddReading(100, reading => + { + reading.AddState(1, 65.2f, StateValueTypeEnum.Float32); // 湿度 + reading.AddState(2, 120.0f, StateValueTypeEnum.Float32); // 光照 + }); + }) + .Build(); +``` + +#### 扩展性设计 +DeviceCommons 的消息结构设计具有良好的扩展性: + +1. 版本兼容:通过版本号字段支持协议演进 +2. 保留字段:预留位和保留字段用于未来扩展 +3. 设备类型注册:通过工厂模式支持自定义设备类型处理 +4. 值类型扩展:值类型枚举可扩展新的数据类型 +5. 元数据支持:状态值可包含额外的元数据信息 + +#### 贡献指南: + +- 遵循现有的代码风格和命名约定 +- 添加适当的单元测试 +- 更新相关文档 +- 确保所有测试通过 + + + +#### 特技 + +1. 🌟 **高性能设计:** 使用 ArrayPool 和 MemoryPool 优化内存使用,减少GC压力 +2. 🔒 **多重安全:** 支持CRC校验、AES加密和自定义加密算法 +3. 📦 **智能压缩:** 自动判断何时使用压缩优化数据传输 +4. 🔧 **灵活扩展:** 通过工厂模式支持自定义设备类型处理 +5. ⚡ **异步支持:** 所有操作都提供异步版本,适合高并发场景 +6. 🌐 **多格式支持:** 支持二进制、十六进制字符串多种数据格式 +7. 🧩 **模块化设计:** 各组件解耦,可按需使用部分功能 + + **性能基准** +| **操作类型** | **数据大小** | **平均耗时** | **内存分配** | +|---------|------|------|------| +| 简单消息序列化 | | | | +| 复杂消息序列化 | | | | +| 带压缩序列化 | | | | +| 消息解析 | | | | + -- Gitee From c107e2c3600a0f7ea27a5fd1eb58f122c5b4515d Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 26 Aug 2025 00:58:56 +0000 Subject: [PATCH 4/6] update README.md. Signed-off-by: Erol --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 482162c..0417eff 100644 --- a/README.md +++ b/README.md @@ -102,9 +102,9 @@ DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组 7. 子设备集合 (DeviceMessageChild) 子设备集合包含多个子设备信息: -| 字段 | 类型 | 长度 | 描述 | -|--------------|----------------------------|-----|------| -| Count | byte | 1字节 | 子设备数量 | +| 字段 | 类型 | 长度 | 描述 | +|------------|---------------------|-----|-------| +| Count | byte | 1字节 | 子设备数量 | | ChildArray | DeviceMessageInfo[] | 变长 | 子设备数组 | **值类型支持** -- Gitee From 34bf7cdf0f7cbff4662f242bf10d689b20c757fa Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 26 Aug 2025 01:00:08 +0000 Subject: [PATCH 5/6] update README.md. Signed-off-by: Erol --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0417eff..06b4845 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组 | Metadata | string | 变长 | 元数据 (可选) | 7. 子设备集合 (DeviceMessageChild) + 子设备集合包含多个子设备信息: | 字段 | 类型 | 长度 | 描述 | |------------|---------------------|-----|-------| -- Gitee From 0988ce0cbf145dc7f0f47dceacf78c34cafe1e79 Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 26 Aug 2025 01:28:20 +0000 Subject: [PATCH 6/6] update README.md. Signed-off-by: Erol --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 06b4845..d0d277b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组 #### 详细组件说明 **1. 消息头 (DeviceMessageHeader)** + 消息头包含协议的元数据信息: | 字段 | 类型 | 长度 | 描述 | @@ -58,7 +59,9 @@ DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组 | └ CRCType | 位4-7 | 4位 | CRC校验类型 (0:无,1:CRC8,2:CRC16,3:CRC32) | **2. 设备信息 (DeviceMessageInfo)** + 设备信息描述单个设备的基本属性和数据: + | 字段 | 类型 | 长度 | 描述 | |------------|---------------------------|-----|-----------| | Length | byte | 1字节 | 设备ID长度 | @@ -68,14 +71,18 @@ DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组 | Reading | DeviceMessageInfoReadings | 变长 | 设备读数集合 | **3. 读数集合 (DeviceMessageInfoReadings)** + 读数集合包含设备的多个读数记录: + | 字段 | 类型 | 长度 | 描述 | |--------------|----------------------------|-----|------| | Count | byte | 1字节 | 读数数量 | | ReadingArray | DeviceMessageInfoReading[] | 变长 | 读数数组 | **4. 单个读数 (DeviceMessageInfoReading)** + 单个读数表示设备在特定时间点的数据记录: + | 字段 | 类型 | 长度 | 描述 | |--------------|----------------------------|-----|------| | Offset | byte[] | 2字节 | 时间偏移量 (大端序) | @@ -83,14 +90,18 @@ DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组 | State | DeviceMessageInfoReadingStates | 变长 | 状态数据集合 | **5. 状态集合 (DeviceMessageInfoReadingStates)** + 状态集合包含单个读数中的多个状态值: + | 字段 | 类型 | 长度 | 描述 | |--------------|----------------------------|-----|------| | Count | byte | 1字节 | 状态数量 | | StateArray | DeviceMessageInfoReadingState[] | 变长 | 状态数组 | **6. 单个状态 (DeviceMessageInfoReadingState)** + 单个状态表示设备的一个特定测量值或状态: + | 字段 | 类型 | 长度 | 描述 | |-----------|--------------------|-----|----------| | SID | byte | 1字节 | 状态标识符 | @@ -103,13 +114,16 @@ DeviceCommons 采用分层结构设计,完整的设备消息由以下部分组 7. 子设备集合 (DeviceMessageChild) 子设备集合包含多个子设备信息: + | 字段 | 类型 | 长度 | 描述 | |------------|---------------------|-----|-------| | Count | byte | 1字节 | 子设备数量 | | ChildArray | DeviceMessageInfo[] | 变长 | 子设备数组 | **值类型支持** + DeviceCommons 支持多种数据类型: + | 值类型 | 枚举值 | 长度 | 描述 | |-----------|-----|-----|------------------| | Float32 | 1 | 4字节 | 单精度浮点数 | @@ -381,6 +395,7 @@ DeviceCommons 的消息结构设计具有良好的扩展性: 7. 🧩 **模块化设计:** 各组件解耦,可按需使用部分功能 **性能基准** + | **操作类型** | **数据大小** | **平均耗时** | **内存分配** | |---------|------|------|------| | 简单消息序列化 | | | | -- Gitee