This is a hand-written reference. The headers in include/qlcrypt/ are
the canonical source. Doxygen comments on every declaration provide
authoritative per-function documentation.
| Header | Purpose |
|---|---|
qlcrypt/qlcrypt.hpp |
Umbrella include for all of the below |
qlcrypt/version.hpp |
VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_STRING, VERSION_NUMBER |
qlcrypt/status.hpp |
enum class Status, ok(), toString() |
qlcrypt/constants.hpp |
MAGIC_QLEN, FORMAT_VERSION_V2, CIPHER_AES_256_GCM, HEADER_SIZE_V2, NONCE_LEN, TAG_LEN, KEY_LEN_V2, KEY_LEN_V1, FINGERPRINT_LEN, MAX_PLAINTEXT_LEN |
qlcrypt/header_info.hpp |
struct HeaderInfo (parsed header) |
qlcrypt/key_db.hpp |
class KeyDB (opaque key holder) |
qlcrypt/file_crypt.hpp |
class FileCrypt (stateless encrypt/decrypt/inspect) |
qlcrypt/processor.hpp |
class Processor (decrypt-once cache) |
enum class qlcrypt::Status { Ok, FileNotFound, ..., InternalError };
constexpr bool ok(Status s) noexcept;
std::string_view toString(Status s) noexcept;Every public API returns Status; check with qlcrypt::ok() or
compare to Status::Ok. toString() returns a static string suitable
for logging.
class KeyDB {
public:
KeyDB(); // empty
static Status create(KeyDB& out,
std::optional<std::string> customerId = {});
static Status load(std::string_view path, KeyDB& out);
Status save(std::string_view path) const; // always v2
bool hasKey() const noexcept;
bool isLegacyV1() const noexcept;
std::array<std::uint8_t, FINGERPRINT_LEN> fingerprint() const noexcept;
std::string fingerprintHex() const; // 16-char lowercase
const std::string& customerId() const noexcept;
};- Move-only. Destructor securely wipes key bytes.
create()generates a random 256-bit key.load()accepts either v2 or legacy v1 key databases; v1 databases setisLegacyV1() == trueand are decrypt-only.save()always writes v2 format.
class FileCrypt {
public:
explicit FileCrypt(const KeyDB& keys); // non-owning ref
Status encryptFile(std::string_view in,
std::string_view out) const; // always v2
Status decryptFile(std::string_view in,
std::string& out) const; // v2, falls back to v1
Status verifyFile(std::string_view in) const; // decrypt + wipe
static Status inspect(std::string_view in,
HeaderInfo& out); // no key required
};FileCryptis move-only and re-entrant forconstmethods.- The bound
KeyDB&must outlive theFileCrypt. encryptFile()uses a fresh random 12-byte nonce per call and binds the entire 27-byte header as AAD.decryptFile()first attempts v2; if the magic is absent and the loaded key database is v1, it falls back. Any v2 header with bad fields short-circuits without calling Crypto++.
class Processor {
public:
explicit Processor(KeyDB&& keys); // takes ownership
Status decryptFile(std::string_view path,
std::string_view& outView);
void clearCache() noexcept;
std::size_t cacheSize() const noexcept;
std::size_t decryptCallCount() const noexcept;
std::size_t decryptMissCount() const noexcept;
};- The decryption cache is keyed on
weakly_canonical(path). - Subsequent calls with the same path return a
string_viewinto the same buffer — never a copy. The view is valid untilclearCache()or destruction. - Thread-safe: concurrent calls on disjoint paths run in parallel; on the same path, one decryption wins and the other returns the cached view.
- The destructor securely wipes every cached buffer and unlocks any pinned pages.
#include <qlcrypt/qlcrypt.hpp>
int loadEncryptedAsset(const std::filesystem::path& keyDbPath,
const std::filesystem::path& enPath,
std::string_view& outView,
qlcrypt::Processor& processor) {
if (!processor.cacheSize()) {
// first call — KeyDB is already owned by the Processor
}
return qlcrypt::ok(processor.decryptFile(enPath.string(), outView)) ? 0 : -1;
}- All public types are
finalfrom a layout perspective: PIMPL'd classes (KeyDB,FileCrypt,Processor) own a singleunique_ptr<Impl>. Adding state to the impl never breaks ABI. - No virtual functions in any public class.
- No exceptions cross the API boundary.
#if QLCRYPT_VERSION_NUMBER < 10000 // less than 1.0.0
# error "qlcrypt 1.0.0 or later required"
#endifNote: pre-1.0 minor bumps (0.x → 0.y) may break ABI. Tag a known-good commit/tag in your submodule pin until v1.0.0 ships.