From 2c856e3976762777996e3ea9d28f88dd6da102f2 Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 21 Dec 2018 01:12:53 +0100 Subject: [PATCH] Add statistics --- io/entry.cpp | 20 ++++++++ io/entry.h | 21 +++++++++ io/passwordfile.cpp | 108 +++++++++++++++++++++++++++++++++++++++----- io/passwordfile.h | 36 +++++++++++++++ 4 files changed, 174 insertions(+), 11 deletions(-) diff --git a/io/entry.cpp b/io/entry.cpp index 0f97457..1d55f60 100644 --- a/io/entry.cpp +++ b/io/entry.cpp @@ -420,6 +420,17 @@ NodeEntry *NodeEntry::clone() const return new NodeEntry(*this); } +/*! + * \brief Accumulates the statistics for this node entry and its children. + */ +void NodeEntry::accumulateStatistics(EntryStatistics &stats) const +{ + ++stats.nodeCount; + for (const auto *children : children()) { + children->accumulateStatistics(stats); + } +} + /*! * \class AccountEntry * \brief The exception that is thrown when a parsing error occurs. @@ -501,4 +512,13 @@ AccountEntry *AccountEntry::clone() const { return new AccountEntry(*this); } + +/*! + * \brief Accumulates the statistics for this account entry and its fields. + */ +void AccountEntry::accumulateStatistics(EntryStatistics &stats) const +{ + stats.accountCount += 1; + stats.fieldCount += fields().size(); +} } // namespace Io diff --git a/io/entry.h b/io/entry.h index 1a3cc0e..6afb72d 100644 --- a/io/entry.h +++ b/io/entry.h @@ -20,6 +20,12 @@ enum class EntryType : int { Account /**< denotes an AccountEntry */ }; +struct EntryStatistics { + std::size_t nodeCount = 0; + std::size_t accountCount = 0; + std::size_t fieldCount = 0; +}; + class NodeEntry; class PASSWORD_FILE_EXPORT Entry { @@ -40,6 +46,8 @@ public: void path(std::list &res) const; virtual void make(std::ostream &stream) const = 0; virtual Entry *clone() const = 0; + EntryStatistics computeStatistics() const; + virtual void accumulateStatistics(EntryStatistics &stats) const = 0; static Entry *parse(std::istream &stream); static bool denotesNodeEntry(byte version); static constexpr EntryType denotedEntryType(byte version); @@ -93,6 +101,17 @@ inline int Entry::index() const return m_index; } +/*! + * \brief Computes statistics for this entry. + * \remarks Takes the current instance and children into account but not parents. + */ +inline EntryStatistics Entry::computeStatistics() const +{ + EntryStatistics stats; + accumulateStatistics(stats); + return stats; +} + class PASSWORD_FILE_EXPORT NodeEntry : public Entry { friend class Entry; @@ -112,6 +131,7 @@ public: void setExpandedByDefault(bool expandedByDefault); virtual void make(std::ostream &stream) const; virtual NodeEntry *clone() const; + void accumulateStatistics(EntryStatistics &stats) const; private: std::vector m_children; @@ -161,6 +181,7 @@ public: std::vector &fields(); virtual void make(std::ostream &stream) const; virtual AccountEntry *clone() const; + void accumulateStatistics(EntryStatistics &stats) const; private: std::vector m_fields; diff --git a/io/passwordfile.cpp b/io/passwordfile.cpp index 9f287ef..e05bc41 100644 --- a/io/passwordfile.cpp +++ b/io/passwordfile.cpp @@ -44,6 +44,9 @@ const unsigned int aes256cbcIvSize = 16U; PasswordFile::PasswordFile() : m_freader(BinaryReader(&m_file)) , m_fwriter(BinaryWriter(&m_file)) + , m_version(0) + , m_openOptions(PasswordFileOpenFlags::None) + , m_saveOptions(PasswordFileSaveFlags::None) { m_file.exceptions(ios_base::failbit | ios_base::badbit); clearPassword(); @@ -55,6 +58,9 @@ PasswordFile::PasswordFile() PasswordFile::PasswordFile(const string &path, const string &password) : m_freader(BinaryReader(&m_file)) , m_fwriter(BinaryWriter(&m_file)) + , m_version(0) + , m_openOptions(PasswordFileOpenFlags::None) + , m_saveOptions(PasswordFileSaveFlags::None) { m_file.exceptions(ios_base::failbit | ios_base::badbit); setPath(path); @@ -72,6 +78,9 @@ PasswordFile::PasswordFile(const PasswordFile &other) , m_encryptedExtendedHeader(other.m_encryptedExtendedHeader) , m_freader(BinaryReader(&m_file)) , m_fwriter(BinaryWriter(&m_file)) + , m_version(other.m_version) + , m_openOptions(other.m_openOptions) + , m_saveOptions(other.m_saveOptions) { m_file.exceptions(ios_base::failbit | ios_base::badbit); } @@ -88,6 +97,9 @@ PasswordFile::PasswordFile(PasswordFile &&other) , m_file(move(other.m_file)) , m_freader(BinaryReader(&m_file)) , m_fwriter(BinaryWriter(&m_file)) + , m_version(other.m_version) + , m_openOptions(other.m_openOptions) + , m_saveOptions(other.m_saveOptions) { } @@ -165,6 +177,8 @@ void PasswordFile::load() open(); } m_file.seekg(0); + m_version = 0; + m_saveOptions = PasswordFileSaveFlags::None; // check magic number if (m_freader.readUInt32LE() != 0x7770616DU) { @@ -172,26 +186,35 @@ void PasswordFile::load() } // check version and flags (used in version 0x3 only) - const auto version = m_freader.readUInt32LE(); - if (version > 0x6U) { - throw ParsingException(argsToString("Version \"", version, "\" is unknown. Only versions 0 to 6 are supported.")); + m_version = m_freader.readUInt32LE(); + if (m_version > 0x6U) { + throw ParsingException(argsToString("Version \"", m_version, "\" is unknown. Only versions 0 to 6 are supported.")); + } + if (m_version >= 0x6U) { + m_saveOptions |= PasswordFileSaveFlags::PasswordHashing; } bool decrypterUsed, ivUsed, compressionUsed; - if (version >= 0x3U) { + if (m_version >= 0x3U) { const auto flags = m_freader.readByte(); - decrypterUsed = flags & 0x80; + if ((decrypterUsed = flags & 0x80)) { + m_saveOptions |= PasswordFileSaveFlags::Encryption; + } + if ((compressionUsed = flags & 0x20)) { + m_saveOptions |= PasswordFileSaveFlags::Compression; + } ivUsed = flags & 0x40; - compressionUsed = flags & 0x20; } else { - decrypterUsed = version >= 0x1U; - ivUsed = version == 0x2U; + if ((decrypterUsed = m_version >= 0x1U)) { + m_saveOptions |= PasswordFileSaveFlags::Encryption; + } compressionUsed = false; + ivUsed = m_version == 0x2U; } // skip extended header // (the extended header might be used in further versions to // add additional information without breaking compatibility) - if (version >= 0x4U) { + if (m_version >= 0x4U) { uint16 extendedHeaderSize = m_freader.readUInt16BE(); m_extendedHeader = m_freader.readString(extendedHeaderSize); } else { @@ -206,7 +229,7 @@ void PasswordFile::load() // read hash count uint32_t hashCount = 0U; - if (version >= 0x6U && decrypterUsed) { + if ((m_saveOptions & PasswordFileSaveFlags::PasswordHashing) && decrypterUsed) { if (remainingSize < 4) { throw ParsingException("Hash count truncated."); } @@ -323,7 +346,7 @@ void PasswordFile::load() #else decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast(remainingSize)); #endif - if (version >= 0x5u) { + if (m_version >= 0x5u) { BinaryReader reader(&decryptedStream); const auto extendedHeaderSize = reader.readUInt16BE(); m_encryptedExtendedHeader = reader.readString(extendedHeaderSize); @@ -342,6 +365,7 @@ void PasswordFile::load() /*! * \brief Returns the minimum file version required to write the current instance with the specified \a options. + * \remarks This version will be used by save() and write() when passing the same \a options. */ uint32 PasswordFile::mininumVersion(PasswordFileSaveFlags options) const { @@ -785,4 +809,66 @@ size_t PasswordFile::size() m_file.seekg(0, ios::end); return static_cast(m_file.tellg()); } + +/*! + * \brief Returns a summary about the file (version, used features, statistics). + */ +string PasswordFile::summary(PasswordFileSaveFlags saveOptions) const +{ + string result = ""; + if (!m_path.empty()) { + result += argsToString(""); + } + result += argsToString(""); + const auto minVersion = mininumVersion(saveOptions); + if (m_version != minVersion) { + result += argsToString(""); + } + result += argsToString(""); + if (m_saveOptions != saveOptions) { + result += argsToString(""); + } + const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() : EntryStatistics(); + result += argsToString("
Path:", m_path, "
Version:", m_version, "
(on disk, after saving: ", minVersion, ")
Features:", flagsToString(m_saveOptions), "
(on disk, after saving: ", flagsToString(saveOptions), ")
Number of categories:", stats.nodeCount, "
Number of accounts:", + stats.accountCount, "
Number of fields:", stats.fieldCount, "
"); + return result; +} + +/*! + * \brief Returns a comma-separated string for the specified \a flags. + */ +string flagsToString(PasswordFileOpenFlags flags) +{ + vector options; + if (flags & PasswordFileOpenFlags::ReadOnly) { + options.emplace_back("read-only"); + } + if (options.empty()) { + options.emplace_back("none"); + } + return joinStrings(options, ", "); +} + +/*! + * \brief Returns a comma-separated string for the specified \a flags. + */ +string flagsToString(PasswordFileSaveFlags flags) +{ + vector options; + options.reserve(3); + if (flags & PasswordFileSaveFlags::Encryption) { + options.emplace_back("encryption"); + } + if (flags & PasswordFileSaveFlags::Compression) { + options.emplace_back("compression"); + } + if (flags & PasswordFileSaveFlags::PasswordHashing) { + options.emplace_back("password hashing"); + } + if (options.empty()) { + options.emplace_back("none"); + } + return joinStrings(options, ", "); +} + } // namespace Io diff --git a/io/passwordfile.h b/io/passwordfile.h index e556843..b5f8277 100644 --- a/io/passwordfile.h +++ b/io/passwordfile.h @@ -22,6 +22,8 @@ enum class PasswordFileOpenFlags : uint64 { Default = None, }; +std::string PASSWORD_FILE_EXPORT flagsToString(PasswordFileOpenFlags flags); + constexpr PasswordFileOpenFlags operator|(PasswordFileOpenFlags lhs, PasswordFileOpenFlags rhs) { return static_cast( @@ -48,6 +50,8 @@ enum class PasswordFileSaveFlags : uint64 { Default = Encryption | Compression | PasswordHashing, }; +std::string PASSWORD_FILE_EXPORT flagsToString(PasswordFileSaveFlags flags); + constexpr PasswordFileSaveFlags operator|(PasswordFileSaveFlags lhs, PasswordFileSaveFlags rhs) { return static_cast( @@ -104,6 +108,10 @@ public: std::string &encryptedExtendedHeader(); const std::string &encryptedExtendedHeader() const; std::size_t size(); + uint32 version() const; + PasswordFileOpenFlags openOptions() const; + PasswordFileSaveFlags saveOptions() const; + std::string summary(PasswordFileSaveFlags saveOptions) const; private: std::string m_path; @@ -114,6 +122,9 @@ private: IoUtilities::NativeFileStream m_file; IoUtilities::BinaryReader m_freader; IoUtilities::BinaryWriter m_fwriter; + uint32 m_version; + PasswordFileOpenFlags m_openOptions; + PasswordFileSaveFlags m_saveOptions; }; /*! @@ -124,6 +135,31 @@ inline IoUtilities::NativeFileStream &PasswordFile::fileStream() return m_file; } +/*! + * \brief Returns the file version used the last time when saving the file (the version of the file as it is on the disk). + * \remarks The version might change when re-saving with different options. See mininumVersion(). + */ +inline uint32 PasswordFile::version() const +{ + return m_version; +} + +/*! + * \brief Returns the options used to open the file. + */ +inline PasswordFileOpenFlags PasswordFile::openOptions() const +{ + return m_openOptions; +} + +/*! + * \brief Returns the save options used the last time when saving the file. + */ +inline PasswordFileSaveFlags PasswordFile::saveOptions() const +{ + return m_saveOptions; +} + } // namespace Io #endif // PASSWORD_FILE_IO_PASSWORD_FILE_H