From 010ba1090f9edde00adb9cc3d57ee483bd4c0d0a Mon Sep 17 00:00:00 2001 From: tigercosmos Date: Sat, 26 Sep 2026 23:29:13 +0900 Subject: [PATCH 1/6] Add GVCP (GigE Vision Control Protocol) layer Add parsing, creation and editing of GVCP messages on UDP port 3956. - GvcpRequestLayer and GvcpAcknowledgeLayer represent all messages. Discovery and Force IP messages have their own layer classes. - A layer holds the GVCP header and the message fields that its class knows. parseNextLayer() puts the rest of the message in a PayloadLayer, and computeCalculateFields() updates the data size. - The header and body structs are protected or private members of the layer classes. - The request and acknowledge constructors reject a value of the wrong kind. Command values are even, and acknowledge values are odd. - GvcpDiscoveryRequestLayer exposes the allow-broadcast-acknowledge flag. - The command and status enums are nested in GvcpLayer. They print by name, and values that the spec does not define map to Unknown. - UdpLayer falls back to PayloadLayer if the data is not valid GVCP. - The protocol type is 65, because GTPv2 uses 57. --- Packet++/CMakeLists.txt | 2 + Packet++/header/GvcpLayer.h | 745 ++++++++++++++++++ Packet++/header/ProtocolType.h | 5 + Packet++/src/GvcpLayer.cpp | 559 +++++++++++++ Packet++/src/UdpLayer.cpp | 5 + README.md | 31 +- Tests/Packet++Test/CMakeLists.txt | 1 + .../Packet++Test/PacketExamples/gvcp_all.pcap | Bin 0 -> 1196 bytes .../PacketExamples/gvcp_discovery_ack.dat | 1 + .../PacketExamples/gvcp_discovery_cmd.dat | 1 + .../PacketExamples/gvcp_forceip_ack.dat | 1 + .../PacketExamples/gvcp_forceip_cmd.dat | 1 + .../PacketExamples/gvcp_forceip_cmd_short.dat | 1 + .../PacketExamples/gvcp_readreg_ack.dat | 1 + .../PacketExamples/gvcp_readreg_cmd.dat | 1 + .../PacketExamples/gvcp_truncated.dat | 1 + .../PacketExamples/gvcp_writereg_ack.dat | 1 + .../PacketExamples/gvcp_writereg_cmd.dat | 1 + Tests/Packet++Test/TestDefinition.h | 8 + Tests/Packet++Test/Tests/GvcpTests.cpp | 441 +++++++++++ Tests/Packet++Test/main.cpp | 7 + translation/README-ja.md | 31 +- translation/README-kor.md | 31 +- translation/README-rus.md | 31 +- translation/README-zh-tw.md | 31 +- 25 files changed, 1863 insertions(+), 75 deletions(-) create mode 100644 Packet++/header/GvcpLayer.h create mode 100644 Packet++/src/GvcpLayer.cpp create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_all.pcap create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_discovery_ack.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_discovery_cmd.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_forceip_ack.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_forceip_cmd_short.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_readreg_ack.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_readreg_cmd.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_truncated.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_writereg_ack.dat create mode 100644 Tests/Packet++Test/PacketExamples/gvcp_writereg_cmd.dat create mode 100644 Tests/Packet++Test/Tests/GvcpTests.cpp 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..50339927eb --- /dev/null +++ b/Packet++/header/GvcpLayer.h @@ -0,0 +1,745 @@ +#pragma once + +#include "IpAddress.h" +#include "Layer.h" +#include "MacAddress.h" +#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; + + /// @struct GvcpVersion + /// The GigE Vision spec version + struct GvcpVersion + { + /// The version major number + uint16_t major; + /// The version minor number + uint16_t minor; + }; + + /// @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 + }; + + /// 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 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; + } + + protected: + 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); + }; + + /// 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); + + /// Output operator for GvcpLayer::GvcpResponseStatus, prints the status name, or "Unknown" if the value isn't + /// defined by the spec + std::ostream& operator<<(std::ostream& os, GvcpLayer::GvcpResponseStatus status); + + /// @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, which must not be 0, optional + /// @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; + + protected: +#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: + /// 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; + + protected: +#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); + } + }; + + /// @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, which must not be 0, optional + 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); + }; + + /// @class GvcpDiscoveryAcknowledgeLayer + /// Represents a GVCP discovery acknowledge (DISCOVERY_ACK) + class GvcpDiscoveryAcknowledgeLayer : 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 + 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); + } + + 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, which must not be 0, optional + 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); + } + + 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..f7c478fd33 --- /dev/null +++ b/Packet++/src/GvcpLayer.cpp @@ -0,0 +1,559 @@ +#include "GvcpLayer.h" +#include "PayloadLayer.h" +#include "SystemUtils.h" +#include +#include +#include +#include + +namespace pcpp +{ + namespace + { + using GvcpCommand = GvcpLayer::GvcpCommand; + using GvcpResponseStatus = GvcpLayer::GvcpResponseStatus; + + 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 + 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 + 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; + } + + /// 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) + { + const char* name = getGvcpCommandName(command); + return os << (name != nullptr ? name : "Unknown"); + } + + std::ostream& operator<<(std::ostream& os, GvcpResponseStatus status) + { + const char* name = getGvcpResponseStatusName(status); + return os << (name != nullptr ? name : "Unknown"); + } + + // -------- Class GvcpLayer ----------------- + + 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(netToHost16(reinterpret_cast(data)->command)); + + 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); + } + + // -------- 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(); + } + + // -------- 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(); + } + + // -------- 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)); + } + + // -------- 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); + } + + // -------- 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(); + } +} // 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 0000000000000000000000000000000000000000..988de4f26feb7c2b335976c4f8d140b89c625bb8 GIT binary patch literal 1196 zcmb`H&1(};6vgkPX^er8Mgn3jID{gpA;YApsf$#an5tlcXuk%`W=w_}OVX02QnARW zU>CZQg7_DhiVM;1x+zK)f*Wz+LfyC!zbJwW<0{@WnZaa&U3ua1-hGq#-E;06;_-St zcZn#Q2=@!THqGdvgOn+jmsiBN6qV$NEETki(Wo>UQA>sR2s!B_WwpwjmRA?W&}^}y zEnQM8^Fv}x8j+%LF?6a}6|b1ZqMDyq=d^;TE}73Ga@gQ4M~Ap62Q;96z+9Wn*51YS z6epr~z(vcdWx{J~gy+D#KggB$udjr%vkj|UP0jC>pRRjKdu@syt& z?D12J1!e}#IKUhoyPiG5HPX%$23B8y`F(+z1#o@U^$UV~Uy@($p$3a~ke~xaUvxSXo+oN#M?C<-$Tmj&?j~E0U=b=?O8G7ANy+xvW*h(A2CsJmXhN zrtQc7VXG)gM3#^4Lc$C&OWYk|i)Z=q{jd6E-Xra^q@3#^W?4yY*97B!d{w@m)6X3# zfj_8Y!AWL)*%b0mL9=4J*2{hVhHZ4_189JjoLNK5{j-gFf@@@#mO*k8>HcSB@S(@h z(r3KRcLyz2fC+ZhJOF$8T%&%TYh)KY{opWs_RHXcZ?NUHb@q9a#2|mr9q7R_m^JBxkh&R3B9`tKW`fy wegbPN>_;c(ogXa_cy`S{e~-QS=N*K|YiQOwG-CopH^_NtM{oTpa0$(S0CF(pjQ{`u literal 0 HcmV?d00001 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..f43d45c465 --- /dev/null +++ b/Tests/Packet++Test/Tests/GvcpTests.cpp @@ -0,0 +1,441 @@ +#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" + +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"); + } + + // 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::GvcpLayer::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"); + } +} // 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")); + } + + // 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::GvcpLayer::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()); + } +} // 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"); + } + + // 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::GvcpLayer::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::GvcpLayer::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"); + } +} // 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::GvcpLayer::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"); + } + + // 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::GvcpLayer::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::GvcpLayer::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::GvcpLayer::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::GvcpLayer::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::GvcpLayer::GvcpResponseStatus::Busy); + gvcpLayer->setAckId(4321); + gvcpLayer->setModelName("New Model"); + + PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpLayer::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 支援 From 9cdc927a2b639d56973d2f749981617eceb61766 Mon Sep 17 00:00:00 2001 From: tigercosmos Date: Thu, 1 Oct 2026 18:31:24 +0900 Subject: [PATCH 2/6] Move GVCP response status and version into their layers Move GvcpResponseStatus into GvcpAcknowledgeLayer and GvcpVersion into GvcpDiscoveryAcknowledgeLayer, the layers that own these fields. --- Packet++/header/GvcpLayer.h | 156 ++++++++++++------------- Packet++/src/GvcpLayer.cpp | 3 +- Tests/Packet++Test/Tests/GvcpTests.cpp | 25 ++-- 3 files changed, 93 insertions(+), 91 deletions(-) diff --git a/Packet++/header/GvcpLayer.h b/Packet++/header/GvcpLayer.h index 50339927eb..cc3c62eb9b 100644 --- a/Packet++/header/GvcpLayer.h +++ b/Packet++/header/GvcpLayer.h @@ -18,16 +18,6 @@ namespace pcpp /// significant bit) is the acknowledge-required bit using GvcpFlag = uint8_t; - /// @struct GvcpVersion - /// The GigE Vision spec version - struct GvcpVersion - { - /// The version major number - uint16_t major; - /// The version minor number - uint16_t minor; - }; - /// @class GvcpLayer /// An abstract class representing the GigE Vision Control Protocol (GVCP). /// The class is implemented according to the GigE Vision specification 2.0. @@ -94,70 +84,6 @@ namespace pcpp Unknown = 0xFFFF }; - /// 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 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 @@ -211,10 +137,6 @@ namespace pcpp /// the spec std::ostream& operator<<(std::ostream& os, GvcpLayer::GvcpCommand command); - /// Output operator for GvcpLayer::GvcpResponseStatus, prints the status name, or "Unknown" if the value isn't - /// defined by the spec - std::ostream& operator<<(std::ostream& os, GvcpLayer::GvcpResponseStatus status); - /// @class GvcpRequestLayer /// Represents a GVCP request (command) message. Commands which don't have a specific layer class are /// represented by this class @@ -328,6 +250,70 @@ namespace pcpp 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 @@ -424,6 +410,10 @@ namespace pcpp } }; + /// 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 @@ -459,6 +449,16 @@ namespace pcpp 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 diff --git a/Packet++/src/GvcpLayer.cpp b/Packet++/src/GvcpLayer.cpp index f7c478fd33..1d2d34803b 100644 --- a/Packet++/src/GvcpLayer.cpp +++ b/Packet++/src/GvcpLayer.cpp @@ -11,7 +11,8 @@ namespace pcpp namespace { using GvcpCommand = GvcpLayer::GvcpCommand; - using GvcpResponseStatus = GvcpLayer::GvcpResponseStatus; + using GvcpResponseStatus = GvcpAcknowledgeLayer::GvcpResponseStatus; + using GvcpVersion = GvcpDiscoveryAcknowledgeLayer::GvcpVersion; constexpr uint8_t gvcpMagicNumber = 0x42; constexpr GvcpFlag gvcpAcknowledgeFlag = 0x01; diff --git a/Tests/Packet++Test/Tests/GvcpTests.cpp b/Tests/Packet++Test/Tests/GvcpTests.cpp index f43d45c465..c055fbaedb 100644 --- a/Tests/Packet++Test/Tests/GvcpTests.cpp +++ b/Tests/Packet++Test/Tests/GvcpTests.cpp @@ -44,7 +44,7 @@ PTF_TEST_CASE(GvcpDiscoveryParsingTest) auto* gvcpLayer = packet.getLayerOfType(); PTF_ASSERT_NOT_NULL(gvcpLayer); PTF_ASSERT_EQUAL(gvcpLayer->getProtocol(), pcpp::GVCP); - PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpLayer::GvcpResponseStatus::Success, enumclass); + 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); @@ -93,7 +93,7 @@ PTF_TEST_CASE(GvcpForceIpParsingTest) auto* gvcpLayer = packet.getLayerOfType(); PTF_ASSERT_NOT_NULL(gvcpLayer); - PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpLayer::GvcpResponseStatus::Success, enumclass); + 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); @@ -132,7 +132,7 @@ PTF_TEST_CASE(GvcpRegisterAccessParsingTest) auto* gvcpLayer = packet.getLayerOfType(); PTF_ASSERT_NOT_NULL(gvcpLayer); - PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpLayer::GvcpResponseStatus::Success, enumclass); + 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); @@ -169,7 +169,8 @@ PTF_TEST_CASE(GvcpRegisterAccessParsingTest) auto* gvcpLayer = packet.getLayerOfType(); PTF_ASSERT_NOT_NULL(gvcpLayer); - PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpLayer::GvcpResponseStatus::AccessDenied, enumclass); + 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); @@ -212,9 +213,9 @@ PTF_TEST_CASE(GvcpMalformedParsingTest) pcpp::GvcpRequestLayer requestLayer(static_cast(0x1234)); PTF_ASSERT_EQUAL(requestLayer.getCommand(), pcpp::GvcpLayer::GvcpCommand::Unknown, enumclass); - pcpp::GvcpAcknowledgeLayer ackLayer(static_cast(0x1234), + pcpp::GvcpAcknowledgeLayer ackLayer(static_cast(0x1234), static_cast(0x4321)); - PTF_ASSERT_EQUAL(ackLayer.getStatus(), pcpp::GvcpLayer::GvcpResponseStatus::Unknown, enumclass); + 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"); @@ -224,7 +225,7 @@ PTF_TEST_CASE(GvcpMalformedParsingTest) { 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::GvcpLayer::GvcpResponseStatus::Success, + 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"); } @@ -264,7 +265,7 @@ PTF_TEST_CASE(GvcpLayerCreationTest) // Discovery acknowledge { - pcpp::GvcpDiscoveryAcknowledgeLayer newLayer(pcpp::GvcpLayer::GvcpResponseStatus::Success, 1); + 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); @@ -327,7 +328,7 @@ PTF_TEST_CASE(GvcpLayerCreationTest) auto* realLayer = realPacket.getLayerOfType(); PTF_ASSERT_NOT_NULL(realLayer); - pcpp::GvcpForceIpAcknowledgeLayer newLayer(pcpp::GvcpLayer::GvcpResponseStatus::Success, 8787); + 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()); } @@ -340,7 +341,7 @@ PTF_TEST_CASE(GvcpLayerCreationTest) pcpp::EthLayer ethLayer(*realPacket.getLayerOfType()); pcpp::IPv4Layer ipLayer(*realPacket.getLayerOfType()); pcpp::UdpLayer udpLayer(*realPacket.getLayerOfType()); - pcpp::GvcpAcknowledgeLayer newAckLayer(pcpp::GvcpLayer::GvcpResponseStatus::Success, + pcpp::GvcpAcknowledgeLayer newAckLayer(pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Success, pcpp::GvcpLayer::GvcpCommand::ReadRegAck, 0x1fee); PTF_ASSERT_EQUAL(newAckLayer.getDataLen(), 8); PTF_ASSERT_EQUAL(newAckLayer.getDataSize(), 0); @@ -426,11 +427,11 @@ PTF_TEST_CASE(GvcpLayerEditTest) auto* gvcpLayer = packet.getLayerOfType(); PTF_ASSERT_NOT_NULL(gvcpLayer); - gvcpLayer->setStatus(pcpp::GvcpLayer::GvcpResponseStatus::Busy); + gvcpLayer->setStatus(pcpp::GvcpAcknowledgeLayer::GvcpResponseStatus::Busy); gvcpLayer->setAckId(4321); gvcpLayer->setModelName("New Model"); - PTF_ASSERT_EQUAL(gvcpLayer->getStatus(), pcpp::GvcpLayer::GvcpResponseStatus::Busy, enumclass); + 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 From b17ec4a02d0b83020df686860f5fba27e6413d5f Mon Sep 17 00:00:00 2001 From: tigercosmos Date: Fri, 2 Oct 2026 00:10:14 +0900 Subject: [PATCH 3/6] Add serialization to the GVCP layers Implement serializeLayer() for the GVCP layers. Each layer writes its fields after the fields of its parent class: the command and data size, then the request or acknowledge header fields, then the Discovery or Force IP fields. Command and status values are written as a number and as a name. --- Packet++/header/GvcpLayer.h | 207 +++++++++++++++++++++++++ Packet++/src/GvcpLayer.cpp | 146 ++++++++++++++++- Tests/Packet++Test/Tests/GvcpTests.cpp | 43 +++++ 3 files changed, 392 insertions(+), 4 deletions(-) diff --git a/Packet++/header/GvcpLayer.h b/Packet++/header/GvcpLayer.h index cc3c62eb9b..8015888808 100644 --- a/Packet++/header/GvcpLayer.h +++ b/Packet++/header/GvcpLayer.h @@ -3,6 +3,9 @@ #include "IpAddress.h" #include "Layer.h" #include "MacAddress.h" +#include +#include +#include #include #include @@ -121,7 +124,36 @@ namespace pcpp 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, DataSize }; + 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; + + /// @brief Data size field from the header + static const FieldDescriptor DataSize; + + /// Maximum field ID used by GvcpLayer + static constexpr uint16_t MaxID = Layer::SerializedFields::MaxID + 3; + }; + protected: + void serializeLayer(ObjectScope& serializer) const override; + GvcpLayer() = default; GvcpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) @@ -206,7 +238,36 @@ namespace pcpp /// @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; + + /// 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. @@ -374,7 +435,36 @@ namespace pcpp /// @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; + + /// 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. @@ -442,6 +532,29 @@ namespace pcpp /// 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; + + /// Maximum field ID used by GvcpDiscoveryRequestLayer + static constexpr uint16_t MaxID = GvcpRequestLayer::SerializedFields::MaxID + 1; + }; + + protected: + void serializeLayer(ObjectScope& serializer) const override; }; /// @class GvcpDiscoveryAcknowledgeLayer @@ -567,6 +680,67 @@ namespace pcpp 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; + + /// 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. @@ -689,6 +863,39 @@ namespace pcpp 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; + + /// 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. diff --git a/Packet++/src/GvcpLayer.cpp b/Packet++/src/GvcpLayer.cpp index 1d2d34803b..5f33fa6682 100644 --- a/Packet++/src/GvcpLayer.cpp +++ b/Packet++/src/GvcpLayer.cpp @@ -151,6 +151,18 @@ namespace pcpp 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]) { @@ -167,14 +179,12 @@ namespace pcpp std::ostream& operator<<(std::ostream& os, GvcpCommand command) { - const char* name = getGvcpCommandName(command); - return os << (name != nullptr ? name : "Unknown"); + return os << commandToString(command); } std::ostream& operator<<(std::ostream& os, GvcpResponseStatus status) { - const char* name = getGvcpResponseStatusName(status); - return os << (name != nullptr ? name : "Unknown"); + return os << statusToString(status); } // -------- Class GvcpLayer ----------------- @@ -219,6 +229,17 @@ namespace pcpp 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" }; + const FieldDescriptor GvcpLayer::SerializedFields::DataSize{ Layer::SerializedFields::MaxID + 3, "dataSize" }; + + void GvcpLayer::serializeLayer(ObjectScope& serializer) const + { + serializer.writeField(SerializedFields::Command, static_cast(getCommand())); + serializer.writeField(SerializedFields::CommandName, commandToString(getCommand())); + serializer.writeField(SerializedFields::DataSize, getDataSize()); + } + // -------- Class GvcpRequestLayer ----------------- GvcpRequestLayer::GvcpRequestLayer(GvcpCommand command, GvcpFlag flag, uint16_t requestId, size_t bodyLen) @@ -288,6 +309,21 @@ namespace pcpp 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, @@ -353,6 +389,21 @@ namespace pcpp 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, static_cast(getStatus())); + serializer.writeField(SerializedFields::StatusName, statusToString(getStatus())); + serializer.writeField(SerializedFields::AckId, getAckId()); + } + // -------- Class GvcpDiscoveryRequestLayer ----------------- GvcpDiscoveryRequestLayer::GvcpDiscoveryRequestLayer(bool allowBroadcastAck, bool acknowledgeRequired, @@ -374,6 +425,16 @@ namespace pcpp : 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) @@ -499,6 +560,61 @@ namespace pcpp 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, @@ -557,4 +673,26 @@ namespace pcpp { 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/Tests/Packet++Test/Tests/GvcpTests.cpp b/Tests/Packet++Test/Tests/GvcpTests.cpp index c055fbaedb..8b34486ec6 100644 --- a/Tests/Packet++Test/Tests/GvcpTests.cpp +++ b/Tests/Packet++Test/Tests/GvcpTests.cpp @@ -8,6 +8,7 @@ #include "PacketTrailerLayer.h" #include "PayloadLayer.h" #include "UdpLayer.h" +#include using pcpp_tests::utils::createPacketFromHexResource; @@ -33,6 +34,13 @@ PTF_TEST_CASE(GvcpDiscoveryParsingTest) 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","dataSize":0,"flag":17,"acknowledgeRequired":true,"requestId":19942,"allowBroadcastAck":true})"); } // Discovery acknowledge @@ -58,6 +66,13 @@ PTF_TEST_CASE(GvcpDiscoveryParsingTest) 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","dataSize":248,"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 @@ -83,6 +98,13 @@ PTF_TEST_CASE(GvcpForceIpParsingTest) 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","dataSize":56,"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 @@ -99,6 +121,13 @@ PTF_TEST_CASE(GvcpForceIpParsingTest) 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","dataSize":0,"status":0,"statusName":"Success","ackId":8787})"); } } // GvcpForceIpParsingTest @@ -122,6 +151,13 @@ PTF_TEST_CASE(GvcpRegisterAccessParsingTest) 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","dataSize":4,"flag":1,"acknowledgeRequired":true,"requestId":35824})"); } // Read register acknowledge @@ -177,6 +213,13 @@ PTF_TEST_CASE(GvcpRegisterAccessParsingTest) 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","dataSize":0,"status":32774,"statusName":"AccessDenied","ackId":8788})"); } } // GvcpRegisterAccessParsingTest From ddcd2c5dfd695f635bfc0250eddd480ac9dd738c Mon Sep 17 00:00:00 2001 From: tigercosmos Date: Fri, 2 Oct 2026 11:07:25 +0900 Subject: [PATCH 4/6] Serialize raw GVCP command and status values Write the command and status values from the header instead of the mapped enum values, so that values which the spec doesn't define keep their number instead of 0xffff. The name fields still show "Unknown". Also state in the docs that the spec requires a non-zero request ID, instead of implying that the constructors enforce it. --- Packet++/header/GvcpLayer.h | 11 ++++++++--- Packet++/src/GvcpLayer.cpp | 16 ++++++++++++---- Tests/Packet++Test/Tests/GvcpTests.cpp | 15 +++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Packet++/header/GvcpLayer.h b/Packet++/header/GvcpLayer.h index 8015888808..f01725584e 100644 --- a/Packet++/header/GvcpLayer.h +++ b/Packet++/header/GvcpLayer.h @@ -163,6 +163,11 @@ namespace pcpp /// 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 @@ -188,7 +193,7 @@ namespace pcpp /// add a PayloadLayer after this layer /// @param[in] command The command /// @param[in] flag The flag, optional - /// @param[in] requestId The request ID, which must not be 0, 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) @@ -521,7 +526,7 @@ namespace pcpp /// 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, which must not be 0, 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); @@ -814,7 +819,7 @@ namespace pcpp /// @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, which must not be 0, 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); diff --git a/Packet++/src/GvcpLayer.cpp b/Packet++/src/GvcpLayer.cpp index 5f33fa6682..4b0ec3c0e4 100644 --- a/Packet++/src/GvcpLayer.cpp +++ b/Packet++/src/GvcpLayer.cpp @@ -2,6 +2,7 @@ #include "PayloadLayer.h" #include "SystemUtils.h" #include +#include #include #include #include @@ -189,14 +190,21 @@ namespace pcpp // -------- 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(netToHost16(reinterpret_cast(data)->command)); + const GvcpCommand command = toGvcpCommand(getRawCommand(data)); if (isRequest) { @@ -235,7 +243,7 @@ namespace pcpp void GvcpLayer::serializeLayer(ObjectScope& serializer) const { - serializer.writeField(SerializedFields::Command, static_cast(getCommand())); + serializer.writeField(SerializedFields::Command, getRawCommand(m_Data)); serializer.writeField(SerializedFields::CommandName, commandToString(getCommand())); serializer.writeField(SerializedFields::DataSize, getDataSize()); } @@ -399,7 +407,7 @@ namespace pcpp void GvcpAcknowledgeLayer::serializeLayer(ObjectScope& serializer) const { GvcpLayer::serializeLayer(serializer); - serializer.writeField(SerializedFields::Status, static_cast(getStatus())); + serializer.writeField(SerializedFields::Status, netToHost16(getGvcpHeader()->status)); serializer.writeField(SerializedFields::StatusName, statusToString(getStatus())); serializer.writeField(SerializedFields::AckId, getAckId()); } diff --git a/Tests/Packet++Test/Tests/GvcpTests.cpp b/Tests/Packet++Test/Tests/GvcpTests.cpp index 8b34486ec6..4fec5aa436 100644 --- a/Tests/Packet++Test/Tests/GvcpTests.cpp +++ b/Tests/Packet++Test/Tests/GvcpTests.cpp @@ -262,6 +262,21 @@ PTF_TEST_CASE(GvcpMalformedParsingTest) 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","dataSize":0,"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","dataSize":0,"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 From df780a2651ca5c8a11df7e309e7c6ac6576e5501 Mon Sep 17 00:00:00 2001 From: tigercosmos Date: Fri, 2 Oct 2026 12:16:03 +0900 Subject: [PATCH 5/6] Suppress cppcheck duplInheritedMember on GVCP SerializedFields::MaxID Each SerializedFields struct must declare its own MaxID so that the next class in the hierarchy can number its fields after it. --- Packet++/header/GvcpLayer.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Packet++/header/GvcpLayer.h b/Packet++/header/GvcpLayer.h index f01725584e..fc7b0e2534 100644 --- a/Packet++/header/GvcpLayer.h +++ b/Packet++/header/GvcpLayer.h @@ -147,6 +147,7 @@ namespace pcpp /// @brief Data size field from the header static const FieldDescriptor DataSize; + // cppcheck-suppress duplInheritedMember /// Maximum field ID used by GvcpLayer static constexpr uint16_t MaxID = Layer::SerializedFields::MaxID + 3; }; @@ -266,6 +267,7 @@ namespace pcpp /// @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; }; @@ -463,6 +465,7 @@ namespace pcpp /// @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; }; @@ -554,6 +557,7 @@ namespace pcpp /// @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; }; @@ -739,6 +743,7 @@ namespace pcpp /// @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; }; @@ -894,6 +899,7 @@ namespace pcpp /// @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; }; From c1cf47a59c92bd2069964591542830ecf8ed7993 Mon Sep 17 00:00:00 2001 From: tigercosmos Date: Fri, 2 Oct 2026 14:12:41 +0900 Subject: [PATCH 6/6] Address GVCP serialization review comments - Make getGvcpCommandName() and getGvcpResponseStatusName() constexpr. - Remove the dataSize serialized field, because the layer serializer already writes the header length. --- Packet++/header/GvcpLayer.h | 7 ++----- Packet++/src/GvcpLayer.cpp | 6 ++---- Tests/Packet++Test/Tests/GvcpTests.cpp | 16 ++++++++-------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Packet++/header/GvcpLayer.h b/Packet++/header/GvcpLayer.h index fc7b0e2534..5cb01ded0a 100644 --- a/Packet++/header/GvcpLayer.h +++ b/Packet++/header/GvcpLayer.h @@ -133,7 +133,7 @@ namespace pcpp static std::vector all() { auto result = Layer::SerializedFields::all(); - std::initializer_list extra{ Command, CommandName, DataSize }; + std::initializer_list extra{ Command, CommandName }; std::copy(extra.begin(), extra.end(), std::back_inserter(result)); return result; } @@ -144,12 +144,9 @@ namespace pcpp /// @brief Name of the command or acknowledge value, or "Unknown" static const FieldDescriptor CommandName; - /// @brief Data size field from the header - static const FieldDescriptor DataSize; - // cppcheck-suppress duplInheritedMember /// Maximum field ID used by GvcpLayer - static constexpr uint16_t MaxID = Layer::SerializedFields::MaxID + 3; + static constexpr uint16_t MaxID = Layer::SerializedFields::MaxID + 2; }; protected: diff --git a/Packet++/src/GvcpLayer.cpp b/Packet++/src/GvcpLayer.cpp index 4b0ec3c0e4..2b24d8ab19 100644 --- a/Packet++/src/GvcpLayer.cpp +++ b/Packet++/src/GvcpLayer.cpp @@ -27,7 +27,7 @@ namespace pcpp } /// @return The name of a command defined by the spec, or nullptr if the value isn't defined - const char* getGvcpCommandName(GvcpCommand command) + constexpr const char* getGvcpCommandName(GvcpCommand command) { switch (command) { @@ -79,7 +79,7 @@ namespace pcpp } /// @return The name of a status defined by the spec, or nullptr if the value isn't defined - const char* getGvcpResponseStatusName(GvcpResponseStatus status) + constexpr const char* getGvcpResponseStatusName(GvcpResponseStatus status) { switch (status) { @@ -239,13 +239,11 @@ namespace pcpp const FieldDescriptor GvcpLayer::SerializedFields::Command{ Layer::SerializedFields::MaxID + 1, "command" }; const FieldDescriptor GvcpLayer::SerializedFields::CommandName{ Layer::SerializedFields::MaxID + 2, "commandName" }; - const FieldDescriptor GvcpLayer::SerializedFields::DataSize{ Layer::SerializedFields::MaxID + 3, "dataSize" }; void GvcpLayer::serializeLayer(ObjectScope& serializer) const { serializer.writeField(SerializedFields::Command, getRawCommand(m_Data)); serializer.writeField(SerializedFields::CommandName, commandToString(getCommand())); - serializer.writeField(SerializedFields::DataSize, getDataSize()); } // -------- Class GvcpRequestLayer ----------------- diff --git a/Tests/Packet++Test/Tests/GvcpTests.cpp b/Tests/Packet++Test/Tests/GvcpTests.cpp index 4fec5aa436..0891b36a76 100644 --- a/Tests/Packet++Test/Tests/GvcpTests.cpp +++ b/Tests/Packet++Test/Tests/GvcpTests.cpp @@ -40,7 +40,7 @@ PTF_TEST_CASE(GvcpDiscoveryParsingTest) gvcpLayer->serialize(serializer); PTF_ASSERT_EQUAL( oss.str(), - R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":2,"commandName":"DiscoveredCmd","dataSize":0,"flag":17,"acknowledgeRequired":true,"requestId":19942,"allowBroadcastAck":true})"); + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":2,"commandName":"DiscoveredCmd","flag":17,"acknowledgeRequired":true,"requestId":19942,"allowBroadcastAck":true})"); } // Discovery acknowledge @@ -72,7 +72,7 @@ PTF_TEST_CASE(GvcpDiscoveryParsingTest) gvcpLayer->serialize(serializer); PTF_ASSERT_EQUAL( oss.str(), - R"json({"protocolName":"GVCP","protocolId":65,"length":256,"command":3,"commandName":"DiscoveredAck","dataSize":248,"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"); + 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 @@ -104,7 +104,7 @@ PTF_TEST_CASE(GvcpForceIpParsingTest) gvcpLayer->serialize(serializer); PTF_ASSERT_EQUAL( oss.str(), - R"({"protocolName":"GVCP","protocolId":65,"length":64,"command":4,"commandName":"ForceIpCmd","dataSize":56,"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"})"); + 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 @@ -127,7 +127,7 @@ PTF_TEST_CASE(GvcpForceIpParsingTest) gvcpLayer->serialize(serializer); PTF_ASSERT_EQUAL( oss.str(), - R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":5,"commandName":"ForceIpAck","dataSize":0,"status":0,"statusName":"Success","ackId":8787})"); + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":5,"commandName":"ForceIpAck","status":0,"statusName":"Success","ackId":8787})"); } } // GvcpForceIpParsingTest @@ -157,7 +157,7 @@ PTF_TEST_CASE(GvcpRegisterAccessParsingTest) gvcpLayer->serialize(serializer); PTF_ASSERT_EQUAL( oss.str(), - R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":128,"commandName":"ReadRegCmd","dataSize":4,"flag":1,"acknowledgeRequired":true,"requestId":35824})"); + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":128,"commandName":"ReadRegCmd","flag":1,"acknowledgeRequired":true,"requestId":35824})"); } // Read register acknowledge @@ -219,7 +219,7 @@ PTF_TEST_CASE(GvcpRegisterAccessParsingTest) gvcpLayer->serialize(serializer); PTF_ASSERT_EQUAL( oss.str(), - R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":131,"commandName":"WriteRegAck","dataSize":0,"status":32774,"statusName":"AccessDenied","ackId":8788})"); + R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":131,"commandName":"WriteRegAck","status":32774,"statusName":"AccessDenied","ackId":8788})"); } } // GvcpRegisterAccessParsingTest @@ -269,14 +269,14 @@ PTF_TEST_CASE(GvcpMalformedParsingTest) requestLayer.serialize(requestSerializer); PTF_ASSERT_EQUAL( requestOss.str(), - R"({"protocolName":"GVCP","protocolId":65,"length":8,"command":4660,"commandName":"Unknown","dataSize":0,"flag":0,"acknowledgeRequired":false,"requestId":1})"); + 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","dataSize":0,"status":4660,"statusName":"Unknown","ackId":0})"); + 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