mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-08-05 09:28:40 +02:00
Device/Disk: Add VSA read and parse functionality
Implement ability to extract network traffic (CAN, LIN, Ethernet, etc.) from VSA message records on disk. Add a method to Device class that uses the VSAParser and the individual record types to extract messages from the VSA message records and pass them back to the communication system. This routes messages such that it appears as if they were discovered live instead of read from disk. The parse process (in Device) requires determination of metadata about the VSA file system on a device before it can begin extracting messages. This currently only handles data captured from the current coremini script on a device.
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
#ifndef __VSA_H__
|
||||
#define __VSA_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "icsneo/communication/network.h"
|
||||
#include "icsneo/communication/packet.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#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::ratio<25>, std::nano>; // Ratio of tick length to seconds (25 nanoseconds)
|
||||
using duration = std::chrono::duration<rep, period>; // Type for duration in 25 nanosecond ticks
|
||||
using time_point = std::chrono::time_point<ICSClock>; // 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<duration>(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<VSA> 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<VSA> 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<Timestamp, Timestamp> 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<VSAMessageReadFilter> filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract VSA base class to store VSA record data read from VSA log file on disk
|
||||
*/
|
||||
class VSA {
|
||||
public:
|
||||
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<ICSClock::duration>(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<Packet> 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>& 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<VSAMessageReadFilter> 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<Network::CoreMini>(UINT16_MAX))
|
||||
: VSA(), payload(messageBytes, messageBytes + numBytes), network(networkId) {}
|
||||
|
||||
std::vector<uint8_t> 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:
|
||||
static void truncatePacket(std::shared_ptr<Packet> packet);
|
||||
|
||||
/**
|
||||
* 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> 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<Network::CoreMini>(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__
|
||||
Reference in New Issue
Block a user