From 0b4798afa17618372e2c969a7f26970b5d8392cf Mon Sep 17 00:00:00 2001 From: Johannes Kauffmann Date: Mon, 9 Aug 2021 14:25:51 +0200 Subject: [PATCH 1/7] feat: Add ANSI Extended Symbol Segment support Added ISegment and DataSegment base classes, and concrete ANSI Extented Symbol Segment implementation. ISegment: - add: Abstract base class which provides segment types and methods to concrete segments. DataSegment: - add: Abstract class, extends from ISegment. Provides DataSegment subtypes to concrete DataSegment implementations. ANSISegment: - Add: Concrete DataSegment implementation, provides functionality for encoding ANSI data. EPath: - add: Constructor which takes a collection of segments to be requested. - add: member which stores the segment collection. - chg: packPaddedPath() and getSizeInWords() check to see if the segment member is empty. If not, packPaddedPath() encodes the segment collection instead of class/object/attribute id, and getSizeInWords() calculates the size of the segment collection instead of the id's. --- src/CMakeLists.txt | 4 +++ src/cip/EPath.cpp | 29 +++++++++++++++ src/cip/EPath.h | 7 ++-- src/cip/segments/ANSISegment.cpp | 55 ++++++++++++++++++++++++++++ src/cip/segments/ANSISegment.h | 45 +++++++++++++++++++++++ src/cip/segments/DataSegment.cpp | 17 +++++++++ src/cip/segments/DataSegment.h | 37 +++++++++++++++++++ src/cip/segments/ISegment.cpp | 17 +++++++++ src/cip/segments/ISegment.h | 62 ++++++++++++++++++++++++++++++++ 9 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 src/cip/segments/ANSISegment.cpp create mode 100644 src/cip/segments/ANSISegment.h create mode 100644 src/cip/segments/DataSegment.cpp create mode 100644 src/cip/segments/DataSegment.h create mode 100644 src/cip/segments/ISegment.cpp create mode 100644 src/cip/segments/ISegment.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6a58194..9c71486 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,10 @@ set(SOURCE_FILES cip/connectionManager/ForwardOpenResponse.cpp cip/connectionManager/NetworkConnectionParametersBuilder.cpp + cip/segments/ANSISegment.cpp + cip/segments/DataSegment.cpp + cip/segments/ISegment.cpp + cip/CipRevision.cpp cip/EPath.cpp cip/MessageRouterRequest.cpp diff --git a/src/cip/EPath.cpp b/src/cip/EPath.cpp index 447ef30..ede1f37 100644 --- a/src/cip/EPath.cpp +++ b/src/cip/EPath.cpp @@ -7,6 +7,7 @@ namespace eipScanner { namespace cip { + using segments::ISegment; using utils::Buffer; enum class EPathSegmentTypes : CipUsint { @@ -49,7 +50,24 @@ namespace cip { , _size{3} { } + EPath::EPath(const std::vector &segments) + : _segments(segments) { + } + std::vector EPath::packPaddedPath(bool use_8_bit_path_segments) const { + + if (!_segments.empty()) { + + Buffer buffer; + + // Append all encoded segment data together. + for (ISegment::SPtr segment : _segments) { + buffer << segment->encode(); + } + + return buffer.data(); + } + if (use_8_bit_path_segments) { Buffer buffer(_size*2); @@ -103,6 +121,17 @@ namespace cip { } CipUsint EPath::getSizeInWords(bool use_8_bit_path_segments) const { + + if (!_segments.empty()) { + CipUsint size = 0; + + for (ISegment::SPtr segment : _segments) { + size += segment->getSize(); + } + + return size / 2; + } + if (use_8_bit_path_segments) { return _size; } diff --git a/src/cip/EPath.h b/src/cip/EPath.h index d2270b6..7029d07 100644 --- a/src/cip/EPath.h +++ b/src/cip/EPath.h @@ -5,11 +5,12 @@ #ifndef EIPSCANNER_CIP_EPATH_H #define EIPSCANNER_CIP_EPATH_H -#include -#include #include +#include +#include #include "Types.h" +#include "segments/ISegment.h" namespace eipScanner { namespace cip { @@ -19,6 +20,7 @@ namespace cip { explicit EPath(CipUint classId); EPath(CipUint classId, CipUint objectId); EPath(CipUint classId, CipUint objectId, CipUint attributeId); + EPath(const std::vector &segments); std::vector packPaddedPath(bool use_8_bit_path_segments=false) const; void expandPaddedPath(const std::vector& data); @@ -35,6 +37,7 @@ namespace cip { CipUint _objectId; CipUint _attributeId; CipUsint _size; + std::vector _segments; }; } } diff --git a/src/cip/segments/ANSISegment.cpp b/src/cip/segments/ANSISegment.cpp new file mode 100644 index 0000000..a4d1096 --- /dev/null +++ b/src/cip/segments/ANSISegment.cpp @@ -0,0 +1,55 @@ +// +// Created by Johannes Kauffmann on 08/06/21. +// + +#include + +#include "ANSISegment.h" +#include "utils/Buffer.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + using eipScanner::cip::segments::DataSegment; + using eipScanner::utils::Buffer; + + ANSISegment::ANSISegment(const std::vector& data) + : DataSegment(data) { + } + + std::vector ANSISegment::encode() const + { + Buffer buffer; + + // Stitch header together from the segment type and segment subtype. + CipUsint header = static_cast(SegmentType::DATA_SEGMENT) + | static_cast(SubType::ANSI_EXTENDED_SYMBOL_SEGMENT); + + // Add the header, symbol size and data. + buffer << header << static_cast(_data.size()) << _data; + + // Add optional padding if the size is of odd length. + if (_data.size() % 2 != 0) { + buffer << static_cast(0x00); + } + + return buffer.data(); + } + + uint8_t ANSISegment::getSize() const + { + // Size is amount of chars + header and symbol length. + uint8_t size = _data.size() + 2; + + // Check for padding. + if (size % 2 != 0) { + size++; + } + + return size; + } + +} +} +} diff --git a/src/cip/segments/ANSISegment.h b/src/cip/segments/ANSISegment.h new file mode 100644 index 0000000..5051751 --- /dev/null +++ b/src/cip/segments/ANSISegment.h @@ -0,0 +1,45 @@ +// +// Created by Johannes Kauffmann on 08/06/21. +// + +#ifndef EIPSCANNER_CIP_SEGMENTS_ANSISEGMENT_H +#define EIPSCANNER_CIP_SEGMENTS_ANSISEGMENT_H + +#include + +#include "cip/CipString.h" +#include "DataSegment.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + class ANSISegment final : public DataSegment { + public: + + /** + * @brief Constructs a CIP DataSegment of type ANSI Extended Symbol Segment. + * @param data The segment data, excluding details such as length or padding + */ + ANSISegment(const std::vector& data); + + /** + * @brief Encodes the segment data with the header, symbol length and data + * with possible padding included. + * @return The encoded segment data + */ + std::vector encode() const override; + + /** + * @brief Calculates the size of the segment in bytes, including the + * header, symbol length and possible padding + * @return The size of the segment in bytes + */ + uint8_t getSize() const override; + }; + +} +} +} + +#endif // EIPSCANNER_CIP_SEGMENTS_ANSISEGMENT_H diff --git a/src/cip/segments/DataSegment.cpp b/src/cip/segments/DataSegment.cpp new file mode 100644 index 0000000..6876a96 --- /dev/null +++ b/src/cip/segments/DataSegment.cpp @@ -0,0 +1,17 @@ +// +// Created by Johannes Kauffmann on 08/06/21. +// + +#include "DataSegment.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + DataSegment::DataSegment(const std::vector &data) + : ISegment(data) { + } + +} +} +} diff --git a/src/cip/segments/DataSegment.h b/src/cip/segments/DataSegment.h new file mode 100644 index 0000000..af2c7e5 --- /dev/null +++ b/src/cip/segments/DataSegment.h @@ -0,0 +1,37 @@ +// +// Created by Johannes Kauffmann on 08/06/21. +// + +#ifndef EIPSCANNER_CIP_SEGMENTS_DATASEGMENT_H +#define EIPSCANNER_CIP_SEGMENTS_DATASEGMENT_H + +#include + +#include "cip/Types.h" +#include "ISegment.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + class DataSegment : public ISegment { + protected: + + /** + * @brief The SubType enum indicates the possible data segment subtypes + * which encompass the 5 lower bits of the segment header, + */ + enum class SubType : CipUsint { + SIMPLE_DATA_SEGMENT = 0x00, + ANSI_EXTENDED_SYMBOL_SEGMENT = 0x11 + }; + + protected: + DataSegment(const std::vector &data); + }; + +} +} +} + +#endif // EIPSCANNER_CIP_SEGMENTS_DATASEGMENT_H diff --git a/src/cip/segments/ISegment.cpp b/src/cip/segments/ISegment.cpp new file mode 100644 index 0000000..41841f5 --- /dev/null +++ b/src/cip/segments/ISegment.cpp @@ -0,0 +1,17 @@ +// +// Created by Johannes Kauffmann on 08/06/21. +// + +#include "ISegment.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + ISegment::ISegment(const std::vector &data) : + _data(data) { + } + +} +} +} diff --git a/src/cip/segments/ISegment.h b/src/cip/segments/ISegment.h new file mode 100644 index 0000000..13025e6 --- /dev/null +++ b/src/cip/segments/ISegment.h @@ -0,0 +1,62 @@ +// +// Created by Johannes Kauffmann on 08/06/21. +// + +#ifndef EIPSCANNER_CIP_SEGMENTS_ISEGMENT_H +#define EIPSCANNER_CIP_SEGMENTS_ISEGMENT_H + +#include +#include +#include + +#include "cip/Types.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + class ISegment { + public: + using SPtr = std::shared_ptr; + + /** + * @brief Encodes the segment data + * @return The encoded segment data + */ + virtual std::vector encode() const = 0; + + /** + * @brief Calculates the size of the segment in bytes + * @return The size of the segment in bytes + */ + virtual uint8_t getSize() const = 0; + + protected: + + /** + * @brief The SegmentType enum encompasses the three most significant bits + * of the segment header + */ + enum class SegmentType : CipUsint { + PORT_SEGMENT = 0x00, + LOGICAL_SEGMENT = 0x20, + NETWORK_SEGMENT = 0x40, + SYMBOLIC_SEGMENT = 0x60, + DATA_SEGMENT = 0x80, + DATA_TYPE_CONSTRUCTED = 0xA0, + DATA_TYPE_ELEMENTARY = 0xC0, + RESERVED = 0xE0 + }; + + protected: + ISegment(const std::vector &data); + + protected: + std::vector _data; + }; + +} +} +} + +#endif // EIPSCANNER_CIP_SEGMENTS_ISEGMENT_H From 78ee9d49d9fbc0477ee41ae019de7a8d7e5fc1f7 Mon Sep 17 00:00:00 2001 From: Johannes Kauffmann Date: Wed, 11 Aug 2021 13:29:11 +0200 Subject: [PATCH 2/7] feat: Add MemberID segment support {ANSI,Data}Segment/EPath: - Fixed some stylistic issues LogicalSegment: - Added LogicalSegment abstract class which inherits from ISegment and contains enums for the logical type bits and logical format bits. MemberIDSegment: - Added concrete MemberIDSegment class which inherits from LogicalSegment and implements encoding a Member ID logical value as either 8bit or 16bit. --- src/CMakeLists.txt | 2 + src/cip/EPath.cpp | 4 +- src/cip/segments/ANSISegment.h | 2 +- src/cip/segments/DataSegment.h | 2 +- src/cip/segments/LogicalSegment.cpp | 19 ++++++++ src/cip/segments/LogicalSegment.h | 58 ++++++++++++++++++++++++ src/cip/segments/MemberIDSegment.cpp | 68 ++++++++++++++++++++++++++++ src/cip/segments/MemberIDSegment.h | 46 +++++++++++++++++++ 8 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 src/cip/segments/LogicalSegment.cpp create mode 100644 src/cip/segments/LogicalSegment.h create mode 100644 src/cip/segments/MemberIDSegment.cpp create mode 100644 src/cip/segments/MemberIDSegment.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c71486..045cbb3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,8 @@ set(SOURCE_FILES cip/segments/ANSISegment.cpp cip/segments/DataSegment.cpp cip/segments/ISegment.cpp + cip/segments/LogicalSegment.cpp + cip/segments/MemberIDSegment.cpp cip/CipRevision.cpp cip/EPath.cpp diff --git a/src/cip/EPath.cpp b/src/cip/EPath.cpp index ede1f37..95764aa 100644 --- a/src/cip/EPath.cpp +++ b/src/cip/EPath.cpp @@ -48,10 +48,12 @@ namespace cip { , _objectId{objectId} , _attributeId{attributeId} , _size{3} { + } EPath::EPath(const std::vector &segments) - : _segments(segments) { + : _segments{segments} { + } std::vector EPath::packPaddedPath(bool use_8_bit_path_segments) const { diff --git a/src/cip/segments/ANSISegment.h b/src/cip/segments/ANSISegment.h index 5051751..e5e7b7c 100644 --- a/src/cip/segments/ANSISegment.h +++ b/src/cip/segments/ANSISegment.h @@ -25,7 +25,7 @@ namespace segments { /** * @brief Encodes the segment data with the header, symbol length and data - * with possible padding included. + * with possible padding included * @return The encoded segment data */ std::vector encode() const override; diff --git a/src/cip/segments/DataSegment.h b/src/cip/segments/DataSegment.h index af2c7e5..1a9caa6 100644 --- a/src/cip/segments/DataSegment.h +++ b/src/cip/segments/DataSegment.h @@ -19,7 +19,7 @@ namespace segments { /** * @brief The SubType enum indicates the possible data segment subtypes - * which encompass the 5 lower bits of the segment header, + * which encompass the 5 lower bits of the segment header */ enum class SubType : CipUsint { SIMPLE_DATA_SEGMENT = 0x00, diff --git a/src/cip/segments/LogicalSegment.cpp b/src/cip/segments/LogicalSegment.cpp new file mode 100644 index 0000000..1df1ff7 --- /dev/null +++ b/src/cip/segments/LogicalSegment.cpp @@ -0,0 +1,19 @@ +// +// Created by Johannes Kauffmann on 08/09/21. +// + +#include "LogicalSegment.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + LogicalSegment::LogicalSegment(const std::vector &value, LogicalFormat format) + : ISegment (value) + ,_format (format) { + + } + +} +} +} diff --git a/src/cip/segments/LogicalSegment.h b/src/cip/segments/LogicalSegment.h new file mode 100644 index 0000000..0910106 --- /dev/null +++ b/src/cip/segments/LogicalSegment.h @@ -0,0 +1,58 @@ +// +// Created by Johannes Kauffmann on 08/09/21. +// + +#ifndef EIPSCANNER_CIP_SEGMENTS_LOGICALSEGMENT_H +#define EIPSCANNER_CIP_SEGMENTS_LOGICALSEGMENT_H + +#include + +#include "ISegment.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + class LogicalSegment : public ISegment { + protected: + + /** + * @brief The LogicalType enum indicates the possible logical types which + * encompass the most significant three bits of the segment format bits + */ + enum class LogicalType : CipUsint { + CLASS_ID = 0x00, + INSTANCE_ID = 0x04, + MEMBER_ID = 0x08, + CONNECTION_POINT = 0x0C, + ATTRIBUTE_ID = 0x10, + SPECIAL = 0x14, + SERVICE_ID = 0x18, + RESERVED = 0x1C + }; + + /** + * @brief The LogicalFormat enum indicates the possible logical formats + * which encompass the two least significant bits of the segment format + * bits + * @note Not all logical segments use these formats. The 32bit logical + * format is only allowed for the Instance ID and Connection Point types + */ + enum class LogicalFormat : CipUsint { + FORMAT_8_BIT = 0x00, + FORMAT_16_BIT = 0x01, + FORMAT_32_BIT = 0x02 + }; + + protected: + LogicalSegment(const std::vector &value, LogicalFormat format); + + protected: + LogicalFormat _format; + }; + +} +} +} + +#endif // EIPSCANNER_CIP_SEGMENTS_LOGICALSEGMENT_H diff --git a/src/cip/segments/MemberIDSegment.cpp b/src/cip/segments/MemberIDSegment.cpp new file mode 100644 index 0000000..226daaf --- /dev/null +++ b/src/cip/segments/MemberIDSegment.cpp @@ -0,0 +1,68 @@ +// +// Created by Johannes Kauffmann on 08/09/21. +// + +#include "MemberIDSegment.h" +#include "utils/Buffer.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + using utils::Buffer; + + MemberIDSegment::MemberIDSegment(CipUint memberId, bool use_8_bits) + : LogicalSegment({}, LogicalFormat::FORMAT_16_BIT) + { + if (use_8_bits) { + _format = LogicalFormat::FORMAT_16_BIT; + } + + // Convert MemberID to uint8_t or two bytes of uint16_t + Buffer buffer; + + if (_format == LogicalFormat::FORMAT_8_BIT) { + buffer << static_cast(memberId); + } else { + buffer << memberId; + } + + _data = buffer.data(); + } + + std::vector MemberIDSegment::encode() const + { + Buffer buffer; + + // The segment header consists of the the segment type, logical type and logical format + CipUsint header = static_cast(SegmentType::LOGICAL_SEGMENT) + | static_cast(LogicalType::MEMBER_ID) + | static_cast(_format); + + buffer << header; + + // Check for padding + if (_format == LogicalFormat::FORMAT_16_BIT) { + buffer << static_cast(0x00); + } + + buffer << _data; + + return buffer.data(); + } + + uint8_t MemberIDSegment::getSize() const + { + uint8_t size = _data.size() + 1; + + // Check for padding + if (_format == LogicalFormat::FORMAT_16_BIT) { + size++; + } + + return size; + } + +} +} +} diff --git a/src/cip/segments/MemberIDSegment.h b/src/cip/segments/MemberIDSegment.h new file mode 100644 index 0000000..3e39a80 --- /dev/null +++ b/src/cip/segments/MemberIDSegment.h @@ -0,0 +1,46 @@ +// +// Created by Johannes Kauffmann on 08/09/21. +// + +#ifndef EIPSCANNER_CIP_SEGMENTS_MEMBERIDSEGMENT_H +#define EIPSCANNER_CIP_SEGMENTS_MEMBERIDSEGMENT_H + +#include + +#include "LogicalSegment.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + class MemberIDSegment final : public LogicalSegment { + public: + + /** + * @brief Constructs a Logical Segment of Logical Type MemberID + * @param memberId The logical value of the Member ID + * @param use_8_bit_segment True if the segment format should be 8bit + * format, false if format should be 16bit format + */ + MemberIDSegment(CipUint memberId, bool use_8_bit_segment=false); + + /** + * @brief Encodes the MemberID Logical Segment with the correct segment + * header, possible padding and little endian encoded data + * @return Vector of bytes with the complete segment encoded + */ + std::vector encode() const override; + + /** + * @brief Calculates the size of the complete MemberID Logical Segment in + * bytes + * @return The size of the complete segment in bytes + */ + uint8_t getSize() const override; + }; + +} +} +} + +#endif // EIPSCANNER_CIP_SEGMENTS_MEMBERIDSEGMENT_H From a12d2ecd59a1fc1ac8c9d5b140a303a9e1e73b9c Mon Sep 17 00:00:00 2001 From: Johannes Kauffmann Date: Thu, 12 Aug 2021 11:02:00 +0200 Subject: [PATCH 3/7] fix/refactor/docs: Fixed small issues, added docs EPath: - add: Added documentation of functions to header file. - chg: toString() now prints all segments when needed. - chg: expandPaddedPath doesn't try to decode the given vector when the EPath consists of a collection of segments. - fix: Initialize all member variables in every constructor. ISegment: - add: virtual function to calculate the segment header, and virtual function to stringify the segment data. MemberIDSegment: - fix: Previously, the 16bit format was set in the constructor, even when the 8bit format was indicated. - impl: getSegmentHeader() and toString() function. ANSISegment: - impl: getSegmentHeader() and toString() function. --- src/cip/EPath.cpp | 45 ++++++++++++++++++---- src/cip/EPath.h | 56 ++++++++++++++++++++++++++++ src/cip/segments/ANSISegment.cpp | 16 ++++++-- src/cip/segments/ANSISegment.h | 15 ++++++++ src/cip/segments/ISegment.h | 13 +++++++ src/cip/segments/MemberIDSegment.cpp | 42 ++++++++++++++++++--- src/cip/segments/MemberIDSegment.h | 14 +++++++ 7 files changed, 185 insertions(+), 16 deletions(-) diff --git a/src/cip/EPath.cpp b/src/cip/EPath.cpp index 95764aa..31b00f5 100644 --- a/src/cip/EPath.cpp +++ b/src/cip/EPath.cpp @@ -1,14 +1,17 @@ // // Created by Aleksey Timin on 11/16/19. // -#include + #include "utils/Buffer.h" +#include "utils/Logger.h" #include "EPath.h" namespace eipScanner { namespace cip { using segments::ISegment; using utils::Buffer; + using utils::Logger; + using utils::LogLevel; enum class EPathSegmentTypes : CipUsint { CLASS_8_BITS = 0x20, @@ -23,7 +26,8 @@ namespace cip { : _classId{0} , _objectId{0} , _attributeId{0} - , _size{0}{ + , _size{0} + , _segments{} { } @@ -31,7 +35,8 @@ namespace cip { : _classId{classId} , _objectId{0} , _attributeId{0} - , _size{1} { + , _size{1} + , _segments{} { } @@ -39,7 +44,8 @@ namespace cip { : _classId{classId} , _objectId{objectId} , _attributeId{0} - , _size{2} { + , _size{2} + , _segments{} { } @@ -47,17 +53,21 @@ namespace cip { : _classId{classId} , _objectId{objectId} , _attributeId{attributeId} - , _size{3} { + , _size{3} + , _segments{} { } EPath::EPath(const std::vector &segments) - : _segments{segments} { + : _classId{0} + , _objectId{0} + , _attributeId{0} + , _size{0} + , _segments{segments} { } std::vector EPath::packPaddedPath(bool use_8_bit_path_segments) const { - if (!_segments.empty()) { Buffer buffer; @@ -123,7 +133,6 @@ namespace cip { } CipUsint EPath::getSizeInWords(bool use_8_bit_path_segments) const { - if (!_segments.empty()) { CipUsint size = 0; @@ -143,6 +152,19 @@ namespace cip { } std::string EPath::toString() const { + if (!_segments.empty()) { + std::stringstream stream; + stream << "[ "; + + for (ISegment::SPtr segment : _segments) { + stream << "[header=0x" << std::hex << int(segment->getSegmentHeader()) << std::dec; + stream << ";value=" << segment->toString() << "]"; + } + + stream << " ]"; + return stream.str(); + } + std::string msg = "[classId=" + std::to_string(_classId); if (_size > 1) { msg += " objectId=" + std::to_string(_objectId); @@ -156,6 +178,13 @@ namespace cip { } void EPath::expandPaddedPath(const std::vector &data) { + + if (!_segments.empty()) { + Logger(LogLevel::WARNING) << "EPath already contains a collection of segments!" + "Not expanding data!"; + return; + } + Buffer buffer(data); _classId = 0; diff --git a/src/cip/EPath.h b/src/cip/EPath.h index 7029d07..ff380fa 100644 --- a/src/cip/EPath.h +++ b/src/cip/EPath.h @@ -20,16 +20,72 @@ namespace cip { explicit EPath(CipUint classId); EPath(CipUint classId, CipUint objectId); EPath(CipUint classId, CipUint objectId, CipUint attributeId); + + /** + * @brief Constructs an EPath based on a collection of segments + * @param segments Ordered collection of segments which the EPath should + * consist of + */ EPath(const std::vector &segments); + + /** + * @brief packPaddedPath Either encodes the class/object/attribute id + * values to the corresponding logical segments, or asks all segments in + * the segment collection to encode themselves + * @param use_8_bit_path_segments True if using 8bit logical format to + * encode the data, false if using 16bit logical format. Only has effect + * on the class/object/attribute id the EPath was constructed with + * @return Encoded EPath as contiguous sequence of bytes + */ std::vector packPaddedPath(bool use_8_bit_path_segments=false) const; + + /** + * @brief expandPaddedPath If the EPath was default-constructed, this + * functions allows to fill the EPath with class/object/attribute id + * values, encoded in a user-specified format. However, if the EPath was + * constructed with a collection of segments, this function does nothing + * @param data Class/object/attribute id logical segments encoded in a + * user-specified format + */ void expandPaddedPath(const std::vector& data); CipUint getClassId() const; CipUint getObjectId() const; CipUint getAttributeId() const; + + /** + * @brief Calculates the total size of the encoded EPath in 16bit words. + * Depending on how the EPath was constructed, either calculates the size + * of the class/object/attribute id logical segments, or asks all segments + * in the collection of segments to measure themselves + * @param use_8_bit_path_segments True if the class/object/attribute id + * logical segments should be counted with an 8bit logical format, false + * if these should be counted as 16bit logical format. Has no effect when + * the EPath was constructed with a collection of segments + * @return The total size of the encoded EPath in 16bit words + */ CipUsint getSizeInWords(bool use_8_bit_path_segments=false) const; + /** + * @brief Constructs a string representation of the EPath. Depending on + * whether or not the EPath was constructed with a collection of segments + * or a class/object/attribute id, it either constructs a string of the + * EPath with class/object/attribute id values or a representation of + * every segment + * @return String representation of the EPath with class/object/attribute + * id values or string representations of the segments + */ std::string toString() const; + + /** + * @brief Compares the EPath for equality to the other EPath, however, + * it only checks for the class/object/attribute id values. In case of an + * EPath with a segment collection, always returns true + * @param other The EPath to compare this EPath with + * @return True if EPath was constructed with segment collection, independent + * of the equality of those segments OR the class/object/attribute id + * values all match, false if these don't + */ bool operator==(const EPath& other) const; private: diff --git a/src/cip/segments/ANSISegment.cpp b/src/cip/segments/ANSISegment.cpp index a4d1096..1865b18 100644 --- a/src/cip/segments/ANSISegment.cpp +++ b/src/cip/segments/ANSISegment.cpp @@ -23,8 +23,7 @@ namespace segments { Buffer buffer; // Stitch header together from the segment type and segment subtype. - CipUsint header = static_cast(SegmentType::DATA_SEGMENT) - | static_cast(SubType::ANSI_EXTENDED_SYMBOL_SEGMENT); + CipUsint header = getSegmentHeader(); // Add the header, symbol size and data. buffer << header << static_cast(_data.size()) << _data; @@ -39,7 +38,7 @@ namespace segments { uint8_t ANSISegment::getSize() const { - // Size is amount of chars + header and symbol length. + // Size is data size, plus header and symbol length (1 each). uint8_t size = _data.size() + 2; // Check for padding. @@ -50,6 +49,17 @@ namespace segments { return size; } + uint8_t ANSISegment::getSegmentHeader() const + { + return static_cast(SegmentType::DATA_SEGMENT) + | static_cast(SubType::ANSI_EXTENDED_SYMBOL_SEGMENT); + } + + std::string ANSISegment::toString() const + { + return std::string( _data.begin(), _data.end() ); + } + } } } diff --git a/src/cip/segments/ANSISegment.h b/src/cip/segments/ANSISegment.h index e5e7b7c..4bbb31c 100644 --- a/src/cip/segments/ANSISegment.h +++ b/src/cip/segments/ANSISegment.h @@ -36,6 +36,21 @@ namespace segments { * @return The size of the segment in bytes */ uint8_t getSize() const override; + + /** + * @brief Calculates the segment header byte for this ANSI Extended Symbol + * Segment + * @return The segment header for this ANSI Extended Symbol Segment + */ + uint8_t getSegmentHeader() const override; + + /** + * @brief Constructs a string representation of the data value of this + * ANSI Extended Symbol Segment + * @return The string representation of the data of this segment + */ + std::string toString() const override; + }; } diff --git a/src/cip/segments/ISegment.h b/src/cip/segments/ISegment.h index 13025e6..7a478dc 100644 --- a/src/cip/segments/ISegment.h +++ b/src/cip/segments/ISegment.h @@ -31,6 +31,19 @@ namespace segments { */ virtual uint8_t getSize() const = 0; + /** + * @brief Calculates the segment header byte for the specific segment + * @return The segment header byte + */ + virtual uint8_t getSegmentHeader() const = 0; + + /** + * @brief Constructs a string representation of the data value of this + * segment in a human-readable form + * @return The string representation of this segment + */ + virtual std::string toString() const = 0; + protected: /** diff --git a/src/cip/segments/MemberIDSegment.cpp b/src/cip/segments/MemberIDSegment.cpp index 226daaf..ba34a56 100644 --- a/src/cip/segments/MemberIDSegment.cpp +++ b/src/cip/segments/MemberIDSegment.cpp @@ -2,6 +2,9 @@ // Created by Johannes Kauffmann on 08/09/21. // +#include +#include + #include "MemberIDSegment.h" #include "utils/Buffer.h" @@ -15,7 +18,7 @@ namespace segments { : LogicalSegment({}, LogicalFormat::FORMAT_16_BIT) { if (use_8_bits) { - _format = LogicalFormat::FORMAT_16_BIT; + _format = LogicalFormat::FORMAT_8_BIT; } // Convert MemberID to uint8_t or two bytes of uint16_t @@ -35,13 +38,11 @@ namespace segments { Buffer buffer; // The segment header consists of the the segment type, logical type and logical format - CipUsint header = static_cast(SegmentType::LOGICAL_SEGMENT) - | static_cast(LogicalType::MEMBER_ID) - | static_cast(_format); + CipUsint header = getSegmentHeader(); buffer << header; - // Check for padding + // Check if padding is needed if (_format == LogicalFormat::FORMAT_16_BIT) { buffer << static_cast(0x00); } @@ -53,6 +54,8 @@ namespace segments { uint8_t MemberIDSegment::getSize() const { + // Size is data size plus header length (1) + // Constructor defines data size for the logical format used. uint8_t size = _data.size() + 1; // Check for padding @@ -63,6 +66,35 @@ namespace segments { return size; } + uint8_t MemberIDSegment::getSegmentHeader() const + { + return static_cast(SegmentType::LOGICAL_SEGMENT) + | static_cast(LogicalType::MEMBER_ID) + | static_cast(_format); + } + + std::string MemberIDSegment::toString() const + { + // Format the hexadecimal value with 2 leading zero's + std::stringstream stream; + stream << "0x" << std::hex << std::setfill('0') << std::setw(2); + + Buffer buffer(_data); + + // Decode to either 8bit or 16bit + if (_format == LogicalFormat::FORMAT_8_BIT) { + CipUsint value; + buffer >> value; + stream << int(value); + } else { + CipUint value; + buffer >> value; + stream << int(value); + } + + return stream.str(); + } + } } } diff --git a/src/cip/segments/MemberIDSegment.h b/src/cip/segments/MemberIDSegment.h index 3e39a80..78221fb 100644 --- a/src/cip/segments/MemberIDSegment.h +++ b/src/cip/segments/MemberIDSegment.h @@ -37,6 +37,20 @@ namespace segments { * @return The size of the complete segment in bytes */ uint8_t getSize() const override; + + /** + * @brief Calculates the segment header byte for this Logical MemberID, + * given its Logical Format + * @return The segment header for this Logical MemberID + */ + uint8_t getSegmentHeader() const override; + + /** + * @brief Constructs a string representation of the Logical MemberID value + * in the format "0xVALU" with the value in hexadecimal format + * @return String with the MemberID as a hexadecimal value + */ + std::string toString() const override; }; } From 8e29c931c89401cf1e70a92892e6494a72ecb9cf Mon Sep 17 00:00:00 2001 From: Johannes Kauffmann Date: Thu, 12 Aug 2021 11:26:45 +0200 Subject: [PATCH 4/7] style: Delete line from EPath::expandPaddedPath() --- src/cip/EPath.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cip/EPath.cpp b/src/cip/EPath.cpp index 31b00f5..0d89911 100644 --- a/src/cip/EPath.cpp +++ b/src/cip/EPath.cpp @@ -178,7 +178,6 @@ namespace cip { } void EPath::expandPaddedPath(const std::vector &data) { - if (!_segments.empty()) { Logger(LogLevel::WARNING) << "EPath already contains a collection of segments!" "Not expanding data!"; From b063c19c5979f05440695e67970fd97eb96f7819 Mon Sep 17 00:00:00 2001 From: Johannes Kauffmann Date: Thu, 12 Aug 2021 11:33:28 +0200 Subject: [PATCH 5/7] chore: Simplified using directive --- src/cip/segments/ANSISegment.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cip/segments/ANSISegment.cpp b/src/cip/segments/ANSISegment.cpp index 1865b18..e9883e3 100644 --- a/src/cip/segments/ANSISegment.cpp +++ b/src/cip/segments/ANSISegment.cpp @@ -11,8 +11,8 @@ namespace eipScanner { namespace cip { namespace segments { - using eipScanner::cip::segments::DataSegment; - using eipScanner::utils::Buffer; + using segments::DataSegment; + using utils::Buffer; ANSISegment::ANSISegment(const std::vector& data) : DataSegment(data) { From f247c399a7a298230c658496f1dedbf01b3b668e Mon Sep 17 00:00:00 2001 From: Johannes Kauffmann Date: Mon, 16 Aug 2021 10:51:34 +0200 Subject: [PATCH 6/7] refactor: Encode segment data in constructor ISegment: - add: Added documentation to the protected contructor and _data member variable as to their usages. - chg: The encode() method and getSize() method are now called data() and size(), respectively, to represent their new roles. Their implementations in the concrete ANSISegment and MemberIDSegment class now just return the data and size, instead of doing complex encoding and size calculations. ANSISegment: - chg: Constructor now encodes the data with header, size and optional padding information instead of the renamed encode() method. The toString method has been refactored to return the correct string. MemberIDSegment: - chg: Constructor now encodes the data with header and correct format information instead of the renamed encode() method. The toString method has been refactored to return the correct string. --- src/cip/EPath.cpp | 4 +-- src/cip/segments/ANSISegment.cpp | 34 +++++++++---------- src/cip/segments/ANSISegment.h | 13 ++++---- src/cip/segments/ISegment.h | 19 ++++++++--- src/cip/segments/MemberIDSegment.cpp | 49 ++++++++++++---------------- src/cip/segments/MemberIDSegment.h | 16 ++++----- 6 files changed, 67 insertions(+), 68 deletions(-) diff --git a/src/cip/EPath.cpp b/src/cip/EPath.cpp index 0d89911..e7e9671 100644 --- a/src/cip/EPath.cpp +++ b/src/cip/EPath.cpp @@ -74,7 +74,7 @@ namespace cip { // Append all encoded segment data together. for (ISegment::SPtr segment : _segments) { - buffer << segment->encode(); + buffer << segment->data(); } return buffer.data(); @@ -137,7 +137,7 @@ namespace cip { CipUsint size = 0; for (ISegment::SPtr segment : _segments) { - size += segment->getSize(); + size += segment->size(); } return size / 2; diff --git a/src/cip/segments/ANSISegment.cpp b/src/cip/segments/ANSISegment.cpp index e9883e3..2ac0976 100644 --- a/src/cip/segments/ANSISegment.cpp +++ b/src/cip/segments/ANSISegment.cpp @@ -14,11 +14,7 @@ namespace segments { using segments::DataSegment; using utils::Buffer; - ANSISegment::ANSISegment(const std::vector& data) - : DataSegment(data) { - } - - std::vector ANSISegment::encode() const + ANSISegment::ANSISegment(const std::vector& data) : DataSegment({}) { Buffer buffer; @@ -26,27 +22,24 @@ namespace segments { CipUsint header = getSegmentHeader(); // Add the header, symbol size and data. - buffer << header << static_cast(_data.size()) << _data; + buffer << header << static_cast(data.size()) << data; // Add optional padding if the size is of odd length. - if (_data.size() % 2 != 0) { + if (data.size() % 2 != 0) { buffer << static_cast(0x00); } - return buffer.data(); + _data = buffer.data(); } - uint8_t ANSISegment::getSize() const + std::vector ANSISegment::data() const { - // Size is data size, plus header and symbol length (1 each). - uint8_t size = _data.size() + 2; - - // Check for padding. - if (size % 2 != 0) { - size++; - } + return _data; + } - return size; + uint8_t ANSISegment::size() const + { + return _data.size(); } uint8_t ANSISegment::getSegmentHeader() const @@ -57,7 +50,12 @@ namespace segments { std::string ANSISegment::toString() const { - return std::string( _data.begin(), _data.end() ); + int paddingSize = 0; + if (_data.back() == 0x00) { + paddingSize++; + } + // Construct a string without the header and optional padding + return std::string( _data.begin() + 2, _data.end() - paddingSize); } } diff --git a/src/cip/segments/ANSISegment.h b/src/cip/segments/ANSISegment.h index 4bbb31c..831b9d3 100644 --- a/src/cip/segments/ANSISegment.h +++ b/src/cip/segments/ANSISegment.h @@ -18,24 +18,23 @@ namespace segments { public: /** - * @brief Constructs a CIP DataSegment of type ANSI Extended Symbol Segment. + * @brief Constructs a CIP DataSegment of type ANSI Extended Symbol Segment + * and encodes the data with header, symbol length, data and possible padding * @param data The segment data, excluding details such as length or padding */ ANSISegment(const std::vector& data); /** - * @brief Encodes the segment data with the header, symbol length and data - * with possible padding included + * @brief Gets the total encoded segment data * @return The encoded segment data */ - std::vector encode() const override; + std::vector data() const override; /** - * @brief Calculates the size of the segment in bytes, including the - * header, symbol length and possible padding + * @brief Gets the total size of the encoded segment in bytes * @return The size of the segment in bytes */ - uint8_t getSize() const override; + uint8_t size() const override; /** * @brief Calculates the segment header byte for this ANSI Extended Symbol diff --git a/src/cip/segments/ISegment.h b/src/cip/segments/ISegment.h index 7a478dc..2255618 100644 --- a/src/cip/segments/ISegment.h +++ b/src/cip/segments/ISegment.h @@ -20,16 +20,16 @@ namespace segments { using SPtr = std::shared_ptr; /** - * @brief Encodes the segment data + * @brief Gets the total encoded segment data * @return The encoded segment data */ - virtual std::vector encode() const = 0; + virtual std::vector data() const = 0; /** - * @brief Calculates the size of the segment in bytes + * @brief Gets the total size of the encoded segment in bytes * @return The size of the segment in bytes */ - virtual uint8_t getSize() const = 0; + virtual uint8_t size() const = 0; /** * @brief Calculates the segment header byte for the specific segment @@ -62,9 +62,20 @@ namespace segments { }; protected: + + /** + * @brief The constructor of a concrete segment should take in any + * segment specific data and encode it accordingly + * @param data The segment-specific encoded data + */ ISegment(const std::vector &data); protected: + + /** + * @brief This member variable contains the encoded segment data + * (including header and padding details) at all times + */ std::vector _data; }; diff --git a/src/cip/segments/MemberIDSegment.cpp b/src/cip/segments/MemberIDSegment.cpp index ba34a56..0959083 100644 --- a/src/cip/segments/MemberIDSegment.cpp +++ b/src/cip/segments/MemberIDSegment.cpp @@ -17,53 +17,38 @@ namespace segments { MemberIDSegment::MemberIDSegment(CipUint memberId, bool use_8_bits) : LogicalSegment({}, LogicalFormat::FORMAT_16_BIT) { + // Store the correct format if (use_8_bits) { _format = LogicalFormat::FORMAT_8_BIT; } - // Convert MemberID to uint8_t or two bytes of uint16_t Buffer buffer; + // The segment header consists of the the segment type, logical type and logical format + CipUsint header = getSegmentHeader(); + + buffer << header; + + // Convert MemberID to uint8_t or two bytes of uint16_t if (_format == LogicalFormat::FORMAT_8_BIT) { buffer << static_cast(memberId); } else { + // Add padding between header and data + buffer << static_cast(0x00); buffer << memberId; } _data = buffer.data(); } - std::vector MemberIDSegment::encode() const + std::vector MemberIDSegment::data() const { - Buffer buffer; - - // The segment header consists of the the segment type, logical type and logical format - CipUsint header = getSegmentHeader(); - - buffer << header; - - // Check if padding is needed - if (_format == LogicalFormat::FORMAT_16_BIT) { - buffer << static_cast(0x00); - } - - buffer << _data; - - return buffer.data(); + return _data; } - uint8_t MemberIDSegment::getSize() const + uint8_t MemberIDSegment::size() const { - // Size is data size plus header length (1) - // Constructor defines data size for the logical format used. - uint8_t size = _data.size() + 1; - - // Check for padding - if (_format == LogicalFormat::FORMAT_16_BIT) { - size++; - } - - return size; + return _data.size(); } uint8_t MemberIDSegment::getSegmentHeader() const @@ -79,7 +64,13 @@ namespace segments { std::stringstream stream; stream << "0x" << std::hex << std::setfill('0') << std::setw(2); - Buffer buffer(_data); + int headerSize = 1; + if (_format == LogicalFormat::FORMAT_16_BIT) { + headerSize++; + } + + // Create a buffer containing just the data, without the header + Buffer buffer({_data.begin() + headerSize, _data.end()}); // Decode to either 8bit or 16bit if (_format == LogicalFormat::FORMAT_8_BIT) { diff --git a/src/cip/segments/MemberIDSegment.h b/src/cip/segments/MemberIDSegment.h index 78221fb..5f98ab9 100644 --- a/src/cip/segments/MemberIDSegment.h +++ b/src/cip/segments/MemberIDSegment.h @@ -17,7 +17,8 @@ namespace segments { public: /** - * @brief Constructs a Logical Segment of Logical Type MemberID + * @brief Constructs a Logical Segment of Logical Type MemberID and encodes + * the given value with header and format information * @param memberId The logical value of the Member ID * @param use_8_bit_segment True if the segment format should be 8bit * format, false if format should be 16bit format @@ -25,18 +26,17 @@ namespace segments { MemberIDSegment(CipUint memberId, bool use_8_bit_segment=false); /** - * @brief Encodes the MemberID Logical Segment with the correct segment - * header, possible padding and little endian encoded data - * @return Vector of bytes with the complete segment encoded + * @brief Gets the total encoded segment data + * @return The encoded segment data */ - std::vector encode() const override; + std::vector data() const override; /** - * @brief Calculates the size of the complete MemberID Logical Segment in + * @brief Gets the total size of the encoded segment in bytes * bytes - * @return The size of the complete segment in bytes + * @return The size of the segment in bytes */ - uint8_t getSize() const override; + uint8_t size() const override; /** * @brief Calculates the segment header byte for this Logical MemberID, From e2190031db1730f8724e714b3b77f31e997b6af7 Mon Sep 17 00:00:00 2001 From: Johannes Kauffmann Date: Mon, 23 Aug 2021 09:11:03 +0200 Subject: [PATCH 7/7] fix: Don't check for padding when length is 0 ANSISegment: fix: Previously, when the segment didn't hold any actual data, the toString() function would crash trying to construct an empty string. This was because the string is constructed from the beginning of the data vector minus two bytes (for the header byte and length byte) to the end of the data vector, minus a possible pad byte. When the data length byte was zero, the check for the padding interpreted the last byte as the pad byte, when this was the length byte, and thus an invalid string would be constructed. Now, this case is checked at the beginning. --- src/cip/segments/ANSISegment.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cip/segments/ANSISegment.cpp b/src/cip/segments/ANSISegment.cpp index 2ac0976..375fb97 100644 --- a/src/cip/segments/ANSISegment.cpp +++ b/src/cip/segments/ANSISegment.cpp @@ -2,9 +2,11 @@ // Created by Johannes Kauffmann on 08/06/21. // +#include "ANSISegment.h" + +#include #include -#include "ANSISegment.h" #include "utils/Buffer.h" namespace eipScanner { @@ -30,6 +32,8 @@ namespace segments { } _data = buffer.data(); + + assert(_data.size() >= 2); } std::vector ANSISegment::data() const @@ -50,10 +54,17 @@ namespace segments { std::string ANSISegment::toString() const { + // Check for empty segment data. + if (_data.size() == 2) { + return {}; + } + + // Check for possible padding. int paddingSize = 0; if (_data.back() == 0x00) { paddingSize++; } + // Construct a string without the header and optional padding return std::string( _data.begin() + 2, _data.end() - paddingSize); }