#ifndef __VSA_H__ #define __VSA_H__ #ifdef __cplusplus #include "icsneo/communication/network.h" #include "icsneo/communication/packet.h" #include #include #include #include "stdint.h" namespace icsneo { using CaptureBitfield = uint16_t; static constexpr uint64_t ICSEpochHoursSinceUnix = 13514 * 24; // Number of hours between the start of the Unix epoch and the start of the ICS epoch static constexpr uint64_t UINT63_MAX = 0x7FFFFFFFFFFFFFFFu; /** * Struct that meets Clock format requirements from STL Chrono library. * Indicates time for for the ICS Epoch (January 1, 2007) in 25 nanosecond ticks. */ struct ICSClock { using rep = uint64_t; // Type for tick count using period = std::ratio_multiply, std::nano>; // Ratio of tick length to seconds (25 nanoseconds) using duration = std::chrono::duration; // Type for duration in 25 nanosecond ticks using time_point = std::chrono::time_point; // Type for a point in time with respect to ICSClock static constexpr bool is_steady = true; // This clock does not move backwards /** * Get the time_point at the current time with respect to ICSClock * * @return Time point at the current time with respect to ICSClock */ static time_point now() noexcept { return time_point { std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - std::chrono::hours(ICSEpochHoursSinceUnix) }; } }; using Timestamp = ICSClock::time_point; // Point in time to start or stop read class VSA; /** * Holds metadata for the VSA log file */ struct VSAMetadata { uint64_t firstRecordLocation = UINT64_MAX; // Location of the record with lowest timestamp in ring buffer std::shared_ptr firstRecord = nullptr; // The record with lowest timestamp uint64_t lastRecordLocation = UINT64_MAX; // Location of the record with the highest timestamp in ring buffer std::shared_ptr lastRecord = nullptr; // The record with the highest timestamp uint64_t bufferEnd = UINT64_MAX; // One byte beyond the last byte of the sequence started from lastRecordLocation uint64_t diskSize = 0; // The size of the vsa log file on the disk bool isOverlapped = false; // Determines if VSA ring buffer has looped to beginning uint64_t coreMiniTimestamp = UINT64_MAX; // Timestamp of the CoreMini message in 25 nanosecond ticks since January 1, 2007 }; /** * Struct used to exclude VSA message records from parse */ struct VSAMessageReadFilter { CaptureBitfield captureBitfield = UINT16_MAX; // The capture from which to gather VSA message records. UINT16_MAX indicates 'all captures' // The range of timestamps to collect record data from std::pair readRange = std::make_pair(Timestamp(ICSClock::duration(0x0ull)), Timestamp(ICSClock::duration(UINT64_MAX))); static constexpr Timestamp MinTimestamp = Timestamp(ICSClock::duration(0x0ull)); static constexpr Timestamp MaxTimestamp = Timestamp(ICSClock::duration(UINT64_MAX)); }; struct VSAExtractionSettings { bool parseOldRecords = false; bool stopCoreMini = true; std::vector filters; }; /** * Abstract VSA base class to store VSA record data read from VSA log file on disk */ class VSA { public: virtual ~VSA() = default; static constexpr size_t StandardRecordSize = 32; // Size of most VSA records static constexpr uint64_t RecordStartOffset = 0x06000000u; // Offset of VSA record ring buffer from start of VSA log file /** * Convert the given time_point object to a timestamp in 25 nanosecond ticks since January 1, 2007 * * @return Timestamp of the given time_point in 25 nanosecond ticks since January 1, 2007 */ static uint64_t getICSTimestampFromTimepoint(const Timestamp& point) noexcept { return point.time_since_epoch().count(); } /** * Enum to determine what type of record is underlying VSA parent class */ enum class Type : uint16_t { AA00 = 0xAA00u, // Pad AA01 = 0xAA01u, // Message Data (Deprecated) AA02 = 0xAA02u, // 'Logdata' AA03 = 0xAA03u, // Event AA04 = 0xAA04u, // Partition Info AA05 = 0xAA05u, // Application Error AA06 = 0xAA06u, // Internal/Debug AA07 = 0xAA07u, // Internal/Debug AA08 = 0xAA08u, // Buffer Info AA09 = 0xAA09u, // Device Info AA0A = 0xAA0Au, // Logger Configuration Info (Deprecated) AA0B = 0xAA0Bu, // Message Data AA0C = 0xAA0Cu, // PCM Audio Data AA0D = 0XAA0Du, // Message Data (Extended) AA0E = 0xAA0Eu, // Message Data (Extended) AA0F = 0xAA0Fu, // Message Data (Extended) AA6A = 0xAA6Au, // Logger Configuration Backup (512 Bytes) Invalid = UINT16_MAX // Used to indicate unset or unhandled VSA record types }; /** * Get the record type * * @return Type of record */ Type getType() const { return type; } /** * Get the timestamp stored in this record * * @return The record's timestamp in 25 nanosecond ticks since January 1, 2007 */ virtual uint64_t getTimestamp() = 0; /** * Determine whether this record has a valid timestamp. All invalid timestamps are set to the maximum value for a uint64_t. * * @return True if the timestamp is set to a valid number */ bool isTimestampValid() { return getTimestamp() != UINT64_MAX && !checksumFailed; } /** * Determine if the checksum for this record failed * * @return True if the checksum does not pass */ bool getChecksumFailed() { return checksumFailed; } /** * Get the timestamp of this record in C++ native std::chrono::time_point * * @return Timestamp of record as an std::chrono::time_point */ Timestamp getTimestampICSClock() { return Timestamp(std::chrono::duration_cast(std::chrono::nanoseconds(getTimestamp() * 25))); } protected: /** * Used to construct a VSA record from child class */ VSA() {} /** * Used to set the type of this record in child class constructors * * @param recordType The type of this record */ void setType(Type recordType) { this->type = recordType; } /** * Set whether the checksum was passed for this record. This is called in each child class constructor. * * @param fail True if checksum did not pass, else false */ void setChecksumFailed(bool fail) { checksumFailed = fail; } private: /** * Performs checksum on data in specific record type. Calls VSA::setChecksumFailed(...) from child class. * * @param recordBytes Bytestream of record to perform checksum with */ virtual void doChecksum(uint8_t* recordBytes) = 0; Type type = Type::Invalid; // The type of this record bool checksumFailed = false; // Determines if checksum failed }; /** * Interface class for handling common functionality of VSAMessage record types (AA0B, AA0D, AA0E, AA0F) */ class VSAMessage : public VSA { public: static constexpr size_t CoreMiniPayloadSize = 24; // Size of CoreMini message (payload) /** * Construct a packet from the message payload and network * * @return Packet constructed from payload and network */ std::shared_ptr getPacket() const; /** * Reserve enough memory to store a CoreMini message in the given packet * * @param packet The packet in which we are reserving memory for a message */ virtual void reservePacketData(std::shared_ptr& packet) const { packet->data.reserve(CoreMiniPayloadSize); } /** * Determine whether to filter out this VSAMessage record during parsing * * @param filter The filter struct to check this message record against * * @return True if this message passes the given filter */ virtual bool filter(const std::shared_ptr filter) = 0; protected: /** * Constructor for normal instance of VSAMessage class * * @param messageBytes Bytestream that begins at the start of the message payload * @param numBytes The number of bytes that the message payload contains * @param networkId The CoreMini ID of the network for this message */ VSAMessage(uint8_t* const messageBytes, size_t numBytes, Network::CoreMini networkId = static_cast(UINT16_MAX)) : VSA(), payload(messageBytes, messageBytes + numBytes), network(networkId) {} std::vector payload; // CoreMini message/payload of VSA record containing message data Network network; // CoreMini network of this message }; /** * Interface class for handling common functionality of VSA Extended Message records (AA0D, AA0E, AA0F) */ class VSAExtendedMessage : public VSAMessage { public: /** * Appends the payload for this message to the given packet. * Also sets the network of the packet if unset (used primarily for AA0F records which do not contain the network in the first extended message record). * * @param packet The packet to append this record's payload to */ void appendPacket(std::shared_ptr packet) const; /** * Get the total number of records for this extended message * * @return Total number of records that this message spans */ uint32_t getRecordCount() const { return totalRecordCount; } /** * Get the index of this record in the extended message sequence * * @return The index of this record */ uint16_t getIndex() { return index; }; /** * Get the numerical id of the sequence of extended message records this record is a part of * * @return The sequence number of this extended message record */ uint16_t getSequenceNum() { return sequenceNum; } protected: /** * Constructor for normal instance of VSAExtendedMessage * * @param messageBytes Bytestream that begins at the start of the message payload * @param numBytes The length of the message payload in bytes * @param networkId The CoreMini ID of the network for this message */ VSAExtendedMessage(uint8_t* const messageBytes, size_t numBytes, Network::CoreMini networkId = static_cast(UINT16_MAX)) : VSAMessage(messageBytes, numBytes, networkId) {} /** * Set the total number of records for this message * * @param recordCount Total number of records for this message */ void setRecordCount(uint32_t recordCount) { totalRecordCount = recordCount; } /** * Set the index of this record * * @param recordIndex The index of this record in its extended message sequence */ void setIndex(uint16_t recordIndex) { this->index = recordIndex; } /** * Set the sequence number of this record * * @param seq The id for the extended message sequence this record is a part of */ void setSequenceNum(uint16_t seq) { sequenceNum = seq; } private: uint32_t totalRecordCount; // The total number of records for the extended message uint16_t index; // The index of this record in its extended message sequence uint16_t sequenceNum; // The id of the sequence of records this record is a part of }; } // namespace icsneo #endif // __cplusplus #endif // __VSA_H__