diff --git a/Packet++/CMakeLists.txt b/Packet++/CMakeLists.txt index 090d2208b7..9e30e88695 100644 --- a/Packet++/CMakeLists.txt +++ b/Packet++/CMakeLists.txt @@ -18,6 +18,7 @@ add_library( src/FtpLayer.cpp src/GreLayer.cpp src/GtpLayer.cpp + src/GvcpLayer.cpp src/HttpLayer.cpp src/IcmpLayer.cpp src/IcmpV6Layer.cpp @@ -100,6 +101,7 @@ set( header/FtpLayer.h header/GreLayer.h header/GtpLayer.h + header/GvcpLayer.h header/HttpLayer.h header/IcmpLayer.h header/IcmpV6Layer.h diff --git a/Packet++/header/GvcpLayer.h b/Packet++/header/GvcpLayer.h new file mode 100644 index 0000000000..5cb01ded0a --- /dev/null +++ b/Packet++/header/GvcpLayer.h @@ -0,0 +1,960 @@ +#pragma once + +#include "IpAddress.h" +#include "Layer.h" +#include "MacAddress.h" +#include +#include +#include +#include +#include + +/// @file +/// This file contains classes for parsing, creating and editing GVCP (GigE Vision Control Protocol) packets. +/// The implementation is based on the GigE Vision (R) Specification version 2.0 + +/// @namespace pcpp +/// @brief The main namespace for the PcapPlusPlus lib +namespace pcpp +{ + /// GVCP flag. The meaning of bits 0-3 is specified by each command, bits 4-6 are reserved and bit 7 (the least + /// significant bit) is the acknowledge-required bit + using GvcpFlag = uint8_t; + + /// @class GvcpLayer + /// An abstract class representing the GigE Vision Control Protocol (GVCP). + /// The class is implemented according to the GigE Vision specification 2.0. + /// A GVCP layer holds the GVCP header, followed by the message fields that its class knows how to parse. + /// parseNextLayer() puts the rest of the message, if any, in a PayloadLayer + /// @see https://en.wikipedia.org/wiki/GigE_Vision + /// @note The class cannot be instantiated directly, use GvcpRequestLayer, GvcpAcknowledgeLayer or their derived + /// classes + class GvcpLayer : public Layer + { + public: + /// GVCP command defines the command values and the corresponding acknowledge values. + /// See more in the spec "18 Command and Acknowledge Values" + enum class GvcpCommand : uint16_t + { + /// Discovery command (Discovery Protocol Control) + DiscoveredCmd = 0x0002, + /// Discovery acknowledge (Discovery Protocol Control) + DiscoveredAck = 0x0003, + /// Force IP command (Discovery Protocol Control) + ForceIpCmd = 0x0004, + /// Force IP acknowledge (Discovery Protocol Control) + ForceIpAck = 0x0005, + + /// Packet resend command (Streaming Protocol Control) + PacketResendCmd = 0x0040, + /// Packet resend acknowledge (Streaming Protocol Control). The resent packet must be on the stream channel + PacketResendAck = 0x0041, + + /// Read register command (Device Memory Access) + ReadRegCmd = 0x0080, + /// Read register acknowledge (Device Memory Access) + ReadRegAck = 0x0081, + /// Write register command (Device Memory Access) + WriteRegCmd = 0x0082, + /// Write register acknowledge (Device Memory Access) + WriteRegAck = 0x0083, + /// Read memory command (Device Memory Access) + ReadMemCmd = 0x0084, + /// Read memory acknowledge (Device Memory Access) + ReadMemAck = 0x0085, + /// Write memory command (Device Memory Access) + WriteMemCmd = 0x0086, + /// Write memory acknowledge (Device Memory Access) + WriteMemAck = 0x0087, + /// Pending acknowledge (Device Memory Access) + PendingAck = 0x0089, + + /// Event command (Asynchronous Events) + EventCmd = 0x00C0, + /// Event acknowledge (Asynchronous Events) + EventAck = 0x00C1, + /// Event data command (Asynchronous Events) + EventDataCmd = 0x00C2, + /// Event data acknowledge (Asynchronous Events) + EventDataAck = 0x00C3, + + /// Action command (Miscellaneous) + ActionCmd = 0x0100, + /// Action acknowledge (Miscellaneous) + ActionAck = 0x0101, + + /// Unknown command + Unknown = 0xFFFF + }; + + /// A static method that checks whether the port is considered as GVCP + /// @param[in] port The port number to be checked + /// @return True if the port is the GVCP port (3956), false otherwise + static bool isGvcpPort(uint16_t port) + { + return port == 3956; + } + + /// A static method that parses the raw data and creates the matching GVCP layer. If the data begins with the + /// GVCP magic number, the method creates a request layer, otherwise an acknowledge layer. For Discovery and + /// Force IP messages, the method creates their specific layers, unless the data is too short for the fields of + /// these messages + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + /// @return A pointer to the newly created GVCP layer, or nullptr if the data isn't a valid GVCP message + static GvcpLayer* parseGvcpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet); + + /// @return The command (or acknowledge) value of this message, GvcpCommand::Unknown if the value isn't + /// defined by the spec + virtual GvcpCommand getCommand() const = 0; + + /// @return The data size field from the header, meaning the size of the message without the GVCP header + virtual uint16_t getDataSize() const = 0; + + // Overridden methods + + /// Creates a PayloadLayer from the rest of the message, if there is data after this layer + void parseNextLayer() override; + + /// @return The OSI Model layer this protocol belongs to + OsiModelLayer getOsiModelLayer() const override + { + return OsiModelApplicationLayer; + } + + /// @struct SerializedFields + /// Fields written by GvcpLayer's serializeLayer(), in addition to + /// Layer::SerializedFields. + struct SerializedFields : Layer::SerializedFields + { + /// @return All field descriptors for GvcpLayer + static std::vector all() + { + auto result = Layer::SerializedFields::all(); + std::initializer_list extra{ Command, CommandName }; + std::copy(extra.begin(), extra.end(), std::back_inserter(result)); + return result; + } + + /// @brief Command or acknowledge value + static const FieldDescriptor Command; + + /// @brief Name of the command or acknowledge value, or "Unknown" + static const FieldDescriptor CommandName; + + // cppcheck-suppress duplInheritedMember + /// Maximum field ID used by GvcpLayer + static constexpr uint16_t MaxID = Layer::SerializedFields::MaxID + 2; + }; + + protected: + void serializeLayer(ObjectScope& serializer) const override; + + GvcpLayer() = default; + + GvcpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : Layer(data, dataLen, prevLayer, packet, GVCP) + {} + + /// Allocate zeroed data for a new layer and set the protocol + /// @param[in] dataLen The size of the data to allocate in bytes + void initLayer(size_t dataLen); + + private: + /// @return The command field of a GVCP request or acknowledge in host byte order, also when the spec doesn't + /// define the value + static uint16_t getRawCommand(const uint8_t* data); + }; + + /// Output operator for GvcpLayer::GvcpCommand, prints the command name, or "Unknown" if the value isn't defined by + /// the spec + std::ostream& operator<<(std::ostream& os, GvcpLayer::GvcpCommand command); + + /// @class GvcpRequestLayer + /// Represents a GVCP request (command) message. Commands which don't have a specific layer class are + /// represented by this class + class GvcpRequestLayer : public GvcpLayer + { + public: + /// A constructor that creates the layer from an existing packet raw data + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + GvcpRequestLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : GvcpLayer(data, dataLen, prevLayer, packet) + {} + + /// A constructor that creates a new GVCP request which contains only the GVCP header. To add message data, + /// add a PayloadLayer after this layer + /// @param[in] command The command + /// @param[in] flag The flag, optional + /// @param[in] requestId The request ID, optional. The spec requires a non-zero value + /// @throw std::invalid_argument if the command is an acknowledge value (an odd value) + explicit GvcpRequestLayer(GvcpCommand command, GvcpFlag flag = 0, uint16_t requestId = 1) + : GvcpRequestLayer(command, flag, requestId, 0) + {} + + /// A static method that checks whether the data is a valid GVCP request. The data must be large enough to hold + /// a GVCP request header and must begin with the GVCP magic number + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @return True if the data is a valid GVCP request, false otherwise + static bool isDataValid(const uint8_t* data, size_t dataLen); + + /// @return The flag from the header + GvcpFlag getFlag() const; + + /// Set the flag in the header + /// @param[in] flag The flag to set + void setFlag(GvcpFlag flag); + + /// @return True if the acknowledge-required bit is set in the flag, false otherwise + bool hasAcknowledgeFlag() const; + + /// @return The request ID from the header + uint16_t getRequestId() const; + + /// Set the request ID in the header + /// @param[in] requestId The request ID to set + void setRequestId(uint16_t requestId); + + // Overridden methods + + /// @return The command from the header, GvcpCommand::Unknown if the value isn't defined by the spec + GvcpCommand getCommand() const override; + + /// @return The data size from the header + uint16_t getDataSize() const override; + + /// @return The size of the GVCP request header + size_t getHeaderLen() const override + { + return sizeof(gvcp_request_header); + } + + /// Calculate the data size field in the header according to the size of the data that follows the header + void computeCalculateFields() override; + + /// @return A string representation of the layer most important data + std::string toString() const override; + + /// @struct SerializedFields + /// Fields written by GvcpRequestLayer's serializeLayer(), in addition to + /// GvcpLayer::SerializedFields. + struct SerializedFields : GvcpLayer::SerializedFields + { + /// @return All field descriptors for GvcpRequestLayer + static std::vector all() + { + auto result = GvcpLayer::SerializedFields::all(); + std::initializer_list extra{ Flag, AcknowledgeRequired, RequestId }; + std::copy(extra.begin(), extra.end(), std::back_inserter(result)); + return result; + } + + /// @brief Flag from the header + static const FieldDescriptor Flag; + + /// @brief Whether the acknowledge-required bit is set + static const FieldDescriptor AcknowledgeRequired; + + /// @brief Request ID + static const FieldDescriptor RequestId; + + // cppcheck-suppress duplInheritedMember + /// Maximum field ID used by GvcpRequestLayer + static constexpr uint16_t MaxID = GvcpLayer::SerializedFields::MaxID + 3; + }; + + protected: + void serializeLayer(ObjectScope& serializer) const override; + +#pragma pack(push, 1) + /// @struct gvcp_request_header + /// GVCP request header, see the spec "15.1 Request Header". The data is stored as big-endian. + struct gvcp_request_header + { + /// Magic number, always 0x42 + uint8_t magicNumber; + /// GVCP flag + uint8_t flag; + /// Command + uint16_t command; + /// Data size (the size of the message without the header) + uint16_t dataSize; + /// Request ID + uint16_t requestId; + }; +#pragma pack(pop) + static_assert(sizeof(gvcp_request_header) == 8, "gvcp_request_header size is not 8 bytes"); + + /// A constructor that creates a new GVCP request with a message body of zeroed bytes after the header + /// @param[in] command The command + /// @param[in] flag The flag + /// @param[in] requestId The request ID + /// @param[in] bodyLen The size of the message body in bytes + /// @throw std::invalid_argument if the command is an acknowledge value (an odd value) + GvcpRequestLayer(GvcpCommand command, GvcpFlag flag, uint16_t requestId, size_t bodyLen); + + private: + // GvcpLayer::parseGvcpLayer() reads the command from the header + friend class GvcpLayer; + + gvcp_request_header* getGvcpHeader() const + { + return reinterpret_cast(m_Data); + } + }; + + /// @class GvcpAcknowledgeLayer + /// Represents a GVCP acknowledge message. Acknowledges which don't have a specific layer class are + /// represented by this class + class GvcpAcknowledgeLayer : public GvcpLayer + { + public: + /// GVCP response status can be returned in an acknowledge message or a GVSP header. + /// See more in the spec "Table 19-1: List of Standard Status Codes" + enum class GvcpResponseStatus : uint16_t + { + /// Command executed successfully + Success = 0x0000, + /// Only applies to packet being resent + PacketResend = 0x0100, + /// Command is not supported by the device + NotImplemented = 0x8001, + /// At least one parameter provided in the command is invalid (or out of range) for the device + InvalidParameter = 0x8002, + /// An attempt was made to access a non-existent address space location + InvalidAddress = 0x8003, + /// The addressed register cannot be written to + WriteProtect = 0x8004, + /// A badly aligned address offset or data size was specified + BadAlignment = 0x8005, + /// An attempt was made to access an address location which is currently/momentary not accessible + AccessDenied = 0x8006, + /// A required resource to service the request is not currently available. The request may be retried at a + /// later time + Busy = 0x8007, + /// Deprecated + LocalProblem = 0x8008, + /// Deprecated + MsgMismatch = 0x8009, + /// Deprecated + InvalidProtocol = 0x800A, + /// Deprecated + NoMsg = 0x800B, + /// The requested packet is not available anymore + PacketUnavailable = 0x800C, + /// Internal memory of GVSP transmitter overrun (typically for image acquisition) + DataOverrun = 0x800D, + /// The message header is not valid. Some of its fields do not match the specification + InvalidHeader = 0x800E, + /// Deprecated + WrongConfig = 0x800F, + /// The requested packet has not yet been acquired. Can be used for linescan cameras device when line + /// trigger rate is slower than application timeout + PacketNotYetAvailable = 0x8010, + /// The requested packet and all previous ones are not available anymore and have been discarded from the + /// GVSP transmitter memory + PacketAndPrevRemovedFromMemory = 0x8011, + /// The requested packet is not available anymore and has been discarded from the GVSP transmitter memory + PacketRemovedFromMemory = 0x8012, + /// The device is not synchronized to a master clock to be used as time reference + NoRefTime = 0x8013, + /// The packet cannot be resent at the moment due to temporary bandwidth issues and should be requested + /// again in the future + PacketTemporarilyUnavailable = 0x8014, + /// A device queue or packet data has overflowed + Overflow = 0x8015, + /// The requested scheduled action command was requested at a time that is already past + ActionLate = 0x8016, + /// Leader or trailer overflow (GEV 2.1) + LeaderTrailerOverflow = 0x8017, + /// Generic error + Error = 0x8FFF, + /// Unknown status + Unknown = 0xFFFF + }; + + /// A constructor that creates the layer from an existing packet raw data + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + GvcpAcknowledgeLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : GvcpLayer(data, dataLen, prevLayer, packet) + {} + + /// A constructor that creates a new GVCP acknowledge which contains only the GVCP header. To add message + /// data, add a PayloadLayer after this layer + /// @param[in] status The response status + /// @param[in] command The acknowledge value + /// @param[in] ackId The acknowledge ID, optional + /// @throw std::invalid_argument if the acknowledge value is a command value (an even value) + GvcpAcknowledgeLayer(GvcpResponseStatus status, GvcpCommand command, uint16_t ackId = 0) + : GvcpAcknowledgeLayer(status, command, ackId, 0) + {} + + /// A static method that checks whether the data is large enough to hold a GVCP acknowledge header + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @return True if the data can be parsed as a GVCP acknowledge, false otherwise + static bool isDataValid(const uint8_t* data, size_t dataLen); + + /// @return The response status from the header, GvcpResponseStatus::Unknown if the value isn't defined by the + /// spec + GvcpResponseStatus getStatus() const; + + /// Set the response status in the header + /// @param[in] status The response status to set + void setStatus(GvcpResponseStatus status); + + /// @return The acknowledge ID from the header + uint16_t getAckId() const; + + /// Set the acknowledge ID in the header + /// @param[in] ackId The acknowledge ID to set + void setAckId(uint16_t ackId); + + // Overridden methods + + /// @return The acknowledge value from the header, GvcpCommand::Unknown if the value isn't defined by the spec + GvcpCommand getCommand() const override; + + /// @return The data size from the header + uint16_t getDataSize() const override; + + /// @return The size of the GVCP acknowledge header + size_t getHeaderLen() const override + { + return sizeof(gvcp_ack_header); + } + + /// Calculate the data size field in the header according to the size of the data that follows the header + void computeCalculateFields() override; + + /// @return A string representation of the layer most important data + std::string toString() const override; + + /// @struct SerializedFields + /// Fields written by GvcpAcknowledgeLayer's serializeLayer(), in addition to + /// GvcpLayer::SerializedFields. + struct SerializedFields : GvcpLayer::SerializedFields + { + /// @return All field descriptors for GvcpAcknowledgeLayer + static std::vector all() + { + auto result = GvcpLayer::SerializedFields::all(); + std::initializer_list extra{ Status, StatusName, AckId }; + std::copy(extra.begin(), extra.end(), std::back_inserter(result)); + return result; + } + + /// @brief Response status value + static const FieldDescriptor Status; + + /// @brief Name of the response status, or "Unknown" + static const FieldDescriptor StatusName; + + /// @brief Acknowledge ID + static const FieldDescriptor AckId; + + // cppcheck-suppress duplInheritedMember + /// Maximum field ID used by GvcpAcknowledgeLayer + static constexpr uint16_t MaxID = GvcpLayer::SerializedFields::MaxID + 3; + }; + + protected: + void serializeLayer(ObjectScope& serializer) const override; + +#pragma pack(push, 1) + /// @struct gvcp_ack_header + /// GVCP acknowledge header, see the spec "15.2 Acknowledge Header". The data is stored as big-endian. + struct gvcp_ack_header + { + /// Response status + uint16_t status; + /// Command + uint16_t command; + /// Data size (the size of the message without the header) + uint16_t dataSize; + /// Acknowledge ID + uint16_t ackId; + }; +#pragma pack(pop) + static_assert(sizeof(gvcp_ack_header) == 8, "gvcp_ack_header size is not 8 bytes"); + + /// A constructor that creates a new GVCP acknowledge with a message body of zeroed bytes after the header + /// @param[in] status The response status + /// @param[in] command The acknowledge value + /// @param[in] ackId The acknowledge ID + /// @param[in] bodyLen The size of the message body in bytes + /// @throw std::invalid_argument if the acknowledge value is a command value (an even value) + GvcpAcknowledgeLayer(GvcpResponseStatus status, GvcpCommand command, uint16_t ackId, size_t bodyLen); + + private: + // GvcpLayer::parseGvcpLayer() reads the command from the header + friend class GvcpLayer; + + gvcp_ack_header* getGvcpHeader() const + { + return reinterpret_cast(m_Data); + } + }; + + /// Output operator for GvcpAcknowledgeLayer::GvcpResponseStatus, prints the status name, or "Unknown" if the value + /// isn't defined by the spec + std::ostream& operator<<(std::ostream& os, GvcpAcknowledgeLayer::GvcpResponseStatus status); + + /// @class GvcpDiscoveryRequestLayer + /// Represents a GVCP discovery command (DISCOVERY_CMD) + class GvcpDiscoveryRequestLayer : public GvcpRequestLayer + { + public: + /// A constructor that creates the layer from an existing packet raw data + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + GvcpDiscoveryRequestLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : GvcpRequestLayer(data, dataLen, prevLayer, packet) + {} + + /// A constructor that creates a new GVCP discovery command + /// @param[in] allowBroadcastAck Whether the device is allowed to broadcast the acknowledge, optional + /// @param[in] acknowledgeRequired Whether an acknowledge is required, optional + /// @param[in] requestId The request ID, optional. The spec requires a non-zero value + explicit GvcpDiscoveryRequestLayer(bool allowBroadcastAck = false, bool acknowledgeRequired = true, + uint16_t requestId = 1); + + /// @return True if the allow-broadcast-acknowledge bit is set in the flag, meaning the device is allowed to + /// broadcast the acknowledge, false otherwise + bool hasAllowBroadcastAckFlag() const; + + /// Set or clear the allow-broadcast-acknowledge bit in the flag + /// @param[in] allowBroadcastAck True to set the bit, false to clear it + void setAllowBroadcastAckFlag(bool allowBroadcastAck); + /// @struct SerializedFields + /// Fields written by GvcpDiscoveryRequestLayer's serializeLayer(), in addition to + /// GvcpRequestLayer::SerializedFields. + struct SerializedFields : GvcpRequestLayer::SerializedFields + { + /// @return All field descriptors for GvcpDiscoveryRequestLayer + static std::vector all() + { + auto result = GvcpRequestLayer::SerializedFields::all(); + std::initializer_list extra{ AllowBroadcastAck }; + std::copy(extra.begin(), extra.end(), std::back_inserter(result)); + return result; + } + + /// @brief Whether the allow-broadcast-acknowledge bit is set + static const FieldDescriptor AllowBroadcastAck; + + // cppcheck-suppress duplInheritedMember + /// Maximum field ID used by GvcpDiscoveryRequestLayer + static constexpr uint16_t MaxID = GvcpRequestLayer::SerializedFields::MaxID + 1; + }; + + protected: + void serializeLayer(ObjectScope& serializer) const override; + }; + + /// @class GvcpDiscoveryAcknowledgeLayer + /// Represents a GVCP discovery acknowledge (DISCOVERY_ACK) + class GvcpDiscoveryAcknowledgeLayer : public GvcpAcknowledgeLayer + { + public: + /// @struct GvcpVersion + /// The GigE Vision spec version + struct GvcpVersion + { + /// The version major number + uint16_t major; + /// The version minor number + uint16_t minor; + }; + + /// A constructor that creates the layer from an existing packet raw data + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + GvcpDiscoveryAcknowledgeLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : GvcpAcknowledgeLayer(data, dataLen, prevLayer, packet) + {} + + /// A constructor that creates a new GVCP discovery acknowledge. The constructor sets all of the discovery + /// fields to zero. Use the setters of this class to set them + /// @param[in] status The response status, optional + /// @param[in] ackId The acknowledge ID, optional + explicit GvcpDiscoveryAcknowledgeLayer(GvcpResponseStatus status = GvcpResponseStatus::Success, + uint16_t ackId = 0); + + // cppcheck-suppress duplInheritedMember + /// A static method that checks whether the data is large enough to hold a GVCP discovery acknowledge + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @return True if the data can be parsed as a GVCP discovery acknowledge, false otherwise + static bool isDataValid(const uint8_t* data, size_t dataLen); + + /// @return The GigE Vision spec version supported by the device + GvcpVersion getVersion() const; + + /// Set the GigE Vision spec version supported by the device + /// @param[in] version The version to set + void setVersion(const GvcpVersion& version); + + /// @return The device MAC address + MacAddress getMacAddress() const; + + /// Set the device MAC address + /// @param[in] macAddress The MAC address to set + void setMacAddress(const MacAddress& macAddress); + + /// @return The device IP address + IPv4Address getIpAddress() const; + + /// Set the device IP address + /// @param[in] ipAddress The IP address to set + void setIpAddress(const IPv4Address& ipAddress); + + /// @return The device subnet mask + IPv4Address getSubnetMask() const; + + /// Set the device subnet mask + /// @param[in] subnetMask The subnet mask to set + void setSubnetMask(const IPv4Address& subnetMask); + + /// @return The device default gateway IP address + IPv4Address getGatewayIpAddress() const; + + /// Set the device default gateway IP address + /// @param[in] gatewayIpAddress The default gateway IP address to set + void setGatewayIpAddress(const IPv4Address& gatewayIpAddress); + + /// @return The manufacturer name + std::string getManufacturerName() const; + + /// Set the manufacturer name + /// @param[in] manufacturerName The manufacturer name to set, truncated if longer than 31 characters + void setManufacturerName(const std::string& manufacturerName); + + /// @return The model name + std::string getModelName() const; + + /// Set the model name + /// @param[in] modelName The model name to set, truncated if longer than 31 characters + void setModelName(const std::string& modelName); + + /// @return The device version + std::string getDeviceVersion() const; + + /// Set the device version + /// @param[in] deviceVersion The device version to set, truncated if longer than 31 characters + void setDeviceVersion(const std::string& deviceVersion); + + /// @return The manufacturer specific information + std::string getManufacturerSpecificInformation() const; + + /// Set the manufacturer specific information + /// @param[in] info The manufacturer specific information to set, truncated if longer than 47 characters + void setManufacturerSpecificInformation(const std::string& info); + + /// @return The serial number + std::string getSerialNumber() const; + + /// Set the serial number + /// @param[in] serialNumber The serial number to set, truncated if longer than 15 characters + void setSerialNumber(const std::string& serialNumber); + + /// @return The user defined name + std::string getUserDefinedName() const; + + /// Set the user defined name + /// @param[in] userDefinedName The user defined name to set, truncated if longer than 15 characters + void setUserDefinedName(const std::string& userDefinedName); + + // Overridden methods + + /// @return The size of the GVCP header and the discovery acknowledge body + size_t getHeaderLen() const override + { + return sizeof(gvcp_ack_header) + sizeof(gvcp_discovery_body); + } + + /// @struct SerializedFields + /// Fields written by GvcpDiscoveryAcknowledgeLayer's serializeLayer(), in addition to + /// GvcpAcknowledgeLayer::SerializedFields. + struct SerializedFields : GvcpAcknowledgeLayer::SerializedFields + { + /// @return All field descriptors for GvcpDiscoveryAcknowledgeLayer + static std::vector all() + { + auto result = GvcpAcknowledgeLayer::SerializedFields::all(); + std::initializer_list extra{ + VersionMajor, VersionMinor, MacAddress, IpAddress, SubnetMask, + GatewayIpAddress, ManufacturerName, ModelName, DeviceVersion, ManufacturerSpecificInformation, + SerialNumber, UserDefinedName + }; + std::copy(extra.begin(), extra.end(), std::back_inserter(result)); + return result; + } + + /// @brief GigE Vision spec version major number + static const FieldDescriptor VersionMajor; + + /// @brief GigE Vision spec version minor number + static const FieldDescriptor VersionMinor; + + /// @brief Device MAC address, as a string + static const FieldDescriptor MacAddress; + + /// @brief Device IP address, as a string + static const FieldDescriptor IpAddress; + + /// @brief Device subnet mask, as a string + static const FieldDescriptor SubnetMask; + + /// @brief Device default gateway IP address, as a string + static const FieldDescriptor GatewayIpAddress; + + /// @brief Manufacturer name + static const FieldDescriptor ManufacturerName; + + /// @brief Model name + static const FieldDescriptor ModelName; + + /// @brief Device version + static const FieldDescriptor DeviceVersion; + + /// @brief Manufacturer specific information + static const FieldDescriptor ManufacturerSpecificInformation; + + /// @brief Serial number + static const FieldDescriptor SerialNumber; + + /// @brief User defined name + static const FieldDescriptor UserDefinedName; + + // cppcheck-suppress duplInheritedMember + /// Maximum field ID used by GvcpDiscoveryAcknowledgeLayer + static constexpr uint16_t MaxID = GvcpAcknowledgeLayer::SerializedFields::MaxID + 12; + }; + + protected: + void serializeLayer(ObjectScope& serializer) const override; + + private: +#pragma pack(push, 1) + /// GVCP discovery acknowledge body, see the spec "16.1.2 DISCOVERY_ACK". The data is stored as big-endian. + struct gvcp_discovery_body + { + /// GigE Vision version major number + uint16_t versionMajor; + /// GigE Vision version minor number + uint16_t versionMinor; + /// Device mode + uint32_t deviceMode; + /// Reserved + uint16_t reserved; + /// MAC address + uint8_t macAddress[6]; + /// Supported IP configuration options + uint32_t supportedIpConfigOptions; + /// Current IP configuration + uint32_t ipConfigCurrent; + /// Reserved + uint8_t reserved2[12]; + /// IP address + uint32_t ipAddress; + /// Reserved + uint8_t reserved3[12]; + /// Subnet mask + uint32_t subnetMask; + /// Reserved + uint8_t reserved4[12]; + /// Default gateway + uint32_t defaultGateway; + /// Manufacturer name + char manufacturerName[32]; + /// Model name + char modelName[32]; + /// Device version + char deviceVersion[32]; + /// Manufacturer specific information + char manufacturerSpecificInformation[48]; + /// Serial number + char serialNumber[16]; + /// User defined name + char userDefinedName[16]; + }; +#pragma pack(pop) + static_assert(sizeof(gvcp_discovery_body) == 248, "gvcp_discovery_body size is not 248 bytes"); + + gvcp_discovery_body* getGvcpDiscoveryBody() const + { + return reinterpret_cast(m_Data + sizeof(gvcp_ack_header)); + } + }; + + /// @class GvcpForceIpRequestLayer + /// Represents a GVCP force IP command (FORCEIP_CMD) + class GvcpForceIpRequestLayer : public GvcpRequestLayer + { + public: + /// A constructor that creates the layer from an existing packet raw data + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + GvcpForceIpRequestLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : GvcpRequestLayer(data, dataLen, prevLayer, packet) + {} + + /// A constructor that creates a new GVCP force IP command + /// @param[in] macAddress The MAC address of the device to configure + /// @param[in] ipAddress The IP address to force on the device + /// @param[in] subnetMask The subnet mask to force on the device + /// @param[in] gatewayIpAddress The default gateway to force on the device + /// @param[in] flag The flag, optional + /// @param[in] requestId The request ID, optional. The spec requires a non-zero value + GvcpForceIpRequestLayer(const MacAddress& macAddress, const IPv4Address& ipAddress, + const IPv4Address& subnetMask, const IPv4Address& gatewayIpAddress, GvcpFlag flag = 0, + uint16_t requestId = 1); + + // cppcheck-suppress duplInheritedMember + /// A static method that checks whether the data is a valid GVCP request which is large enough to hold a GVCP + /// force IP command + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @return True if the data can be parsed as a GVCP force IP command, false otherwise + static bool isDataValid(const uint8_t* data, size_t dataLen); + + /// @return The MAC address of the device to configure + MacAddress getMacAddress() const; + + /// Set the MAC address of the device to configure + /// @param[in] macAddress The MAC address to set + void setMacAddress(const MacAddress& macAddress); + + /// @return The IP address to force on the device + IPv4Address getIpAddress() const; + + /// Set the IP address to force on the device + /// @param[in] ipAddress The IP address to set + void setIpAddress(const IPv4Address& ipAddress); + + /// @return The subnet mask to force on the device + IPv4Address getSubnetMask() const; + + /// Set the subnet mask to force on the device + /// @param[in] subnetMask The subnet mask to set + void setSubnetMask(const IPv4Address& subnetMask); + + /// @return The default gateway IP address to force on the device + IPv4Address getGatewayIpAddress() const; + + /// Set the default gateway IP address to force on the device + /// @param[in] gatewayIpAddress The default gateway IP address to set + void setGatewayIpAddress(const IPv4Address& gatewayIpAddress); + + // Overridden methods + + /// @return The size of the GVCP header and the force IP command body + size_t getHeaderLen() const override + { + return sizeof(gvcp_request_header) + sizeof(gvcp_forceip_body); + } + + /// @struct SerializedFields + /// Fields written by GvcpForceIpRequestLayer's serializeLayer(), in addition to + /// GvcpRequestLayer::SerializedFields. + struct SerializedFields : GvcpRequestLayer::SerializedFields + { + /// @return All field descriptors for GvcpForceIpRequestLayer + static std::vector all() + { + auto result = GvcpRequestLayer::SerializedFields::all(); + std::initializer_list extra{ MacAddress, IpAddress, SubnetMask, GatewayIpAddress }; + std::copy(extra.begin(), extra.end(), std::back_inserter(result)); + return result; + } + + /// @brief MAC address of the device to configure, as a string + static const FieldDescriptor MacAddress; + + /// @brief IP address to force on the device, as a string + static const FieldDescriptor IpAddress; + + /// @brief Subnet mask to force on the device, as a string + static const FieldDescriptor SubnetMask; + + /// @brief Default gateway IP address to force on the device, as a string + static const FieldDescriptor GatewayIpAddress; + + // cppcheck-suppress duplInheritedMember + /// Maximum field ID used by GvcpForceIpRequestLayer + static constexpr uint16_t MaxID = GvcpRequestLayer::SerializedFields::MaxID + 4; + }; + + protected: + void serializeLayer(ObjectScope& serializer) const override; + + private: +#pragma pack(push, 1) + /// GVCP force IP command body, see the spec "16.2 FORCEIP". The data is stored as big-endian. + struct gvcp_forceip_body + { + /// Reserved + uint8_t reserved[2]; + /// MAC address + uint8_t macAddress[6]; + /// Reserved + uint8_t reserved2[12]; + /// IP address + uint32_t ipAddress; + /// Reserved + uint8_t reserved3[12]; + /// Subnet mask + uint32_t subnetMask; + /// Reserved + uint8_t reserved4[12]; + /// Default gateway + uint32_t gateway; + }; +#pragma pack(pop) + static_assert(sizeof(gvcp_forceip_body) == 56, "gvcp_forceip_body size is not 56 bytes"); + + gvcp_forceip_body* getGvcpForceIpBody() const + { + return reinterpret_cast(m_Data + sizeof(gvcp_request_header)); + } + }; + + /// @class GvcpForceIpAcknowledgeLayer + /// Represents a GVCP force IP acknowledge (FORCEIP_ACK) + class GvcpForceIpAcknowledgeLayer : public GvcpAcknowledgeLayer + { + public: + /// A constructor that creates the layer from an existing packet raw data + /// @param[in] data A pointer to the raw data + /// @param[in] dataLen Size of the data in bytes + /// @param[in] prevLayer A pointer to the previous layer + /// @param[in] packet A pointer to the Packet instance where layer will be stored in + GvcpForceIpAcknowledgeLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + : GvcpAcknowledgeLayer(data, dataLen, prevLayer, packet) + {} + + /// A constructor that creates a new GVCP force IP acknowledge + /// @param[in] status The response status, optional + /// @param[in] ackId The acknowledge ID, optional + explicit GvcpForceIpAcknowledgeLayer(GvcpResponseStatus status = GvcpResponseStatus::Success, + uint16_t ackId = 0) + : GvcpAcknowledgeLayer(status, GvcpCommand::ForceIpAck, ackId) + {} + }; +} // namespace pcpp diff --git a/Packet++/header/ProtocolType.h b/Packet++/header/ProtocolType.h index 0de4ffaf55..2815466c44 100644 --- a/Packet++/header/ProtocolType.h +++ b/Packet++/header/ProtocolType.h @@ -266,6 +266,9 @@ namespace pcpp /// QUIC v1 protocol const ProtocolType QUICv1 = 64; + /// GigE Vision Control Protocol (GVCP) + const ProtocolType GVCP = 65; + /// @} /// Convert a protocol type to its string representation. @@ -405,6 +408,8 @@ namespace pcpp return "MySQL"; case QUICv1: return "QUICv1"; + case GVCP: + return "GVCP"; default: throw std::invalid_argument("Unknown protocol type"); } diff --git a/Packet++/src/GvcpLayer.cpp b/Packet++/src/GvcpLayer.cpp new file mode 100644 index 0000000000..2b24d8ab19 --- /dev/null +++ b/Packet++/src/GvcpLayer.cpp @@ -0,0 +1,704 @@ +#include "GvcpLayer.h" +#include "PayloadLayer.h" +#include "SystemUtils.h" +#include +#include +#include +#include +#include + +namespace pcpp +{ + namespace + { + using GvcpCommand = GvcpLayer::GvcpCommand; + using GvcpResponseStatus = GvcpAcknowledgeLayer::GvcpResponseStatus; + using GvcpVersion = GvcpDiscoveryAcknowledgeLayer::GvcpVersion; + + constexpr uint8_t gvcpMagicNumber = 0x42; + constexpr GvcpFlag gvcpAcknowledgeFlag = 0x01; + constexpr GvcpFlag gvcpAllowBroadcastAckFlag = 0x10; + + /// @return True if the value is an acknowledge value. Command values are even and each acknowledge value is + /// the matching command value + 1 + bool isAcknowledgeValue(GvcpCommand command) + { + return (static_cast(command) & 0x01) != 0; + } + + /// @return The name of a command defined by the spec, or nullptr if the value isn't defined + constexpr const char* getGvcpCommandName(GvcpCommand command) + { + switch (command) + { + case GvcpCommand::DiscoveredCmd: + return "DiscoveredCmd"; + case GvcpCommand::DiscoveredAck: + return "DiscoveredAck"; + case GvcpCommand::ForceIpCmd: + return "ForceIpCmd"; + case GvcpCommand::ForceIpAck: + return "ForceIpAck"; + case GvcpCommand::PacketResendCmd: + return "PacketResendCmd"; + case GvcpCommand::PacketResendAck: + return "PacketResendAck"; + case GvcpCommand::ReadRegCmd: + return "ReadRegCmd"; + case GvcpCommand::ReadRegAck: + return "ReadRegAck"; + case GvcpCommand::WriteRegCmd: + return "WriteRegCmd"; + case GvcpCommand::WriteRegAck: + return "WriteRegAck"; + case GvcpCommand::ReadMemCmd: + return "ReadMemCmd"; + case GvcpCommand::ReadMemAck: + return "ReadMemAck"; + case GvcpCommand::WriteMemCmd: + return "WriteMemCmd"; + case GvcpCommand::WriteMemAck: + return "WriteMemAck"; + case GvcpCommand::PendingAck: + return "PendingAck"; + case GvcpCommand::EventCmd: + return "EventCmd"; + case GvcpCommand::EventAck: + return "EventAck"; + case GvcpCommand::EventDataCmd: + return "EventDataCmd"; + case GvcpCommand::EventDataAck: + return "EventDataAck"; + case GvcpCommand::ActionCmd: + return "ActionCmd"; + case GvcpCommand::ActionAck: + return "ActionAck"; + default: + return nullptr; + } + } + + /// @return The name of a status defined by the spec, or nullptr if the value isn't defined + constexpr const char* getGvcpResponseStatusName(GvcpResponseStatus status) + { + switch (status) + { + case GvcpResponseStatus::Success: + return "Success"; + case GvcpResponseStatus::PacketResend: + return "PacketResend"; + case GvcpResponseStatus::NotImplemented: + return "NotImplemented"; + case GvcpResponseStatus::InvalidParameter: + return "InvalidParameter"; + case GvcpResponseStatus::InvalidAddress: + return "InvalidAddress"; + case GvcpResponseStatus::WriteProtect: + return "WriteProtect"; + case GvcpResponseStatus::BadAlignment: + return "BadAlignment"; + case GvcpResponseStatus::AccessDenied: + return "AccessDenied"; + case GvcpResponseStatus::Busy: + return "Busy"; + case GvcpResponseStatus::LocalProblem: + return "LocalProblem"; + case GvcpResponseStatus::MsgMismatch: + return "MsgMismatch"; + case GvcpResponseStatus::InvalidProtocol: + return "InvalidProtocol"; + case GvcpResponseStatus::NoMsg: + return "NoMsg"; + case GvcpResponseStatus::PacketUnavailable: + return "PacketUnavailable"; + case GvcpResponseStatus::DataOverrun: + return "DataOverrun"; + case GvcpResponseStatus::InvalidHeader: + return "InvalidHeader"; + case GvcpResponseStatus::WrongConfig: + return "WrongConfig"; + case GvcpResponseStatus::PacketNotYetAvailable: + return "PacketNotYetAvailable"; + case GvcpResponseStatus::PacketAndPrevRemovedFromMemory: + return "PacketAndPrevRemovedFromMemory"; + case GvcpResponseStatus::PacketRemovedFromMemory: + return "PacketRemovedFromMemory"; + case GvcpResponseStatus::NoRefTime: + return "NoRefTime"; + case GvcpResponseStatus::PacketTemporarilyUnavailable: + return "PacketTemporarilyUnavailable"; + case GvcpResponseStatus::Overflow: + return "Overflow"; + case GvcpResponseStatus::ActionLate: + return "ActionLate"; + case GvcpResponseStatus::LeaderTrailerOverflow: + return "LeaderTrailerOverflow"; + case GvcpResponseStatus::Error: + return "Error"; + default: + return nullptr; + } + } + + GvcpCommand toGvcpCommand(uint16_t value) + { + const auto command = static_cast(value); + return getGvcpCommandName(command) != nullptr ? command : GvcpCommand::Unknown; + } + + GvcpResponseStatus toGvcpResponseStatus(uint16_t value) + { + const auto status = static_cast(value); + return getGvcpResponseStatusName(status) != nullptr ? status : GvcpResponseStatus::Unknown; + } + + const char* commandToString(GvcpCommand command) + { + const char* name = getGvcpCommandName(command); + return name != nullptr ? name : "Unknown"; + } + + const char* statusToString(GvcpResponseStatus status) + { + const char* name = getGvcpResponseStatusName(status); + return name != nullptr ? name : "Unknown"; + } + + /// Read a fixed size char field which isn't necessarily null-terminated + template std::string fieldToString(const char (&field)[N]) + { + return std::string(field, std::find(field, field + N, '\0')); + } + + /// Write a string to a fixed size char field, always leaving room for the null terminator + template void stringToField(const std::string& value, char (&field)[N]) + { + memset(field, 0, N); + memcpy(field, value.data(), std::min(value.size(), N - 1)); + } + } // namespace + + std::ostream& operator<<(std::ostream& os, GvcpCommand command) + { + return os << commandToString(command); + } + + std::ostream& operator<<(std::ostream& os, GvcpResponseStatus status) + { + return os << statusToString(status); + } + + // -------- Class GvcpLayer ----------------- + + uint16_t GvcpLayer::getRawCommand(const uint8_t* data) + { + static_assert(offsetof(GvcpRequestLayer::gvcp_request_header, command) == + offsetof(GvcpAcknowledgeLayer::gvcp_ack_header, command), + "The command must have the same offset in both GVCP headers"); + return netToHost16(reinterpret_cast(data)->command); + } + + GvcpLayer* GvcpLayer::parseGvcpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) + { + const bool isRequest = GvcpRequestLayer::isDataValid(data, dataLen); + if (!isRequest && !GvcpAcknowledgeLayer::isDataValid(data, dataLen)) + return nullptr; + + const GvcpCommand command = toGvcpCommand(getRawCommand(data)); + + if (isRequest) + { + if (command == GvcpCommand::DiscoveredCmd) + return new GvcpDiscoveryRequestLayer(data, dataLen, prevLayer, packet); + if (command == GvcpCommand::ForceIpCmd && GvcpForceIpRequestLayer::isDataValid(data, dataLen)) + return new GvcpForceIpRequestLayer(data, dataLen, prevLayer, packet); + return new GvcpRequestLayer(data, dataLen, prevLayer, packet); + } + + if (command == GvcpCommand::DiscoveredAck && GvcpDiscoveryAcknowledgeLayer::isDataValid(data, dataLen)) + return new GvcpDiscoveryAcknowledgeLayer(data, dataLen, prevLayer, packet); + if (command == GvcpCommand::ForceIpAck) + return new GvcpForceIpAcknowledgeLayer(data, dataLen, prevLayer, packet); + return new GvcpAcknowledgeLayer(data, dataLen, prevLayer, packet); + } + + void GvcpLayer::initLayer(size_t dataLen) + { + allocData(dataLen); + m_Protocol = GVCP; + } + + void GvcpLayer::parseNextLayer() + { + const size_t headerLen = getHeaderLen(); + if (m_DataLen <= headerLen) + return; + + constructNextLayer(m_Data + headerLen, m_DataLen - headerLen); + } + + const FieldDescriptor GvcpLayer::SerializedFields::Command{ Layer::SerializedFields::MaxID + 1, "command" }; + const FieldDescriptor GvcpLayer::SerializedFields::CommandName{ Layer::SerializedFields::MaxID + 2, "commandName" }; + + void GvcpLayer::serializeLayer(ObjectScope& serializer) const + { + serializer.writeField(SerializedFields::Command, getRawCommand(m_Data)); + serializer.writeField(SerializedFields::CommandName, commandToString(getCommand())); + } + + // -------- Class GvcpRequestLayer ----------------- + + GvcpRequestLayer::GvcpRequestLayer(GvcpCommand command, GvcpFlag flag, uint16_t requestId, size_t bodyLen) + { + if (isAcknowledgeValue(command)) + throw std::invalid_argument("A GVCP request can't be created with an acknowledge value"); + + initLayer(sizeof(gvcp_request_header) + bodyLen); + + auto* header = getGvcpHeader(); + header->magicNumber = gvcpMagicNumber; + header->flag = flag; + header->command = hostToNet16(static_cast(command)); + header->dataSize = hostToNet16(static_cast(bodyLen)); + header->requestId = hostToNet16(requestId); + } + + bool GvcpRequestLayer::isDataValid(const uint8_t* data, size_t dataLen) + { + return canReinterpretAs(data, dataLen) && data[0] == gvcpMagicNumber; + } + + GvcpFlag GvcpRequestLayer::getFlag() const + { + return getGvcpHeader()->flag; + } + + void GvcpRequestLayer::setFlag(GvcpFlag flag) + { + getGvcpHeader()->flag = flag; + } + + bool GvcpRequestLayer::hasAcknowledgeFlag() const + { + return (getGvcpHeader()->flag & gvcpAcknowledgeFlag) != 0; + } + + uint16_t GvcpRequestLayer::getRequestId() const + { + return netToHost16(getGvcpHeader()->requestId); + } + + void GvcpRequestLayer::setRequestId(uint16_t requestId) + { + getGvcpHeader()->requestId = hostToNet16(requestId); + } + + GvcpCommand GvcpRequestLayer::getCommand() const + { + return toGvcpCommand(netToHost16(getGvcpHeader()->command)); + } + + uint16_t GvcpRequestLayer::getDataSize() const + { + return netToHost16(getGvcpHeader()->dataSize); + } + + void GvcpRequestLayer::computeCalculateFields() + { + getGvcpHeader()->dataSize = hostToNet16(static_cast(m_DataLen - sizeof(gvcp_request_header))); + } + + std::string GvcpRequestLayer::toString() const + { + std::ostringstream ss; + ss << "GVCP Request Layer, Command: " << getCommand() << ", Request ID: " << getRequestId(); + return ss.str(); + } + + const FieldDescriptor GvcpRequestLayer::SerializedFields::Flag{ GvcpLayer::SerializedFields::MaxID + 1, "flag" }; + const FieldDescriptor GvcpRequestLayer::SerializedFields::AcknowledgeRequired{ + GvcpLayer::SerializedFields::MaxID + 2, "acknowledgeRequired" + }; + const FieldDescriptor GvcpRequestLayer::SerializedFields::RequestId{ GvcpLayer::SerializedFields::MaxID + 3, + "requestId" }; + + void GvcpRequestLayer::serializeLayer(ObjectScope& serializer) const + { + GvcpLayer::serializeLayer(serializer); + serializer.writeField(SerializedFields::Flag, getFlag()); + serializer.writeField(SerializedFields::AcknowledgeRequired, hasAcknowledgeFlag()); + serializer.writeField(SerializedFields::RequestId, getRequestId()); + } + + // -------- Class GvcpAcknowledgeLayer ----------------- + + GvcpAcknowledgeLayer::GvcpAcknowledgeLayer(GvcpResponseStatus status, GvcpCommand command, uint16_t ackId, + size_t bodyLen) + { + if (!isAcknowledgeValue(command)) + throw std::invalid_argument("A GVCP acknowledge can't be created with a command value"); + + initLayer(sizeof(gvcp_ack_header) + bodyLen); + + auto* header = getGvcpHeader(); + header->status = hostToNet16(static_cast(status)); + header->command = hostToNet16(static_cast(command)); + header->dataSize = hostToNet16(static_cast(bodyLen)); + header->ackId = hostToNet16(ackId); + } + + bool GvcpAcknowledgeLayer::isDataValid(const uint8_t* data, size_t dataLen) + { + return canReinterpretAs(data, dataLen); + } + + GvcpResponseStatus GvcpAcknowledgeLayer::getStatus() const + { + return toGvcpResponseStatus(netToHost16(getGvcpHeader()->status)); + } + + void GvcpAcknowledgeLayer::setStatus(GvcpResponseStatus status) + { + getGvcpHeader()->status = hostToNet16(static_cast(status)); + } + + uint16_t GvcpAcknowledgeLayer::getAckId() const + { + return netToHost16(getGvcpHeader()->ackId); + } + + void GvcpAcknowledgeLayer::setAckId(uint16_t ackId) + { + getGvcpHeader()->ackId = hostToNet16(ackId); + } + + GvcpCommand GvcpAcknowledgeLayer::getCommand() const + { + return toGvcpCommand(netToHost16(getGvcpHeader()->command)); + } + + uint16_t GvcpAcknowledgeLayer::getDataSize() const + { + return netToHost16(getGvcpHeader()->dataSize); + } + + void GvcpAcknowledgeLayer::computeCalculateFields() + { + getGvcpHeader()->dataSize = hostToNet16(static_cast(m_DataLen - sizeof(gvcp_ack_header))); + } + + std::string GvcpAcknowledgeLayer::toString() const + { + std::ostringstream ss; + ss << "GVCP Acknowledge Layer, Command: " << getCommand() << ", Acknowledge ID: " << getAckId() + << ", Status: " << getStatus(); + return ss.str(); + } + + const FieldDescriptor GvcpAcknowledgeLayer::SerializedFields::Status{ GvcpLayer::SerializedFields::MaxID + 1, + "status" }; + const FieldDescriptor GvcpAcknowledgeLayer::SerializedFields::StatusName{ GvcpLayer::SerializedFields::MaxID + 2, + "statusName" }; + const FieldDescriptor GvcpAcknowledgeLayer::SerializedFields::AckId{ GvcpLayer::SerializedFields::MaxID + 3, + "ackId" }; + + void GvcpAcknowledgeLayer::serializeLayer(ObjectScope& serializer) const + { + GvcpLayer::serializeLayer(serializer); + serializer.writeField(SerializedFields::Status, netToHost16(getGvcpHeader()->status)); + serializer.writeField(SerializedFields::StatusName, statusToString(getStatus())); + serializer.writeField(SerializedFields::AckId, getAckId()); + } + + // -------- Class GvcpDiscoveryRequestLayer ----------------- + + GvcpDiscoveryRequestLayer::GvcpDiscoveryRequestLayer(bool allowBroadcastAck, bool acknowledgeRequired, + uint16_t requestId) + : GvcpRequestLayer(GvcpCommand::DiscoveredCmd, + static_cast((allowBroadcastAck ? gvcpAllowBroadcastAckFlag : 0) | + (acknowledgeRequired ? gvcpAcknowledgeFlag : 0)), + requestId) + {} + + bool GvcpDiscoveryRequestLayer::hasAllowBroadcastAckFlag() const + { + return (getFlag() & gvcpAllowBroadcastAckFlag) != 0; + } + + void GvcpDiscoveryRequestLayer::setAllowBroadcastAckFlag(bool allowBroadcastAck) + { + setFlag(static_cast(allowBroadcastAck ? getFlag() | gvcpAllowBroadcastAckFlag + : getFlag() & ~gvcpAllowBroadcastAckFlag)); + } + + const FieldDescriptor GvcpDiscoveryRequestLayer::SerializedFields::AllowBroadcastAck{ + GvcpRequestLayer::SerializedFields::MaxID + 1, "allowBroadcastAck" + }; + + void GvcpDiscoveryRequestLayer::serializeLayer(ObjectScope& serializer) const + { + GvcpRequestLayer::serializeLayer(serializer); + serializer.writeField(SerializedFields::AllowBroadcastAck, hasAllowBroadcastAckFlag()); + } + + // -------- Class GvcpDiscoveryAcknowledgeLayer ----------------- + + GvcpDiscoveryAcknowledgeLayer::GvcpDiscoveryAcknowledgeLayer(GvcpResponseStatus status, uint16_t ackId) + : GvcpAcknowledgeLayer(status, GvcpCommand::DiscoveredAck, ackId, sizeof(gvcp_discovery_body)) + {} + + bool GvcpDiscoveryAcknowledgeLayer::isDataValid(const uint8_t* data, size_t dataLen) + { + return GvcpAcknowledgeLayer::isDataValid(data, dataLen) && + dataLen >= sizeof(gvcp_ack_header) + sizeof(gvcp_discovery_body); + } + + GvcpVersion GvcpDiscoveryAcknowledgeLayer::getVersion() const + { + auto* body = getGvcpDiscoveryBody(); + return { netToHost16(body->versionMajor), netToHost16(body->versionMinor) }; + } + + void GvcpDiscoveryAcknowledgeLayer::setVersion(const GvcpVersion& version) + { + auto* body = getGvcpDiscoveryBody(); + body->versionMajor = hostToNet16(version.major); + body->versionMinor = hostToNet16(version.minor); + } + + MacAddress GvcpDiscoveryAcknowledgeLayer::getMacAddress() const + { + return MacAddress(getGvcpDiscoveryBody()->macAddress); + } + + void GvcpDiscoveryAcknowledgeLayer::setMacAddress(const MacAddress& macAddress) + { + macAddress.copyTo(getGvcpDiscoveryBody()->macAddress, sizeof(gvcp_discovery_body::macAddress)); + } + + IPv4Address GvcpDiscoveryAcknowledgeLayer::getIpAddress() const + { + return { getGvcpDiscoveryBody()->ipAddress }; + } + + void GvcpDiscoveryAcknowledgeLayer::setIpAddress(const IPv4Address& ipAddress) + { + getGvcpDiscoveryBody()->ipAddress = ipAddress.toInt(); + } + + IPv4Address GvcpDiscoveryAcknowledgeLayer::getSubnetMask() const + { + return { getGvcpDiscoveryBody()->subnetMask }; + } + + void GvcpDiscoveryAcknowledgeLayer::setSubnetMask(const IPv4Address& subnetMask) + { + getGvcpDiscoveryBody()->subnetMask = subnetMask.toInt(); + } + + IPv4Address GvcpDiscoveryAcknowledgeLayer::getGatewayIpAddress() const + { + return { getGvcpDiscoveryBody()->defaultGateway }; + } + + void GvcpDiscoveryAcknowledgeLayer::setGatewayIpAddress(const IPv4Address& gatewayIpAddress) + { + getGvcpDiscoveryBody()->defaultGateway = gatewayIpAddress.toInt(); + } + + std::string GvcpDiscoveryAcknowledgeLayer::getManufacturerName() const + { + return fieldToString(getGvcpDiscoveryBody()->manufacturerName); + } + + void GvcpDiscoveryAcknowledgeLayer::setManufacturerName(const std::string& manufacturerName) + { + stringToField(manufacturerName, getGvcpDiscoveryBody()->manufacturerName); + } + + std::string GvcpDiscoveryAcknowledgeLayer::getModelName() const + { + return fieldToString(getGvcpDiscoveryBody()->modelName); + } + + void GvcpDiscoveryAcknowledgeLayer::setModelName(const std::string& modelName) + { + stringToField(modelName, getGvcpDiscoveryBody()->modelName); + } + + std::string GvcpDiscoveryAcknowledgeLayer::getDeviceVersion() const + { + return fieldToString(getGvcpDiscoveryBody()->deviceVersion); + } + + void GvcpDiscoveryAcknowledgeLayer::setDeviceVersion(const std::string& deviceVersion) + { + stringToField(deviceVersion, getGvcpDiscoveryBody()->deviceVersion); + } + + std::string GvcpDiscoveryAcknowledgeLayer::getManufacturerSpecificInformation() const + { + return fieldToString(getGvcpDiscoveryBody()->manufacturerSpecificInformation); + } + + void GvcpDiscoveryAcknowledgeLayer::setManufacturerSpecificInformation(const std::string& info) + { + stringToField(info, getGvcpDiscoveryBody()->manufacturerSpecificInformation); + } + + std::string GvcpDiscoveryAcknowledgeLayer::getSerialNumber() const + { + return fieldToString(getGvcpDiscoveryBody()->serialNumber); + } + + void GvcpDiscoveryAcknowledgeLayer::setSerialNumber(const std::string& serialNumber) + { + stringToField(serialNumber, getGvcpDiscoveryBody()->serialNumber); + } + + std::string GvcpDiscoveryAcknowledgeLayer::getUserDefinedName() const + { + return fieldToString(getGvcpDiscoveryBody()->userDefinedName); + } + + void GvcpDiscoveryAcknowledgeLayer::setUserDefinedName(const std::string& userDefinedName) + { + stringToField(userDefinedName, getGvcpDiscoveryBody()->userDefinedName); + } + + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::VersionMajor{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 1, "versionMajor" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::VersionMinor{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 2, "versionMinor" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::MacAddress{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 3, "macAddress" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::IpAddress{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 4, "ipAddress" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::SubnetMask{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 5, "subnetMask" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::GatewayIpAddress{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 6, "gatewayIpAddress" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::ManufacturerName{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 7, "manufacturerName" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::ModelName{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 8, "modelName" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::DeviceVersion{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 9, "deviceVersion" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::ManufacturerSpecificInformation{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 10, "manufacturerSpecificInformation" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::SerialNumber{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 11, "serialNumber" + }; + const FieldDescriptor GvcpDiscoveryAcknowledgeLayer::SerializedFields::UserDefinedName{ + GvcpAcknowledgeLayer::SerializedFields::MaxID + 12, "userDefinedName" + }; + + void GvcpDiscoveryAcknowledgeLayer::serializeLayer(ObjectScope& serializer) const + { + GvcpAcknowledgeLayer::serializeLayer(serializer); + const GvcpVersion version = getVersion(); + serializer.writeField(SerializedFields::VersionMajor, version.major); + serializer.writeField(SerializedFields::VersionMinor, version.minor); + serializer.writeField(SerializedFields::MacAddress, getMacAddress().toString()); + serializer.writeField(SerializedFields::IpAddress, getIpAddress().toString()); + serializer.writeField(SerializedFields::SubnetMask, getSubnetMask().toString()); + serializer.writeField(SerializedFields::GatewayIpAddress, getGatewayIpAddress().toString()); + serializer.writeField(SerializedFields::ManufacturerName, getManufacturerName()); + serializer.writeField(SerializedFields::ModelName, getModelName()); + serializer.writeField(SerializedFields::DeviceVersion, getDeviceVersion()); + serializer.writeField(SerializedFields::ManufacturerSpecificInformation, getManufacturerSpecificInformation()); + serializer.writeField(SerializedFields::SerialNumber, getSerialNumber()); + serializer.writeField(SerializedFields::UserDefinedName, getUserDefinedName()); + } + + // -------- Class GvcpForceIpRequestLayer ----------------- + + GvcpForceIpRequestLayer::GvcpForceIpRequestLayer(const MacAddress& macAddress, const IPv4Address& ipAddress, + const IPv4Address& subnetMask, const IPv4Address& gatewayIpAddress, + GvcpFlag flag, uint16_t requestId) + : GvcpRequestLayer(GvcpCommand::ForceIpCmd, flag, requestId, sizeof(gvcp_forceip_body)) + { + setMacAddress(macAddress); + setIpAddress(ipAddress); + setSubnetMask(subnetMask); + setGatewayIpAddress(gatewayIpAddress); + } + + bool GvcpForceIpRequestLayer::isDataValid(const uint8_t* data, size_t dataLen) + { + return GvcpRequestLayer::isDataValid(data, dataLen) && + dataLen >= sizeof(gvcp_request_header) + sizeof(gvcp_forceip_body); + } + + MacAddress GvcpForceIpRequestLayer::getMacAddress() const + { + return MacAddress(getGvcpForceIpBody()->macAddress); + } + + void GvcpForceIpRequestLayer::setMacAddress(const MacAddress& macAddress) + { + macAddress.copyTo(getGvcpForceIpBody()->macAddress, sizeof(gvcp_forceip_body::macAddress)); + } + + IPv4Address GvcpForceIpRequestLayer::getIpAddress() const + { + return { getGvcpForceIpBody()->ipAddress }; + } + + void GvcpForceIpRequestLayer::setIpAddress(const IPv4Address& ipAddress) + { + getGvcpForceIpBody()->ipAddress = ipAddress.toInt(); + } + + IPv4Address GvcpForceIpRequestLayer::getSubnetMask() const + { + return { getGvcpForceIpBody()->subnetMask }; + } + + void GvcpForceIpRequestLayer::setSubnetMask(const IPv4Address& subnetMask) + { + getGvcpForceIpBody()->subnetMask = subnetMask.toInt(); + } + + IPv4Address GvcpForceIpRequestLayer::getGatewayIpAddress() const + { + return { getGvcpForceIpBody()->gateway }; + } + + void GvcpForceIpRequestLayer::setGatewayIpAddress(const IPv4Address& gatewayIpAddress) + { + getGvcpForceIpBody()->gateway = gatewayIpAddress.toInt(); + } + const FieldDescriptor GvcpForceIpRequestLayer::SerializedFields::MacAddress{ + GvcpRequestLayer::SerializedFields::MaxID + 1, "macAddress" + }; + const FieldDescriptor GvcpForceIpRequestLayer::SerializedFields::IpAddress{ + GvcpRequestLayer::SerializedFields::MaxID + 2, "ipAddress" + }; + const FieldDescriptor GvcpForceIpRequestLayer::SerializedFields::SubnetMask{ + GvcpRequestLayer::SerializedFields::MaxID + 3, "subnetMask" + }; + const FieldDescriptor GvcpForceIpRequestLayer::SerializedFields::GatewayIpAddress{ + GvcpRequestLayer::SerializedFields::MaxID + 4, "gatewayIpAddress" + }; + + void GvcpForceIpRequestLayer::serializeLayer(ObjectScope& serializer) const + { + GvcpRequestLayer::serializeLayer(serializer); + serializer.writeField(SerializedFields::MacAddress, getMacAddress().toString()); + serializer.writeField(SerializedFields::IpAddress, getIpAddress().toString()); + serializer.writeField(SerializedFields::SubnetMask, getSubnetMask().toString()); + serializer.writeField(SerializedFields::GatewayIpAddress, getGatewayIpAddress().toString()); + } + +} // namespace pcpp diff --git a/Packet++/src/UdpLayer.cpp b/Packet++/src/UdpLayer.cpp index 9a7bc03d55..14d95377d7 100644 --- a/Packet++/src/UdpLayer.cpp +++ b/Packet++/src/UdpLayer.cpp @@ -13,6 +13,7 @@ #include "SipLayer.h" #include "RadiusLayer.h" #include "GtpLayer.h" +#include "GvcpLayer.h" #include "NtpLayer.h" #include "SomeIpLayer.h" #include "WakeOnLanLayer.h" @@ -170,6 +171,10 @@ namespace pcpp tryConstructNextLayerFromFactoryWithFallback(WireGuardLayer::parseWireGuardLayer, udpData, udpDataLen); } + else if (GvcpLayer::isGvcpPort(portDst) || GvcpLayer::isGvcpPort(portSrc)) + { + tryConstructNextLayerFromFactoryWithFallback(GvcpLayer::parseGvcpLayer, udpData, udpDataLen); + } // If a valid layer was found, return immediately if (hasNextLayer()) diff --git a/README.md b/README.md index a658994a25..76ff88a9d5 100644 --- a/README.md +++ b/README.md @@ -277,21 +277,22 @@ PcapPlusPlus currently supports parsing, editing and creation of packets of the 42. DNS 43. DoIP 44. FTP -45. HTTP headers (request & response) -46. LDAP -47. Modbus -48. MySQL - parsing only (no editing capabilities) -49. NTP (v3, v4) -50. PEM decoder and encoder -51. PostgreSQL Wire Protocol (PGWire) - parsing only (no editing capabilities) -52. Radius -53. S7 Communication (S7comm) -54. SMTP -55. SOME/IP -56. SSH - parsing only (no editing capabilities) -57. Telnet - parsing only (no editing capabilities) -58. X509 certificates - parsing only (no editing capabilities) -59. Generic payload +45. GVCP +46. HTTP headers (request & response) +47. LDAP +48. Modbus +49. MySQL - parsing only (no editing capabilities) +50. NTP (v3, v4) +51. PEM decoder and encoder +52. PostgreSQL Wire Protocol (PGWire) - parsing only (no editing capabilities) +53. Radius +54. S7 Communication (S7comm) +55. SMTP +56. SOME/IP +57. SSH - parsing only (no editing capabilities) +58. Telnet - parsing only (no editing capabilities) +59. X509 certificates - parsing only (no editing capabilities) +60. Generic payload ## DPDK And PF_RING Support diff --git a/Tests/Packet++Test/CMakeLists.txt b/Tests/Packet++Test/CMakeLists.txt index 65155c3dca..e5777498b2 100644 --- a/Tests/Packet++Test/CMakeLists.txt +++ b/Tests/Packet++Test/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable( Tests/FtpTests.cpp Tests/GreTests.cpp Tests/GtpTests.cpp + Tests/GvcpTests.cpp Tests/HttpTests.cpp Tests/IcmpTests.cpp Tests/IcmpV6Tests.cpp diff --git a/Tests/Packet++Test/PacketExamples/gvcp_all.pcap b/Tests/Packet++Test/PacketExamples/gvcp_all.pcap new file mode 100644 index 0000000000..988de4f26f Binary files /dev/null and b/Tests/Packet++Test/PacketExamples/gvcp_all.pcap differ diff --git a/Tests/Packet++Test/PacketExamples/gvcp_discovery_ack.dat b/Tests/Packet++Test/PacketExamples/gvcp_discovery_ack.dat new file mode 100644 index 0000000000..ed8778944f --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_discovery_ack.dat @@ -0,0 +1 @@ +aabbccddeeff00112233445508004500011c7cc340004011ec6fac1c3c64ac1c3c010f74945b010812410000000300f800010002000180000000000000044beab0b40000000700000005000000000000000000000000ac1c3c64000000000000000000000000ffffff00000000000000000000000000ac1c3c0156656e646f7230310000000000000000000000000000000000000000000000004142434445203344205363616e6e657220285457290000000000000000000000584c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005858582d30303500000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_discovery_cmd.dat b/Tests/Packet++Test/PacketExamples/gvcp_discovery_cmd.dat new file mode 100644 index 0000000000..b85c8b6e2f --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_discovery_cmd.dat @@ -0,0 +1 @@ +aabbccddeeff001122334455080045000024a98040004011a92bac1c3c01ffffffffeb320f740010e83e4211000200004de6 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_forceip_ack.dat b/Tests/Packet++Test/PacketExamples/gvcp_forceip_ack.dat new file mode 100644 index 0000000000..bc8bbbfcc6 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_forceip_ack.dat @@ -0,0 +1 @@ +aabbccddeeff001122334455080045000024000800008011b36ec0a80501c0a801010f740f7400100000000000050000225300000000000000000000 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd.dat b/Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd.dat new file mode 100644 index 0000000000..d777bb2b75 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd.dat @@ -0,0 +1 @@ +aabbccddeeff00112233445508004500005c06a7400040117241c0a80101ffffffff0f740f740048c202420100040038225300008ce9b40163b2000000000000000000000000c0a80501000000000000000000000000ffff000000000000000000000000000000000000 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd_short.dat b/Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd_short.dat new file mode 100644 index 0000000000..d12ebcfa4e --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd_short.dat @@ -0,0 +1 @@ +aabbccddeeff00112233445508004500002ca98040004011a92bac1c3c01ffffffffeb320f740018e83e420100040008225300008ce9b40163b2 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_readreg_ack.dat b/Tests/Packet++Test/PacketExamples/gvcp_readreg_ack.dat new file mode 100644 index 0000000000..1167053bf2 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_readreg_ack.dat @@ -0,0 +1 @@ +aabbccddeeff001122334455080045000028000d00008011b365c0a80501c0a801010f740f74001400000000008100041fee80000001000000000000 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_readreg_cmd.dat b/Tests/Packet++Test/PacketExamples/gvcp_readreg_cmd.dat new file mode 100644 index 0000000000..2d6af60931 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_readreg_cmd.dat @@ -0,0 +1 @@ +aabbccddeeff001122334455080045000028db7940004011d7f8c0a80101c0a805010f740f74001487784201008000048bf000000000 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_truncated.dat b/Tests/Packet++Test/PacketExamples/gvcp_truncated.dat new file mode 100644 index 0000000000..abd1f24793 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_truncated.dat @@ -0,0 +1 @@ +aabbccddeeff001122334455080045000020a98040004011a92bac1c3c01ffffffffeb320f74000ce83e42110002 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_writereg_ack.dat b/Tests/Packet++Test/PacketExamples/gvcp_writereg_ack.dat new file mode 100644 index 0000000000..ff1135d95f --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_writereg_ack.dat @@ -0,0 +1 @@ +aabbccddeeff001122334455080045000024000900008011b36dc0a80501c0a801010f740f7400100000800600830000225400000000000000000000 \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/gvcp_writereg_cmd.dat b/Tests/Packet++Test/PacketExamples/gvcp_writereg_cmd.dat new file mode 100644 index 0000000000..eca924993d --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/gvcp_writereg_cmd.dat @@ -0,0 +1 @@ +aabbccddeeff0011223344550800450000449926400040111a30c0a80101c0a805010f740f7400308794420100820020225400000a00000000020000064cc0a805010000065cffff00000000001400000005 \ No newline at end of file diff --git a/Tests/Packet++Test/TestDefinition.h b/Tests/Packet++Test/TestDefinition.h index 6a3ae32b73..4cb5cc1ccf 100644 --- a/Tests/Packet++Test/TestDefinition.h +++ b/Tests/Packet++Test/TestDefinition.h @@ -337,6 +337,14 @@ PTF_TEST_CASE(ModbusLayerCreationTest); PTF_TEST_CASE(ModbusLayerParsingTest); PTF_TEST_CASE(ModbusLayerTruncatedTest); +// Implemented in GvcpTests.cpp +PTF_TEST_CASE(GvcpDiscoveryParsingTest); +PTF_TEST_CASE(GvcpForceIpParsingTest); +PTF_TEST_CASE(GvcpRegisterAccessParsingTest); +PTF_TEST_CASE(GvcpMalformedParsingTest); +PTF_TEST_CASE(GvcpLayerCreationTest); +PTF_TEST_CASE(GvcpLayerEditTest); + // Implemented in X509Tests.cpp PTF_TEST_CASE(X509ParsingTest); PTF_TEST_CASE(X509VariantsParsingTest); diff --git a/Tests/Packet++Test/Tests/GvcpTests.cpp b/Tests/Packet++Test/Tests/GvcpTests.cpp new file mode 100644 index 0000000000..0891b36a76 --- /dev/null +++ b/Tests/Packet++Test/Tests/GvcpTests.cpp @@ -0,0 +1,500 @@ +#include "../TestDefinition.h" +#include "../Utils/TestUtils.h" +#include "EthLayer.h" +#include "GeneralUtils.h" +#include "GvcpLayer.h" +#include "IPv4Layer.h" +#include "Packet.h" +#include "PacketTrailerLayer.h" +#include "PayloadLayer.h" +#include "UdpLayer.h" +#include + +using pcpp_tests::utils::createPacketFromHexResource; + +PTF_TEST_CASE(GvcpDiscoveryParsingTest) +{ + // Discovery command + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_discovery_cmd.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + PTF_ASSERT_EQUAL(std::string(pcpp::protocolTypeToString(pcpp::GVCP)), "GVCP"); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getProtocol(), pcpp::GVCP); + PTF_ASSERT_EQUAL(gvcpLayer->getOsiModelLayer(), pcpp::OsiModelApplicationLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getFlag(), 0x11); // allow broadcast, acknowledge required + PTF_ASSERT_TRUE(gvcpLayer->hasAcknowledgeFlag()); + PTF_ASSERT_TRUE(gvcpLayer->hasAllowBroadcastAckFlag()); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::DiscoveredCmd, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getRequestId(), 19942); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 0); + PTF_ASSERT_EQUAL(gvcpLayer->getHeaderLen(), 8); + PTF_ASSERT_NULL(gvcpLayer->getNextLayer()); + PTF_ASSERT_EQUAL(gvcpLayer->toString(), "GVCP Request Layer, Command: DiscoveredCmd, Request ID: 19942"); + + std::ostringstream oss; + pcpp::JsonSerializer serializer(oss); + gvcpLayer->serialize(serializer); + PTF_ASSERT_EQUAL( + oss.str(), + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":2,"commandName":"DiscoveredCmd","flag":17,"acknowledgeRequired":true,"requestId":19942,"allowBroadcastAck":true})"); + } + + // Discovery acknowledge + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_discovery_ack.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getProtocol(), pcpp::GVCP); + PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::DiscoveredAck, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getAckId(), 1); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 248); + PTF_ASSERT_EQUAL(gvcpLayer->getHeaderLen(), 256); + PTF_ASSERT_NULL(gvcpLayer->getNextLayer()); + + PTF_ASSERT_EQUAL(gvcpLayer->getMacAddress(), pcpp::MacAddress("00:04:4b:ea:b0:b4")); + PTF_ASSERT_EQUAL(gvcpLayer->getIpAddress(), pcpp::IPv4Address("172.28.60.100")); + PTF_ASSERT_EQUAL(gvcpLayer->getManufacturerName(), "Vendor01"); + PTF_ASSERT_EQUAL(gvcpLayer->getModelName(), "ABCDE 3D Scanner (TW)"); + PTF_ASSERT_EQUAL(gvcpLayer->getSerialNumber(), "XXX-005"); + PTF_ASSERT_EQUAL(gvcpLayer->toString(), + "GVCP Acknowledge Layer, Command: DiscoveredAck, Acknowledge ID: 1, Status: Success"); + + std::ostringstream oss; + pcpp::JsonSerializer serializer(oss); + gvcpLayer->serialize(serializer); + PTF_ASSERT_EQUAL( + oss.str(), + R"json({"protocolName":"GVCP","protocolId":65,"length":256,"command":3,"commandName":"DiscoveredAck","status":0,"statusName":"Success","ackId":1,"versionMajor":2,"versionMinor":1,"macAddress":"00:04:4b:ea:b0:b4","ipAddress":"172.28.60.100","subnetMask":"255.255.255.0","gatewayIpAddress":"172.28.60.1","manufacturerName":"Vendor01","modelName":"ABCDE 3D Scanner (TW)","deviceVersion":"XL","manufacturerSpecificInformation":"","serialNumber":"XXX-005","userDefinedName":""})json"); + } +} // GvcpDiscoveryParsingTest + +PTF_TEST_CASE(GvcpForceIpParsingTest) +{ + // Force IP command + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_forceip_cmd.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getFlag(), 0x01); + PTF_ASSERT_TRUE(gvcpLayer->hasAcknowledgeFlag()); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::ForceIpCmd, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getRequestId(), 8787); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 56); + PTF_ASSERT_EQUAL(gvcpLayer->getHeaderLen(), 64); + PTF_ASSERT_NULL(gvcpLayer->getNextLayer()); + + PTF_ASSERT_EQUAL(gvcpLayer->getMacAddress(), pcpp::MacAddress("8c:e9:b4:01:63:b2")); + PTF_ASSERT_EQUAL(gvcpLayer->getIpAddress(), pcpp::IPv4Address("192.168.5.1")); + PTF_ASSERT_EQUAL(gvcpLayer->getSubnetMask(), pcpp::IPv4Address("255.255.0.0")); + PTF_ASSERT_EQUAL(gvcpLayer->getGatewayIpAddress(), pcpp::IPv4Address("0.0.0.0")); + + std::ostringstream oss; + pcpp::JsonSerializer serializer(oss); + gvcpLayer->serialize(serializer); + PTF_ASSERT_EQUAL( + oss.str(), + R"({"protocolName":"GVCP","protocolId":65,"length":64,"command":4,"commandName":"ForceIpCmd","flag":1,"acknowledgeRequired":true,"requestId":8787,"macAddress":"8c:e9:b4:01:63:b2","ipAddress":"192.168.5.1","subnetMask":"255.255.0.0","gatewayIpAddress":"0.0.0.0"})"); + } + + // Force IP acknowledge + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_forceip_ack.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::ForceIpAck, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getAckId(), 8787); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 0); + PTF_ASSERT_EQUAL(gvcpLayer->getHeaderLen(), 8); + PTF_ASSERT_NULL(packet.getLayerOfType()); + + std::ostringstream oss; + pcpp::JsonSerializer serializer(oss); + gvcpLayer->serialize(serializer); + PTF_ASSERT_EQUAL( + oss.str(), + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":5,"commandName":"ForceIpAck","status":0,"statusName":"Success","ackId":8787})"); + } +} // GvcpForceIpParsingTest + +PTF_TEST_CASE(GvcpRegisterAccessParsingTest) +{ + // Read register command + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_readreg_cmd.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getFlag(), 0x01); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::ReadRegCmd, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getRequestId(), 35824); + PTF_ASSERT_EQUAL(gvcpLayer->getHeaderLen(), 8); + + auto* payloadLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(payloadLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getNextLayer(), payloadLayer, ptr); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), payloadLayer->getPayloadLen()); + PTF_ASSERT_EQUAL(pcpp::byteArrayToHexString(payloadLayer->getPayload(), 4), "00000000"); + + std::ostringstream oss; + pcpp::JsonSerializer serializer(oss); + gvcpLayer->serialize(serializer); + PTF_ASSERT_EQUAL( + oss.str(), + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":128,"commandName":"ReadRegCmd","flag":1,"acknowledgeRequired":true,"requestId":35824})"); + } + + // Read register acknowledge + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_readreg_ack.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::ReadRegAck, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getAckId(), 0x1fee); + + auto* payloadLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(payloadLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), payloadLayer->getPayloadLen()); + PTF_ASSERT_EQUAL(pcpp::byteArrayToHexString(payloadLayer->getPayload(), 4), "80000001"); + } + + // Write register command + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_writereg_cmd.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getFlag(), 0x01); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::WriteRegCmd, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getRequestId(), 8788); + + auto* payloadLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(payloadLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), payloadLayer->getPayloadLen()); + PTF_ASSERT_EQUAL(pcpp::byteArrayToHexString(payloadLayer->getPayload(), payloadLayer->getPayloadLen()), + "00000a00000000020000064cc0a805010000065cffff00000000001400000005"); + } + + // Write register acknowledge with an error status + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_writereg_ack.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::AccessDenied, + enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::WriteRegAck, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getAckId(), 8788); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 0); + PTF_ASSERT_NULL(packet.getLayerOfType()); + PTF_ASSERT_EQUAL(gvcpLayer->toString(), + "GVCP Acknowledge Layer, Command: WriteRegAck, Acknowledge ID: 8788, Status: AccessDenied"); + + std::ostringstream oss; + pcpp::JsonSerializer serializer(oss); + gvcpLayer->serialize(serializer); + PTF_ASSERT_EQUAL( + oss.str(), + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":131,"commandName":"WriteRegAck","status":32774,"statusName":"AccessDenied","ackId":8788})"); + } +} // GvcpRegisterAccessParsingTest + +PTF_TEST_CASE(GvcpMalformedParsingTest) +{ + // The data is too short to hold a GVCP header, so it must not be parsed as GVCP + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_truncated.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_FALSE(packet.isPacketOfType(pcpp::GVCP)); + PTF_ASSERT_NULL(packet.getLayerOfType()); + PTF_ASSERT_NOT_NULL(packet.getLayerOfType()); + } + + // A force IP command which is too short to hold the force IP body is parsed as a generic request + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_forceip_cmd_short.dat"); + pcpp::Packet packet(rawPacket1.get()); + PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::GVCP)); + PTF_ASSERT_NULL(packet.getLayerOfType()); + + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::ForceIpCmd, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getHeaderLen(), 8); + + auto* payloadLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(payloadLayer); + PTF_ASSERT_EQUAL(payloadLayer->getPayloadLen(), 8); + } + + // Unknown command and status values + { + pcpp::GvcpRequestLayer requestLayer(static_cast(0x1234)); + PTF_ASSERT_EQUAL(requestLayer.getCommand(), pcpp::GvcpLayer::GvcpCommand::Unknown, enumclass); + + pcpp::GvcpAcknowledgeLayer ackLayer(static_cast(0x1234), + static_cast(0x4321)); + PTF_ASSERT_EQUAL(ackLayer.getStatus(), pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Unknown, enumclass); + PTF_ASSERT_EQUAL(ackLayer.getCommand(), pcpp::GvcpLayer::GvcpCommand::Unknown, enumclass); + PTF_ASSERT_EQUAL(ackLayer.toString(), + "GVCP Acknowledge Layer, Command: Unknown, Acknowledge ID: 0, Status: Unknown"); + + // the serialized values are the raw values from the header + std::ostringstream requestOss; + pcpp::JsonSerializer requestSerializer(requestOss); + requestLayer.serialize(requestSerializer); + PTF_ASSERT_EQUAL( + requestOss.str(), + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":4660,"commandName":"Unknown","flag":0,"acknowledgeRequired":false,"requestId":1})"); + + std::ostringstream ackOss; + pcpp::JsonSerializer ackSerializer(ackOss); + ackLayer.serialize(ackSerializer); + PTF_ASSERT_EQUAL( + ackOss.str(), + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":17185,"commandName":"Unknown","status":4660,"statusName":"Unknown","ackId":0})"); + } + + // A request can't be created with an acknowledge value, and an acknowledge can't be created with a command value + { + PTF_ASSERT_RAISES(pcpp::GvcpRequestLayer{ pcpp::GvcpLayer::GvcpCommand::ReadRegAck }, std::invalid_argument, + "A GVCP request can't be created with an acknowledge value"); + PTF_ASSERT_RAISES(pcpp::GvcpAcknowledgeLayer(pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, + pcpp::GvcpLayer::GvcpCommand::ReadRegCmd), + std::invalid_argument, "A GVCP acknowledge can't be created with a command value"); + } + + PTF_ASSERT_FALSE(pcpp::GvcpDiscoveryAcknowledgeLayer::isDataValid(nullptr, 256)); + PTF_ASSERT_FALSE(pcpp::GvcpForceIpRequestLayer::isDataValid(nullptr, 64)); + PTF_ASSERT_FALSE(pcpp::GvcpRequestLayer::isDataValid(nullptr, 8)); + PTF_ASSERT_FALSE(pcpp::GvcpAcknowledgeLayer::isDataValid(nullptr, 8)); + PTF_ASSERT_NULL(pcpp::GvcpLayer::parseGvcpLayer(nullptr, 8, nullptr, nullptr)); +} // GvcpMalformedParsingTest + +PTF_TEST_CASE(GvcpLayerCreationTest) +{ + // Discovery command + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_discovery_cmd.dat"); + pcpp::Packet realPacket(rawPacket1.get()); + auto* realLayer = realPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(realLayer); + + pcpp::GvcpDiscoveryRequestLayer newLayer(true, true, 19942); + PTF_ASSERT_EQUAL(newLayer.getProtocol(), pcpp::GVCP); + PTF_ASSERT_EQUAL(newLayer.getDataLen(), realLayer->getDataLen()); + PTF_ASSERT_BUF_COMPARE(newLayer.getData(), realLayer->getData(), realLayer->getDataLen()); + + newLayer.setAllowBroadcastAckFlag(false); + PTF_ASSERT_FALSE(newLayer.hasAllowBroadcastAckFlag()); + PTF_ASSERT_TRUE(newLayer.hasAcknowledgeFlag()); + PTF_ASSERT_EQUAL(newLayer.getFlag(), 0x01); + newLayer.setAllowBroadcastAckFlag(true); + PTF_ASSERT_EQUAL(newLayer.getFlag(), 0x11); + + pcpp::GvcpDiscoveryRequestLayer defaultLayer; + PTF_ASSERT_EQUAL(defaultLayer.getFlag(), 0x01); + PTF_ASSERT_EQUAL(defaultLayer.getRequestId(), 1); + } + + // Discovery acknowledge + { + pcpp::GvcpDiscoveryAcknowledgeLayer newLayer(pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, 1); + PTF_ASSERT_EQUAL(newLayer.getProtocol(), pcpp::GVCP); + PTF_ASSERT_EQUAL(newLayer.getCommand(), pcpp::GvcpLayer::GvcpCommand::DiscoveredAck, enumclass); + PTF_ASSERT_EQUAL(newLayer.getDataLen(), 256); + PTF_ASSERT_EQUAL(newLayer.getDataSize(), 248); + + newLayer.setVersion({ 2, 1 }); + newLayer.setMacAddress(pcpp::MacAddress("00:04:4b:ea:b0:b4")); + newLayer.setIpAddress(pcpp::IPv4Address("172.28.60.100")); + newLayer.setSubnetMask(pcpp::IPv4Address("255.255.255.0")); + newLayer.setGatewayIpAddress(pcpp::IPv4Address("172.28.60.1")); + newLayer.setManufacturerName("Vendor01"); + newLayer.setModelName("ABCDE 3D Scanner (TW)"); + newLayer.setDeviceVersion("1.2.3"); + newLayer.setManufacturerSpecificInformation("info"); + newLayer.setSerialNumber("XXX-005"); + // the name is longer than the field, so the setter truncates it to 15 characters + newLayer.setUserDefinedName("0123456789abcdefgh"); + + PTF_ASSERT_EQUAL(newLayer.getVersion().major, 2); + PTF_ASSERT_EQUAL(newLayer.getVersion().minor, 1); + PTF_ASSERT_EQUAL(newLayer.getMacAddress(), pcpp::MacAddress("00:04:4b:ea:b0:b4")); + PTF_ASSERT_EQUAL(newLayer.getIpAddress(), pcpp::IPv4Address("172.28.60.100")); + PTF_ASSERT_EQUAL(newLayer.getSubnetMask(), pcpp::IPv4Address("255.255.255.0")); + PTF_ASSERT_EQUAL(newLayer.getGatewayIpAddress(), pcpp::IPv4Address("172.28.60.1")); + PTF_ASSERT_EQUAL(newLayer.getManufacturerName(), "Vendor01"); + PTF_ASSERT_EQUAL(newLayer.getModelName(), "ABCDE 3D Scanner (TW)"); + PTF_ASSERT_EQUAL(newLayer.getDeviceVersion(), "1.2.3"); + PTF_ASSERT_EQUAL(newLayer.getManufacturerSpecificInformation(), "info"); + PTF_ASSERT_EQUAL(newLayer.getSerialNumber(), "XXX-005"); + PTF_ASSERT_EQUAL(newLayer.getUserDefinedName(), "0123456789abcde"); + } + + // Force IP command, as a part of a full packet + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_forceip_cmd.dat"); + pcpp::Packet realPacket(rawPacket1.get()); + + pcpp::EthLayer ethLayer(*realPacket.getLayerOfType()); + pcpp::IPv4Layer ipLayer(*realPacket.getLayerOfType()); + pcpp::UdpLayer udpLayer(*realPacket.getLayerOfType()); + pcpp::GvcpForceIpRequestLayer newLayer(pcpp::MacAddress("8c:e9:b4:01:63:b2"), pcpp::IPv4Address("192.168.5.1"), + pcpp::IPv4Address("255.255.0.0"), pcpp::IPv4Address("0.0.0.0"), 0x01, + 8787); + + pcpp::Packet newPacket; + PTF_ASSERT_TRUE(newPacket.addLayer(ðLayer)); + PTF_ASSERT_TRUE(newPacket.addLayer(&ipLayer)); + PTF_ASSERT_TRUE(newPacket.addLayer(&udpLayer)); + PTF_ASSERT_TRUE(newPacket.addLayer(&newLayer)); + + PTF_ASSERT_EQUAL(newPacket.getRawPacket()->getRawDataLen(), realPacket.getRawPacket()->getRawDataLen()); + PTF_ASSERT_BUF_COMPARE(newPacket.getRawPacket()->getRawData(), realPacket.getRawPacket()->getRawData(), + realPacket.getRawPacket()->getRawDataLen()); + } + + // Force IP acknowledge + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_forceip_ack.dat"); + pcpp::Packet realPacket(rawPacket1.get()); + auto* realLayer = realPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(realLayer); + + pcpp::GvcpForceIpAcknowledgeLayer newLayer(pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, 8787); + PTF_ASSERT_EQUAL(newLayer.getDataLen(), realLayer->getDataLen()); + PTF_ASSERT_BUF_COMPARE(newLayer.getData(), realLayer->getData(), realLayer->getDataLen()); + } + + // Generic acknowledge followed by a payload layer, as a part of a full packet + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_readreg_ack.dat"); + pcpp::Packet realPacket(rawPacket1.get()); + + pcpp::EthLayer ethLayer(*realPacket.getLayerOfType()); + pcpp::IPv4Layer ipLayer(*realPacket.getLayerOfType()); + pcpp::UdpLayer udpLayer(*realPacket.getLayerOfType()); + pcpp::GvcpAcknowledgeLayer newAckLayer(pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, + pcpp::GvcpLayer::GvcpCommand::ReadRegAck, 0x1fee); + PTF_ASSERT_EQUAL(newAckLayer.getDataLen(), 8); + PTF_ASSERT_EQUAL(newAckLayer.getDataSize(), 0); + + uint8_t payload[] = { 0x80, 0x00, 0x00, 0x01 }; + pcpp::PayloadLayer payloadLayer(payload, sizeof(payload)); + + pcpp::Packet newPacket; + PTF_ASSERT_TRUE(newPacket.addLayer(ðLayer)); + PTF_ASSERT_TRUE(newPacket.addLayer(&ipLayer)); + PTF_ASSERT_TRUE(newPacket.addLayer(&udpLayer)); + PTF_ASSERT_TRUE(newPacket.addLayer(&newAckLayer)); + PTF_ASSERT_TRUE(newPacket.addLayer(&payloadLayer)); + newAckLayer.computeCalculateFields(); + + PTF_ASSERT_EQUAL(newAckLayer.getDataSize(), sizeof(payload)); + + // the real packet ends with an Ethernet padding which the new packet doesn't have + auto* trailerLayer = realPacket.getLayerOfType(); + PTF_ASSERT_NOT_NULL(trailerLayer); + const size_t expectedLen = realPacket.getRawPacket()->getRawDataLen() - trailerLayer->getDataLen(); + PTF_ASSERT_EQUAL(newPacket.getRawPacket()->getRawDataLen(), expectedLen); + PTF_ASSERT_BUF_COMPARE(newPacket.getRawPacket()->getRawData(), realPacket.getRawPacket()->getRawData(), + expectedLen); + } + + // Generic request + { + pcpp::GvcpRequestLayer newRequestLayer(pcpp::GvcpLayer::GvcpCommand::ReadRegCmd, 0x01, 2); + PTF_ASSERT_EQUAL(newRequestLayer.getProtocol(), pcpp::GVCP); + PTF_ASSERT_EQUAL(newRequestLayer.getCommand(), pcpp::GvcpLayer::GvcpCommand::ReadRegCmd, enumclass); + PTF_ASSERT_EQUAL(newRequestLayer.getFlag(), 0x01); + PTF_ASSERT_EQUAL(newRequestLayer.getRequestId(), 2); + PTF_ASSERT_EQUAL(newRequestLayer.getDataSize(), 0); + PTF_ASSERT_EQUAL(newRequestLayer.getDataLen(), 8); + } +} // GvcpLayerCreationTest + +PTF_TEST_CASE(GvcpLayerEditTest) +{ + // Request + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_forceip_cmd.dat"); + pcpp::Packet packet(rawPacket1.get()); + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + + gvcpLayer->setFlag(0x00); + gvcpLayer->setRequestId(1234); + gvcpLayer->setMacAddress(pcpp::MacAddress("00:11:22:33:44:55")); + gvcpLayer->setIpAddress(pcpp::IPv4Address("10.0.0.2")); + gvcpLayer->setSubnetMask(pcpp::IPv4Address("255.0.0.0")); + gvcpLayer->setGatewayIpAddress(pcpp::IPv4Address("10.0.0.1")); + + PTF_ASSERT_EQUAL(gvcpLayer->getFlag(), 0x00); + PTF_ASSERT_FALSE(gvcpLayer->hasAcknowledgeFlag()); + PTF_ASSERT_EQUAL(gvcpLayer->getRequestId(), 1234); + PTF_ASSERT_EQUAL(gvcpLayer->getMacAddress(), pcpp::MacAddress("00:11:22:33:44:55")); + PTF_ASSERT_EQUAL(gvcpLayer->getIpAddress(), pcpp::IPv4Address("10.0.0.2")); + PTF_ASSERT_EQUAL(gvcpLayer->getSubnetMask(), pcpp::IPv4Address("255.0.0.0")); + PTF_ASSERT_EQUAL(gvcpLayer->getGatewayIpAddress(), pcpp::IPv4Address("10.0.0.1")); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::ForceIpCmd, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 56); + } + + // The data size field is calculated from the size of the data after the GVCP header + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_forceip_cmd_short.dat"); + pcpp::Packet packet(rawPacket1.get()); + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + gvcpLayer->getData()[5] = 0x38; // a data size which doesn't match the data after the header + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 56); + + packet.computeCalculateFields(); + PTF_ASSERT_EQUAL(gvcpLayer->getDataSize(), 8); + } + + // Acknowledge + { + auto rawPacket1 = createPacketFromHexResource("PacketExamples/gvcp_discovery_ack.dat"); + pcpp::Packet packet(rawPacket1.get()); + auto* gvcpLayer = packet.getLayerOfType(); + PTF_ASSERT_NOT_NULL(gvcpLayer); + + gvcpLayer->setStatus(pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Busy); + gvcpLayer->setAckId(4321); + gvcpLayer->setModelName("New Model"); + + PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Busy, enumclass); + PTF_ASSERT_EQUAL(gvcpLayer->getAckId(), 4321); + PTF_ASSERT_EQUAL(gvcpLayer->getModelName(), "New Model"); + // the rest of the fields aren't affected + PTF_ASSERT_EQUAL(gvcpLayer->getManufacturerName(), "Vendor01"); + PTF_ASSERT_EQUAL(gvcpLayer->getSerialNumber(), "XXX-005"); + PTF_ASSERT_EQUAL(gvcpLayer->getCommand(), pcpp::GvcpLayer::GvcpCommand::DiscoveredAck, enumclass); + } +} // GvcpLayerEditTest diff --git a/Tests/Packet++Test/main.cpp b/Tests/Packet++Test/main.cpp index 20180b0426..8e9b6c75d4 100644 --- a/Tests/Packet++Test/main.cpp +++ b/Tests/Packet++Test/main.cpp @@ -409,6 +409,13 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(ModbusLayerParsingTest, "modbus"); PTF_RUN_TEST(ModbusLayerTruncatedTest, "modbus"); + PTF_RUN_TEST(GvcpDiscoveryParsingTest, "gvcp"); + PTF_RUN_TEST(GvcpForceIpParsingTest, "gvcp"); + PTF_RUN_TEST(GvcpRegisterAccessParsingTest, "gvcp"); + PTF_RUN_TEST(GvcpMalformedParsingTest, "gvcp"); + PTF_RUN_TEST(GvcpLayerCreationTest, "gvcp"); + PTF_RUN_TEST(GvcpLayerEditTest, "gvcp"); + PTF_RUN_TEST(X509ParsingTest, "x509"); PTF_RUN_TEST(X509VariantsParsingTest, "x509"); PTF_RUN_TEST(X509InvalidDataTest, "x509"); diff --git a/translation/README-ja.md b/translation/README-ja.md index 932b0c613b..5a5cc2e369 100644 --- a/translation/README-ja.md +++ b/translation/README-ja.md @@ -279,21 +279,22 @@ PcapPlusPlus は現在、以下のプロトコルのパケットの解析、編 42. DNS 43. DoIP 44. FTP -45. HTTP ヘッダ (リクエストとレスポンス) -46. LDAP -47. Modbus -48. MySQL - 解析のみ (編集機能なし) -49. NTP (v3, v4) -50. PEM デコーダおよびエンコーダ -51. PostgreSQL Wire Protocol (PGWire) - 解析のみ (編集機能なし) -52. Radius -53. S7 Communication (S7comm) -54. SMTP -55. SOME/IP -56. SSH - 解析のみ (編集機能なし) -57. Telnet - 解析のみ (編集機能なし) -58. X509 証明書 - 解析のみ (編集機能なし) -59. 汎用ペイロード +45. GVCP +46. HTTP ヘッダ (リクエストとレスポンス) +47. LDAP +48. Modbus +49. MySQL - 解析のみ (編集機能なし) +50. NTP (v3, v4) +51. PEM デコーダおよびエンコーダ +52. PostgreSQL Wire Protocol (PGWire) - 解析のみ (編集機能なし) +53. Radius +54. S7 Communication (S7comm) +55. SMTP +56. SOME/IP +57. SSH - 解析のみ (編集機能なし) +58. Telnet - 解析のみ (編集機能なし) +59. X509 証明書 - 解析のみ (編集機能なし) +60. 汎用ペイロード ## DPDK と PF_RING のサポート diff --git a/translation/README-kor.md b/translation/README-kor.md index 37eb008434..2225d9aefb 100644 --- a/translation/README-kor.md +++ b/translation/README-kor.md @@ -277,21 +277,22 @@ PcapPlusPlus는 현재 다음 프로토콜의 패킷을 파싱, 편집 및 생 42. DNS 43. DoIP 44. FTP -45. HTTP 헤더 (요청 및 응답) -46. LDAP -47. Modbus -48. MySQL - 파싱만 가능 (편집 불가) -49. NTP (v3, v4) -50. PEM 인코더 및 디코더 -51. PostgreSQL Wire Protocol (PGWire) - 파싱만 가능 (편집 불가) -52. Radius -53. S7 Communication (S7comm) -54. SMTP -55. SOME/IP -56. SSH - 파싱만 가능 (편집 불가) -57. Telnet - 파싱만 가능 (편집 불가) -58. X509 인증서 - 파싱만 가능 (편집 불가) -59. 일반 페이로드 +45. GVCP +46. HTTP 헤더 (요청 및 응답) +47. LDAP +48. Modbus +49. MySQL - 파싱만 가능 (편집 불가) +50. NTP (v3, v4) +51. PEM 인코더 및 디코더 +52. PostgreSQL Wire Protocol (PGWire) - 파싱만 가능 (편집 불가) +53. Radius +54. S7 Communication (S7comm) +55. SMTP +56. SOME/IP +57. SSH - 파싱만 가능 (편집 불가) +58. Telnet - 파싱만 가능 (편집 불가) +59. X509 인증서 - 파싱만 가능 (편집 불가) +60. 일반 페이로드 ## DPDK 및 PF_RING 지원 diff --git a/translation/README-rus.md b/translation/README-rus.md index da23a90d4f..e19ba30b38 100644 --- a/translation/README-rus.md +++ b/translation/README-rus.md @@ -278,21 +278,22 @@ PcapPlusPlus в настоящее время поддерживает анал 42. DNS 43. DoIP 44. FTP -45. Заголовки HTTP (запрос и ответ) -46. LDAP -47. Modbus -48. MySQL — только анализ (без возможностей редактирования) -49. NTP (v3, v4) -50. Декодер и энкодер PEM -51. PostgreSQL Wire Protocol (PGWire) — только анализ (без возможностей редактирования) -52. Radius -53. S7 Communication (S7comm) -54. SMTP -55. SOME/IP -56. SSH — только анализ (без возможностей редактирования) -57. Telnet — только анализ (без возможностей редактирования) -58. Сертификаты X509 — только анализ (без возможностей редактирования) -59. Generic payload +45. GVCP +46. Заголовки HTTP (запрос и ответ) +47. LDAP +48. Modbus +49. MySQL — только анализ (без возможностей редактирования) +50. NTP (v3, v4) +51. Декодер и энкодер PEM +52. PostgreSQL Wire Protocol (PGWire) — только анализ (без возможностей редактирования) +53. Radius +54. S7 Communication (S7comm) +55. SMTP +56. SOME/IP +57. SSH — только анализ (без возможностей редактирования) +58. Telnet — только анализ (без возможностей редактирования) +59. Сертификаты X509 — только анализ (без возможностей редактирования) +60. Generic payload ## Поддержка DPDK и PF_RING diff --git a/translation/README-zh-tw.md b/translation/README-zh-tw.md index b9f048d3ac..ed6be72b60 100644 --- a/translation/README-zh-tw.md +++ b/translation/README-zh-tw.md @@ -276,21 +276,22 @@ PcapPlusPlus 目前支援解析、編輯和建構以下網路協定的封包: 42. DNS 43. DoIP 44. FTP -45. HTTP 標頭(請求和響應) -46. LDAP -47. Modbus -48. MySQL - 僅支援解析(不支援編輯) -49. NTP (v3, v4) -50. PEM 編碼器與解碼器 -51. PostgreSQL Wire Protocol (PGWire) - 僅支援解析(不支援編輯) -52. Radius -53. S7 通訊(S7comm) -54. SMTP -55. SOME/IP -56. SSH - 僅支援解析(不支援編輯) -57. Telnet - 僅支援解析(不支援編輯) -58. X509 憑證 - 僅支援解析(不支援編輯) -59. 通用酬載(Generic Payload) +45. GVCP +46. HTTP 標頭(請求和響應) +47. LDAP +48. Modbus +49. MySQL - 僅支援解析(不支援編輯) +50. NTP (v3, v4) +51. PEM 編碼器與解碼器 +52. PostgreSQL Wire Protocol (PGWire) - 僅支援解析(不支援編輯) +53. Radius +54. S7 通訊(S7comm) +55. SMTP +56. SOME/IP +57. SSH - 僅支援解析(不支援編輯) +58. Telnet - 僅支援解析(不支援編輯) +59. X509 憑證 - 僅支援解析(不支援編輯) +60. 通用酬載(Generic Payload) ## DPDK 和 PF_RING 支援