diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6a58194..045cbb3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,12 @@ set(SOURCE_FILES cip/connectionManager/ForwardOpenResponse.cpp cip/connectionManager/NetworkConnectionParametersBuilder.cpp + 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 cip/MessageRouterRequest.cpp diff --git a/src/cip/EPath.cpp b/src/cip/EPath.cpp index 447ef30..e7e9671 100644 --- a/src/cip/EPath.cpp +++ b/src/cip/EPath.cpp @@ -1,13 +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, @@ -22,7 +26,8 @@ namespace cip { : _classId{0} , _objectId{0} , _attributeId{0} - , _size{0}{ + , _size{0} + , _segments{} { } @@ -30,7 +35,8 @@ namespace cip { : _classId{classId} , _objectId{0} , _attributeId{0} - , _size{1} { + , _size{1} + , _segments{} { } @@ -38,7 +44,8 @@ namespace cip { : _classId{classId} , _objectId{objectId} , _attributeId{0} - , _size{2} { + , _size{2} + , _segments{} { } @@ -46,10 +53,33 @@ namespace cip { : _classId{classId} , _objectId{objectId} , _attributeId{attributeId} - , _size{3} { + , _size{3} + , _segments{} { + } + EPath::EPath(const std::vector &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; + + // Append all encoded segment data together. + for (ISegment::SPtr segment : _segments) { + buffer << segment->data(); + } + + return buffer.data(); + } + if (use_8_bit_path_segments) { Buffer buffer(_size*2); @@ -103,6 +133,16 @@ 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->size(); + } + + return size / 2; + } + if (use_8_bit_path_segments) { return _size; } @@ -112,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); @@ -125,6 +178,12 @@ 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 d2270b6..ff380fa 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,15 +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: @@ -35,6 +93,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..375fb97 --- /dev/null +++ b/src/cip/segments/ANSISegment.cpp @@ -0,0 +1,74 @@ +// +// Created by Johannes Kauffmann on 08/06/21. +// + +#include "ANSISegment.h" + +#include +#include + +#include "utils/Buffer.h" + +namespace eipScanner { +namespace cip { +namespace segments { + + using segments::DataSegment; + using utils::Buffer; + + ANSISegment::ANSISegment(const std::vector& data) : DataSegment({}) + { + Buffer buffer; + + // Stitch header together from the segment type and segment subtype. + CipUsint header = getSegmentHeader(); + + // 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); + } + + _data = buffer.data(); + + assert(_data.size() >= 2); + } + + std::vector ANSISegment::data() const + { + return _data; + } + + uint8_t ANSISegment::size() const + { + return _data.size(); + } + + uint8_t ANSISegment::getSegmentHeader() const + { + return static_cast(SegmentType::DATA_SEGMENT) + | static_cast(SubType::ANSI_EXTENDED_SYMBOL_SEGMENT); + } + + 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); + } + +} +} +} diff --git a/src/cip/segments/ANSISegment.h b/src/cip/segments/ANSISegment.h new file mode 100644 index 0000000..831b9d3 --- /dev/null +++ b/src/cip/segments/ANSISegment.h @@ -0,0 +1,59 @@ +// +// 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 + * 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 Gets the total encoded segment data + * @return The encoded segment data + */ + std::vector data() const override; + + /** + * @brief Gets the total size of the encoded segment in bytes + * @return The size of the segment in bytes + */ + uint8_t size() 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; + + }; + +} +} +} + +#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..1a9caa6 --- /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..2255618 --- /dev/null +++ b/src/cip/segments/ISegment.h @@ -0,0 +1,86 @@ +// +// 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 Gets the total encoded segment data + * @return The encoded segment data + */ + virtual std::vector data() const = 0; + + /** + * @brief Gets the total size of the encoded segment in bytes + * @return The size of the segment in bytes + */ + virtual uint8_t size() 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: + + /** + * @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: + + /** + * @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; + }; + +} +} +} + +#endif // EIPSCANNER_CIP_SEGMENTS_ISEGMENT_H 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..0959083 --- /dev/null +++ b/src/cip/segments/MemberIDSegment.cpp @@ -0,0 +1,91 @@ +// +// Created by Johannes Kauffmann on 08/09/21. +// + +#include +#include + +#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) + { + // Store the correct format + if (use_8_bits) { + _format = LogicalFormat::FORMAT_8_BIT; + } + + 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::data() const + { + return _data; + } + + uint8_t MemberIDSegment::size() const + { + return _data.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); + + 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) { + 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 new file mode 100644 index 0000000..5f98ab9 --- /dev/null +++ b/src/cip/segments/MemberIDSegment.h @@ -0,0 +1,60 @@ +// +// 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 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 + */ + MemberIDSegment(CipUint memberId, bool use_8_bit_segment=false); + + /** + * @brief Gets the total encoded segment data + * @return The encoded segment data + */ + std::vector data() const override; + + /** + * @brief Gets the total size of the encoded segment in bytes + * bytes + * @return The size of the segment in bytes + */ + uint8_t size() 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; + }; + +} +} +} + +#endif // EIPSCANNER_CIP_SEGMENTS_MEMBERIDSEGMENT_H