diff --git a/CMakeLists.txt b/CMakeLists.txt index df548c5..8a145f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,8 @@ set(SRC_FILES device/idevicesettings.cpp device/devicefinder.cpp device/device.cpp + disk/diskreaddriver.cpp + disk/nulldiskreaddriver.cpp ${PLATFORM_SRC} ) diff --git a/api/icsneocpp/event.cpp b/api/icsneocpp/event.cpp index 64f7ec9..49aca7b 100644 --- a/api/icsneocpp/event.cpp +++ b/api/icsneocpp/event.cpp @@ -70,6 +70,7 @@ static constexpr const char* DEVICE_NOT_CURRENTLY_POLLING = "The device is not c static constexpr const char* UNSUPPORTED_TX_NETWORK = "Message network is not a supported TX network."; static constexpr const char* MESSAGE_MAX_LENGTH_EXCEEDED = "The message was too long."; static constexpr const char* VALUE_NOT_YET_PRESENT = "The value is not yet present."; +static constexpr const char* TIMEOUT = "The timeout was reached."; // Device Errors static constexpr const char* POLLING_MESSAGE_OVERFLOW = "Too many messages have been recieved for the polling message buffer, some have been lost!"; @@ -103,6 +104,8 @@ static constexpr const char* TERMINATION_NOT_SUPPORTED_DEVICE = "This device doe static constexpr const char* TERMINATION_NOT_SUPPORTED_NETWORK = "This network does not support software selectable termination on this device."; static constexpr const char* ANOTHER_IN_TERMINATION_GROUP_ENABLED = "A mutually exclusive network already has termination enabled."; static constexpr const char* ETH_PHY_REGISTER_CONTROL_NOT_AVAILABLE = "Ethernet PHY register control is not available for this device."; +static constexpr const char* DISK_NOT_SUPPORTED = "This device does not support accessing the specified disk."; +static constexpr const char* EOF_REACHED = "The requested length exceeds the available data from this disk."; static constexpr const char* SETTINGS_DEFAULTS_USED = "The device settings could not be loaded, the default settings have been applied."; // Transport Errors @@ -151,6 +154,8 @@ const char* APIEvent::DescriptionForType(Type type) { return MESSAGE_MAX_LENGTH_EXCEEDED; case Type::ValueNotYetPresent: return VALUE_NOT_YET_PRESENT; + case Type::Timeout: + return TIMEOUT; // Device Errors case Type::PollingMessageOverflow: @@ -215,6 +220,10 @@ const char* APIEvent::DescriptionForType(Type type) { return NO_SERIAL_NUMBER_FW_12V; case Type::EthPhyRegisterControlNotAvailable: return ETH_PHY_REGISTER_CONTROL_NOT_AVAILABLE; + case Type::DiskNotSupported: + return DISK_NOT_SUPPORTED; + case Type::EOFReached: + return EOF_REACHED; case Type::SettingsDefaultsUsed: return SETTINGS_DEFAULTS_USED; diff --git a/device/device.cpp b/device/device.cpp index 207551d..2f0ae94 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -474,6 +474,20 @@ Network Device::getNetworkByNumber(Network::Type type, size_t index) const { return Network::NetID::Invalid; } +optional Device::readLogicalDisk(uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout) { + if(!into || timeout <= std::chrono::milliseconds(0)) { + report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); + return nullopt; + } + + if(!isOpen()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return nullopt; + } + + return diskReadDriver->readLogicalDisk(*com, report, pos, into, amount, timeout); +} + optional Device::getDigitalIO(IO type, size_t number /* = 1 */) { if(number == 0) { // Start counting from 1 report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); diff --git a/disk/diskreaddriver.cpp b/disk/diskreaddriver.cpp new file mode 100644 index 0000000..0111a2b --- /dev/null +++ b/disk/diskreaddriver.cpp @@ -0,0 +1,58 @@ +#include "icsneo/disk/diskreaddriver.h" +#include + +using namespace icsneo; + +optional DiskReadDriver::readLogicalDisk(Communication& com, device_eventhandler_t report, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout) { + optional ret; + + // Read into here if we can't read directly into the user buffer + // That would be the case either if we don't want some at the + // beginning or end of the block. + std::vector alignedReadBuffer; + + const uint32_t idealBlockSize = getBlockSizeBounds().second; + const uint64_t startBlock = pos / idealBlockSize; + const uint64_t posWithinFirstBlock = pos % idealBlockSize; + const uint64_t blocks = amount / idealBlockSize + (amount % idealBlockSize ? 1 : 0) + (posWithinFirstBlock ? 1 : 0); + uint64_t blocksProcessed = 0; + + while(blocksProcessed < blocks && timeout >= std::chrono::milliseconds::zero()) { + const uint64_t currentBlock = startBlock + blocksProcessed; + + const int intoOffset = std::min((blocksProcessed * idealBlockSize) - posWithinFirstBlock, 0); + const auto posWithinCurrentBlock = (blocksProcessed ? 0 : posWithinFirstBlock); + auto curAmt = idealBlockSize - posWithinCurrentBlock; + const auto amountLeft = amount - *ret; + if(curAmt > amountLeft) + curAmt = amountLeft; + + const bool useAlignedReadBuffer = (posWithinCurrentBlock != 0 || curAmt != idealBlockSize); + if(useAlignedReadBuffer && alignedReadBuffer.size() < idealBlockSize) + alignedReadBuffer.resize(idealBlockSize); + + auto start = std::chrono::high_resolution_clock::now(); + auto readAmount = readLogicalDiskAligned(com, report, currentBlock * idealBlockSize, + useAlignedReadBuffer ? alignedReadBuffer.data() : into + intoOffset, idealBlockSize, timeout); + timeout -= std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start); + + if(!readAmount.has_value() || *readAmount == 0) { + if(timeout < std::chrono::milliseconds::zero()) + report(APIEvent::Type::Timeout, APIEvent::Severity::Error); + else + report(blocksProcessed ? APIEvent::Type::EOFReached : APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); + break; + } + + if(useAlignedReadBuffer) + memcpy(into + intoOffset, alignedReadBuffer.data() + posWithinCurrentBlock, curAmt); + + if(!ret) + ret.emplace(); + *ret += *readAmount; + blocksProcessed++; + } + + return ret; +} \ No newline at end of file diff --git a/disk/nulldiskreaddriver.cpp b/disk/nulldiskreaddriver.cpp new file mode 100644 index 0000000..b8e96c4 --- /dev/null +++ b/disk/nulldiskreaddriver.cpp @@ -0,0 +1,15 @@ +#include "icsneo/disk/nulldiskreaddriver.h" + +using namespace icsneo; + +optional NullDiskReadDriver::readLogicalDisk(Communication& com, device_eventhandler_t report, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout) { + report(APIEvent::Type::DiskNotSupported, APIEvent::Severity::Error); + return std::nullopt; +} + +optional NullDiskReadDriver::readLogicalDiskAligned(Communication& com, device_eventhandler_t report, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout) { + report(APIEvent::Type::DiskNotSupported, APIEvent::Severity::Error); + return std::nullopt; +} \ No newline at end of file diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index 2407807..80901fb 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -47,6 +47,7 @@ public: UnsupportedTXNetwork = 0x1011, MessageMaxLengthExceeded = 0x1012, ValueNotYetPresent = 0x1013, + Timeout = 0x1014, // Device Events PollingMessageOverflow = 0x2000, @@ -80,6 +81,8 @@ public: NoSerialNumber12V = 0x2028, // The device must be powered with 12V for communication to be established NoSerialNumberFW12V = 0x2029, // The device must be powered with 12V for communication to be established, a firmware update was already attempted EthPhyRegisterControlNotAvailable = 0x2030, //The device doesn't support Ethernet PHY MDIO access + DiskNotSupported = 0x2031, + EOFReached = 0x2032, SettingsDefaultsUsed = 0x2033, // Transport Events diff --git a/include/icsneo/device/device.h b/include/icsneo/device/device.h index 8831d61..4fbc2f6 100644 --- a/include/icsneo/device/device.h +++ b/include/icsneo/device/device.h @@ -16,6 +16,8 @@ #include "icsneo/device/nullsettings.h" #include "icsneo/device/devicetype.h" #include "icsneo/device/deviceversion.h" +#include "icsneo/disk/diskreaddriver.h" +#include "icsneo/disk/nulldiskreaddriver.h" #include "icsneo/communication/communication.h" #include "icsneo/communication/packetizer.h" #include "icsneo/communication/encoder.h" @@ -139,6 +141,23 @@ public: virtual size_t getNetworkCountByType(Network::Type) const; virtual Network getNetworkByNumber(Network::Type, size_t) const; + /** + * Read from the logical disk in this device, starting from byte `pos` + * and reading up to `amount` bytes. + * + * The number of bytes read will be returned in case of success. + * + * If the number of bytes read is less than the amount requested, + * an error will be set in icsneo::GetLastError() explaining why. + * Likely, either the end of the logical disk has been reached, or + * the timeout was reached while the read had only partially completed. + * + * Upon failure, icsneo::nullopt will be returned and an error will be + * set in icsneo::GetLastError(). + */ + optional readLogicalDisk(uint64_t pos, uint8_t* into, uint64_t amount, + std::chrono::milliseconds timeout = DiskReadDriver::DefaultTimeout); + /** * Retrieve the number of Ethernet (DoIP) Activation lines present * on this device. @@ -257,7 +276,7 @@ protected: data.device = this; } - template + template void initialize() { report = makeEventHandler(); auto driver = makeDriver(); @@ -270,6 +289,7 @@ protected: setupCommunication(*com); settings = makeSettings(com); setupSettings(*settings); + diskReadDriver = std::make_unique(); setupSupportedRXNetworks(supportedRXNetworks); setupSupportedTXNetworks(supportedTXNetworks); setupExtensions(); @@ -345,6 +365,7 @@ private: neodevice_t data; std::shared_ptr latestResetStatus; std::vector> versions; + std::unique_ptr diskReadDriver; mutable std::mutex extensionsLock; std::vector> extensions; diff --git a/include/icsneo/disk/diskreaddriver.h b/include/icsneo/disk/diskreaddriver.h new file mode 100644 index 0000000..0db498b --- /dev/null +++ b/include/icsneo/disk/diskreaddriver.h @@ -0,0 +1,47 @@ +#ifndef __DISKREADDRIVER_H__ +#define __DISKREADDRIVER_H__ + +#ifdef __cplusplus + +#include "icsneo/platform/optional.h" +#include "icsneo/communication/communication.h" +#include "icsneo/api/eventmanager.h" +#include +#include + +namespace icsneo { + +/** + * Interface for drivers which read block data from devices + */ +class DiskReadDriver { +public: + static constexpr const std::chrono::milliseconds DefaultTimeout{2000}; + static constexpr const size_t SectorSize = 512; + enum class Access { + None, + EntireCard, + VSA + }; + + virtual ~DiskReadDriver() = default; + virtual optional readLogicalDisk(Communication& com, device_eventhandler_t report, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout = DefaultTimeout); + virtual Access getAccess() const = 0; + virtual std::pair getBlockSizeBounds() const = 0; + +protected: + /** + * Perform a read which the driver can do in one shot. + * + * The `pos` requested must be sector-aligned, and the `amount` must be + * within the block size bounds provided by the driver. + */ + virtual optional readLogicalDiskAligned(Communication& com, device_eventhandler_t report, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout) = 0; +}; + +} + +#endif // __cplusplus +#endif // __DISKREADDRIVER_H__ \ No newline at end of file diff --git a/include/icsneo/disk/nulldiskreaddriver.h b/include/icsneo/disk/nulldiskreaddriver.h new file mode 100644 index 0000000..2f5047c --- /dev/null +++ b/include/icsneo/disk/nulldiskreaddriver.h @@ -0,0 +1,30 @@ +#ifndef __NULLDISKREADDRIVER_H__ +#define __NULLDISKREADDRIVER_H__ + +#ifdef __cplusplus + +#include "icsneo/disk/diskreaddriver.h" + +namespace icsneo { + +/** + * A disk driver which always returns the requested disk as unsupported + * + * Used for devices which do not have a disk, or do not provide any means for accessing it + */ +class NullDiskReadDriver : public DiskReadDriver { +public: + optional readLogicalDisk(Communication& com, device_eventhandler_t report, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout = DefaultTimeout) override; + Access getAccess() const override { return Access::None; } + std::pair getBlockSizeBounds() const override { return {SectorSize, SectorSize}; } + +private: + optional readLogicalDiskAligned(Communication& com, device_eventhandler_t report, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds timeout) override; +}; + +} + +#endif // __cplusplus +#endif // __NULLDISKREADDRIVER_H__ \ No newline at end of file