Skip to content
Snippets Groups Projects
Verified Commit 61aa3d49 authored by Daniel Lyubomirov's avatar Daniel Lyubomirov
Browse files

[17] crypto storage locking

parent a4531d95
No related branches found
No related tags found
1 merge request!97Crypto Storage and LoginWithNewDevice and LoginWithPreviouslyAddedDevice APIs
Showing
with 397 additions and 312 deletions
......@@ -60,6 +60,7 @@ set(VEREIGNLIB_SRC
vereign/grpc/service_registry.cc
vereign/grpc/server.cc
vereign/sqlite/statement.cc
vereign/sqlite/connection.cc
vereign/bytes/view_dump.cc
......@@ -75,6 +76,7 @@ set(VEREIGNLIB_SRC
vereign/crypto/digest.cc
vereign/kvstore/lock.cc
vereign/kvstore/sqlite_storage.cc
vereign/kvstore/crypto_storage.cc
......
......@@ -12,39 +12,5 @@ auto main(int argc, char** argv) -> int {
boost::ignore_unused(argc);
boost::ignore_unused(argv);
// fs::path ;
// std::wstring s = L"c:\\проба.txt";
std::string path = "c:\\ztrash\\проба.txt";
std::wstring ws = vereign::string::widen(path);
std::string p2 = vereign::string::narrow(ws);
std::cout << vereign::bytes::dump(vereign::bytes::View(path)) << std::endl;
std::cout << vereign::bytes::dump(vereign::bytes::View(p2)) << std::endl;
if (p2 == path) {
std::cout << "yes" << std::endl;
}
std::cout << vereign::bytes::dump(vereign::bytes::View(ws)) << std::endl;
// std::ofstream f{ws};
// std::wcout << ws;
std::cout << path << std::endl;
std::wcout << ws << std::endl;
// std::cout << "проба" << std::endl;
// wchar_t ru[] = L"Привет"; //Russian language
// std::wcout << ru;
// std::cout << std::endl;
// std::cout << "Temp directory is " << fs::temp_directory_path() << '\n';
// std::cout << "Home directory is " << fs::user << '\n';
// p.wstring();
// std::cout << p.string() << std::endl;;
// wchar_t* in = L"ทดสอบ"; // thai language
// char* out=(char *)malloc(15);
// WideCharToMultiByte(874, 0, in, 15, out, 15, NULL, NULL);
// printf(out); // result is correctly in Thai although not neat
return 1;
return 0;
}
......@@ -80,6 +80,10 @@ public:
return std::memcmp(data_, other.data_, size_) == 0;
}
auto operator!=(View other) const noexcept -> bool {
return !(*this == other);
}
auto operator[](std::size_t index) const -> const uint8_t& {
if (index >= size_ ) {
throw std::runtime_error("index out of bounds");
......
......@@ -9,7 +9,7 @@ namespace vereign::grpc {
/**
* BindError is thrown when the Server::Server could not start listening.
*/
class BindError: public virtual std::exception {
class BindError: public std::exception {
public:
auto what() const noexcept -> const char* override {
return "gRPC listen failed";
......
......@@ -2,24 +2,25 @@
#include <vereign/kvstore/detail/value_encoder.hh>
#include <vereign/kvstore/errors.hh>
#include <vereign/kvstore/lock.hh>
#include <vereign/crypto/digest.hh>
#include <vereign/crypto/errors.hh>
#include <vereign/crypto/rand.hh>
#include <vereign/crypto/rsa.hh>
#include <vereign/crypto/aes.hh>
#include <vereign/crypto/bio.hh>
#include <openssl/base.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/aes.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <chrono>
namespace {
// FIXME: should these be injected and provided by the integrator
constexpr int iterations = 1 << 18;
constexpr int saltSizeBytes = 64;
constexpr int aesKeySizeBytes = 32;
constexpr int lockRetryCount = 10;
constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
}
namespace vereign::kvstore::detail {
......@@ -29,12 +30,14 @@ CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage)
{}
void CryptoStorageImpl::Open(const std::string& pin) {
kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
bytes::Buffer salt;
auto iterations = storage_.GetInt64("master_key_iterations");
auto iterations = storage_.GetInt64("__master_key_iterations");
if (iterations == 0) {
throw StorageNotInitializedError{};
}
storage_.GetBytes("master_key_salt", salt);
storage_.GetBytes("__master_key_salt", salt);
bytes::Buffer key{aesKeySizeBytes};
......@@ -54,6 +57,16 @@ void CryptoStorageImpl::Open(const std::string& pin) {
key.IncSize(aesKeySizeBytes);
key_ = std::move(key);
// FIXME: write tests for tampering and regular identity change
try {
bytes::Buffer tag;
GetBytes("__tag", tag);
tag_ = std::move(tag);
} catch (const crypto::Error& err) {
throw IdentityChanged{};
}
}
void CryptoStorageImpl::Reset(const std::string& pin) {
......@@ -76,14 +89,25 @@ void CryptoStorageImpl::Reset(const std::string& pin) {
}
key.IncSize(aesKeySizeBytes);
key_ = std::move(key);
storage_.PutInt64("master_key_iterations", iterations);
storage_.PutBytes("master_key_salt", salt.View());
bytes::Buffer tag{saltSizeBytes};
crypto::Rand(tag);
key_ = std::move(key);
{
kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
storage_.DeleteAll();
encryptBytes("__tag", tag.View());
tag_ = std::move(tag);
storage_.PutInt64("__master_key_iterations", iterations);
storage_.PutBytes("__master_key_salt", salt.View());
}
}
void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
void CryptoStorageImpl::encryptBytes(const std::string& key, bytes::View value) {
if (key_.Size() == 0) {
throw Error("crypto storage is not initialized");
}
......@@ -100,7 +124,26 @@ void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
storage_.PutBytes(key, encoded_value.View());
}
void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
bytes::Buffer tag;
try {
GetBytes("__tag", tag);
} catch (const crypto::Error& err) {
throw IdentityChanged{};
}
if (tag.View() != tag_.View()) {
throw IdentityChanged{};
}
encryptBytes(key, value);
}
void CryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) {
kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
if (key_.Size() == 0) {
throw Error("crypto storage is not initialized");
}
......
......@@ -19,10 +19,15 @@ public:
void PutBytes(const std::string& key, bytes::View value);
void GetBytes(const std::string& key, bytes::Buffer& value);
private:
void encryptBytes(const std::string& key, bytes::View value);
private:
kvstore::Storage& storage_;
bytes::Buffer key_;
bytes::Buffer tag_;
};
} // namespace vereign::kvstore::detail
......
......@@ -21,6 +21,22 @@ public:
}
};
class LockError : public Error {
public:
LockError()
: Error{"cannot acquire storage lock"}
{
}
};
class IdentityChanged : public Error {
public:
IdentityChanged()
: Error{"identity has been changed"}
{
}
};
} // namespace vereign::kvstore
#endif // __VEREIGN_KVSTORE_ERRORS_HH
#include <vereign/kvstore/lock.hh>
#include <vereign/kvstore/errors.hh>
#include <thread>
#include <sqlite3.h>
namespace vereign::kvstore {
Lock::Lock(Storage& storage)
: storage_{storage}
{
storage.Lock();
}
Lock::Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_interval)
: storage_{storage}
{
for (int i = 0; i < retry_count; i++) {
try {
storage.Lock();
return;
} catch (const LockError& err) {
std::this_thread::sleep_for(sleep_interval);
continue;
}
}
throw LockError{};
}
Lock::~Lock() noexcept {
storage_.Unlock();
}
} // namespace vereign::kvstore
#ifndef __VEREIGN_KVSTORE_LOCK_HH
#define __VEREIGN_KVSTORE_LOCK_HH
#include <vereign/kvstore/storage.hh>
#include <chrono>
namespace vereign::kvstore {
class Lock {
public:
explicit Lock(Storage& storage);
Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_interval);
~Lock() noexcept;
Lock(const Lock&) = delete;
auto operator=(const Lock&) -> Lock& = delete;
private:
Storage& storage_;
};
} // namespace vereign::kvstore
#endif // __VEREIGN_KVSTORE_LOCK_HH
#include <vereign/kvstore/sqlite_storage.hh>
#include <vereign/encoding/binary.hh>
#include <vereign/kvstore/errors.hh>
#include <vereign/kvstore/lock.hh>
#include <vereign/encoding/binary.hh>
#include <boost/optional.hpp>
#include <vereign/core/lock_guard.hh>
#include <vereign/sqlite/errors.hh>
#include <sqlite3.h>
#include <mutex>
#include <array>
namespace {
constexpr int createTableRetryCount = 10;
constexpr auto createTableRetrySleep = std::chrono::milliseconds{1000};
}
namespace vereign::kvstore {
namespace detail {
SqliteStorage::SqliteStorage(const std::string& db_path)
: db_{db_path}
{
kvstore::Lock l{*this, createTableRetryCount, createTableRetrySleep};
class SqliteLock {
public:
SqliteLock(sqlite::Connection& db)
: db_{db}
{
}
db_.Execute(R"(
CREATE TABLE IF NOT EXISTS storage (
key TEXT PRIMARY KEY NOT NULL,
value BLOB
);
)");
}
~SqliteLock() {
Unlock();
SqliteStorage::~SqliteStorage() {
if (lock_count_ != 0) {
db_.Commit();
}
}
void Lock() {
if (!tr_) {
tr_ = db_.BeginExplicitTransaction();
}
void SqliteStorage::Lock() {
if (lock_count_ != 0) {
lock_count_++;
count_++;
return;
}
void Unlock() {
if (count_ == 0 || !tr_) {
throw Error{"SqliteStorage unexpected call Unlock with non existent lock"};
try {
db_.BeginExplicitTransaction();
lock_count_++;
} catch (const sqlite::Error& err) {
if (err.code() == SQLITE_BUSY) {
throw LockError{};
}
count_--;
if (count_ == 0) {
tr_->Commit();
tr_.reset();
}
throw;
}
private:
int count_ = 0;
boost::optional<sqlite::Transaction> tr_;
sqlite::Connection& db_;
};
}
SqliteStorage::SqliteStorage(const std::string& db_path)
: db_{db_path},
lock_{std::make_unique<detail::SqliteLock>(db_)}
{
db_.Execute(R"(
CREATE TABLE IF NOT EXISTS storage (
key TEXT PRIMARY KEY NOT NULL,
value BLOB
);
)");
void SqliteStorage::Unlock() {
if (lock_count_ == 0) {
throw Error{"unexpected call Unlock with non existent lock"};
}
lock_count_--;
if (lock_count_ == 0) {
db_.Commit();
}
}
SqliteStorage::~SqliteStorage() = default;
void SqliteStorage::Lock() {
lock_->Lock();
}
void SqliteStorage::DeleteAll() {
kvstore::Lock l{*this};
void SqliteStorage::Unlock() {
lock_->Unlock();
db_.Execute("DELETE FROM storage;");
}
void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
core::LockGuard<detail::SqliteLock> l{*lock_};
kvstore::Lock l{*this};
auto stmt = db_.Prepare("REPLACE INTO storage(key, value) VALUES(?, ?);");
stmt.BindText(1, key);
......@@ -83,7 +84,7 @@ void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
}
void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
core::LockGuard<detail::SqliteLock> l{*lock_};
kvstore::Lock l{*this};
auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
stmt.BindText(1, key);
......@@ -95,7 +96,7 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
}
void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
core::LockGuard<detail::SqliteLock> l{*lock_};
kvstore::Lock l{*this};
std::array<uint8_t, 8> encoded;
encoding::binary::EncodeUint64(encoded.data(), value);
......@@ -107,7 +108,7 @@ void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
}
auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
core::LockGuard<detail::SqliteLock> l{*lock_};
kvstore::Lock l{*this};
auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
stmt.BindText(1, key);
......
......@@ -4,14 +4,8 @@
#include <vereign/kvstore/storage.hh>
#include <vereign/sqlite/connection.hh>
#include <memory>
namespace vereign::kvstore {
namespace detail {
class SqliteLock;
}
class SqliteStorage : public Storage {
public:
SqliteStorage(const std::string& db_path);
......@@ -23,6 +17,8 @@ public:
void Lock() override;
void Unlock() override;
void DeleteAll() override;
void PutBytes(const std::string& key, bytes::View value) override;
void GetBytes(const std::string& key, bytes::Buffer& value) override;
......@@ -31,7 +27,7 @@ public:
private:
sqlite::Connection db_;
std::unique_ptr<detail::SqliteLock> lock_;
int lock_count_ = 0;
};
} // namespace vereign::kvstore
......
......@@ -10,6 +10,8 @@ public:
virtual void Lock() = 0;
virtual void Unlock() = 0;
virtual void DeleteAll() = 0;
virtual void PutBytes(const std::string& key, bytes::View value) = 0;
virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0;
......
#include <stdexcept>
#include <vereign/sqlite/connection.hh>
#include <vereign/sqlite/errors.hh>
#include <vereign/sqlite/errors.hh>
#include <vereign/core/scope_guard.hh>
#include <fmt/format.h>
#include <sqlite3.h>
#include <memory>
#include <iostream>
namespace vereign::sqlite {
Transaction::Transaction(sqlite3* db)
: finished_{false},
db_{db}
{}
Transaction::Transaction(Transaction&& other)
: finished_{other.finished_},
db_{other.db_}
Connection::Connection(const std::string& path)
: db_{nullptr}
{
other.finished_ = true;
}
auto Transaction::operator=(Transaction&& other) -> Transaction& {
if (!finished_) {
throw std::runtime_error{"cannot move assign to non finished transaction"};
auto rc = sqlite3_open(path.data(), &db_);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("open db failed, err: {}", sqlite3_errmsg(db_))};
}
}
db_ = other.db_;
finished_ = other.finished_;
other.finished_ = true;
return *this;
Connection::~Connection() noexcept {
sqlite3_close(db_);
}
void Transaction::Commit() {
void Connection::BeginExplicitTransaction() {
char* errMsg = nullptr;
auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
auto rc = sqlite3_exec(db_, "COMMIT", nullptr, nullptr, &errMsg);
auto rc = sqlite3_exec(db_, "BEGIN EXCLUSIVE", nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK) {
throw Error(rc, fmt::format("commit transaction failed, err: {}", errMsg));
throw Error(rc, fmt::format("starting transaction failed, err: {}", errMsg));
}
finished_ = true;
}
void Transaction::Rollback() {
void Connection::Commit() {
char* errMsg = nullptr;
auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
auto rc = sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK) {
throw Error(rc, fmt::format("rollback transaction failed, err: {}", errMsg));
}
finished_ = true;
}
Transaction::~Transaction() {
if (!finished_) {
Rollback();
}
}
Statement::Statement(sqlite3* db, sqlite3_stmt* stmt)
: db_{db},
stmt_{stmt}
{}
void Statement::Finalize() {
sqlite3_finalize(stmt_);
}
Statement::~Statement() {
sqlite3_finalize(stmt_);
}
void Statement::BindBlob(int index, bytes::View blob) {
auto rc = sqlite3_bind_blob64(stmt_, index, blob.CharData(), blob.Size(), SQLITE_STATIC);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("bind blob parameter failed, err: {}", sqlite3_errmsg(db_))};
}
}
void Statement::BindText(int index, const std::string& text) {
auto rc = sqlite3_bind_text(stmt_, index, text.data(), text.size(), SQLITE_STATIC);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("bind text parameter failed, err: {}", sqlite3_errmsg(db_))};
}
}
auto Statement::Step() -> bool {
auto rc = sqlite3_step(stmt_);
switch (rc) {
case SQLITE_DONE:
return true;
case SQLITE_ROW:
return false;
default:
throw Error{rc, fmt::format("executing statement failed, err: {}", sqlite3_errmsg(db_))};
}
}
auto Statement::GetColumnBlob(int index) -> bytes::View {
auto size = sqlite3_column_bytes(stmt_, index);
auto blob = sqlite3_column_blob(stmt_, index);
return bytes::View{blob, static_cast<size_t>(size)};
}
auto Statement::GetColumnText(int index) -> std::string_view {
std::size_t size = sqlite3_column_bytes(stmt_, index);
auto blob = sqlite3_column_text(stmt_, index);
return std::string_view{reinterpret_cast<const char*>(blob), size};
}
void Statement::ResetAndClearBindings() {
auto rc = sqlite3_clear_bindings(stmt_);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("statement reset bindings failed, err: {}", sqlite3_errmsg(db_))};
}
Reset();
}
void Statement::Reset() {
auto rc = sqlite3_reset(stmt_);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("statement reset failed, err: {}", sqlite3_errmsg(db_))};
}
}
Connection::Connection(const std::string& path)
: db_{nullptr}
{
auto rc = sqlite3_open(path.data(), &db_);
auto rc = sqlite3_exec(db_, "COMMIT", nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("open db failed, err: {}", sqlite3_errmsg(db_))};
throw Error(rc, fmt::format("commit transaction failed, err: {}", errMsg));
}
}
Connection::~Connection() noexcept {
sqlite3_close(db_);
}
auto Connection::BeginExplicitTransaction() -> Transaction {
void Connection::Rollback() {
char* errMsg = nullptr;
auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
auto rc = sqlite3_exec(db_, "BEGIN EXCLUSIVE", nullptr, nullptr, &errMsg);
auto rc = sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK) {
throw Error(rc, fmt::format("starting transaction failed, err: {}", errMsg));
throw Error(rc, fmt::format("rollback transaction failed, err: {}", errMsg));
}
return Transaction{db_};
}
void Connection::Execute(const std::string& sql) {
char* errMsg = nullptr;
auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
......
#ifndef __VEREIGN_SQLITE_CONNECTION_HH
#define __VEREIGN_SQLITE_CONNECTION_HH
#include <vereign/bytes/view.hh>
#include <vereign/bytes/bytes.hh>
#include <string>
#include <vereign/sqlite/statement.hh>
struct sqlite3;
struct sqlite3_stmt;
namespace vereign::sqlite {
class Transaction {
private:
friend class Connection;
explicit Transaction(sqlite3* db);
public:
void Commit();
void Rollback();
Transaction(Transaction&&);
auto operator=(Transaction&&) -> Transaction&;
~Transaction();
Transaction(const Transaction&) = delete;
auto operator=(const Transaction&) -> Transaction& = delete;
private:
bool finished_;
sqlite3* db_;
};
class Statement {
private:
friend class Connection;
explicit Statement(sqlite3* db, sqlite3_stmt* stmt);
public:
~Statement();
void BindBlob(int index, bytes::View blob);
void BindText(int index, const std::string& text);
auto Step() -> bool;
auto GetColumnBlob(int index) -> bytes::View;
auto GetColumnText(int index) -> std::string_view;
void Reset();
void ResetAndClearBindings();
void Finalize();
private:
sqlite3* db_;
sqlite3_stmt* stmt_;
};
class Connection {
public:
Connection(const std::string& path);
......@@ -66,9 +16,13 @@ public:
Connection(const Connection&) = delete;
auto operator=(const Connection&) -> Connection& = delete;
auto BeginExplicitTransaction() -> Transaction;
void BeginExplicitTransaction();
void Commit();
void Rollback();
void Execute(const std::string& sql);
auto Prepare(const std::string& sql) -> Statement;
private:
sqlite3* db_;
};
......
......@@ -5,15 +5,12 @@
namespace vereign::sqlite {
class Error : virtual std::exception {
class Error : public std::runtime_error {
public:
Error(int code, std::string&& msg)
: code_{code},
msg_{std::move(msg)}
{}
auto what() const noexcept -> const char* override {
return msg_.data();
Error(int code, const std::string& msg)
: std::runtime_error(msg),
code_{code}
{
}
auto code() const noexcept -> int {
......@@ -22,7 +19,6 @@ public:
private:
int code_;
std::string msg_;
};
} // namespace vereign::sqlite
......
#include <vereign/sqlite/statement.hh>
#include <vereign/sqlite/errors.hh>
#include <fmt/format.h>
#include <sqlite3.h>
namespace vereign::sqlite {
Statement::Statement(sqlite3* db, sqlite3_stmt* stmt)
: db_{db},
stmt_{stmt}
{}
void Statement::Finalize() {
sqlite3_finalize(stmt_);
}
Statement::~Statement() {
sqlite3_finalize(stmt_);
}
void Statement::BindBlob(int index, bytes::View blob) {
auto rc = sqlite3_bind_blob64(stmt_, index, blob.CharData(), blob.Size(), SQLITE_STATIC);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("bind blob parameter failed, err: {}", sqlite3_errmsg(db_))};
}
}
void Statement::BindText(int index, const std::string& text) {
auto rc = sqlite3_bind_text(stmt_, index, text.data(), text.size(), SQLITE_STATIC);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("bind text parameter failed, err: {}", sqlite3_errmsg(db_))};
}
}
auto Statement::Step() -> bool {
auto rc = sqlite3_step(stmt_);
switch (rc) {
case SQLITE_DONE:
return true;
case SQLITE_ROW:
return false;
default:
throw Error{rc, fmt::format("executing statement failed, err: {}", sqlite3_errmsg(db_))};
}
}
auto Statement::GetColumnBlob(int index) -> bytes::View {
auto size = sqlite3_column_bytes(stmt_, index);
auto blob = sqlite3_column_blob(stmt_, index);
return bytes::View{blob, static_cast<size_t>(size)};
}
auto Statement::GetColumnText(int index) -> std::string_view {
std::size_t size = sqlite3_column_bytes(stmt_, index);
auto blob = sqlite3_column_text(stmt_, index);
return std::string_view{reinterpret_cast<const char*>(blob), size};
}
void Statement::ResetAndClearBindings() {
auto rc = sqlite3_clear_bindings(stmt_);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("statement reset bindings failed, err: {}", sqlite3_errmsg(db_))};
}
Reset();
}
void Statement::Reset() {
auto rc = sqlite3_reset(stmt_);
if (rc != SQLITE_OK) {
throw Error{rc, fmt::format("statement reset failed, err: {}", sqlite3_errmsg(db_))};
}
}
} // namespace vereign::sqlite
#ifndef __VEREIGN_SQLITE_STATEMENT_HH
#define __VEREIGN_SQLITE_STATEMENT_HH
#include <vereign/bytes/view.hh>
#include <vereign/bytes/bytes.hh>
#include <string>
struct sqlite3;
struct sqlite3_stmt;
namespace vereign::sqlite {
class Statement {
private:
friend class Connection;
explicit Statement(sqlite3* db, sqlite3_stmt* stmt);
public:
~Statement();
void BindBlob(int index, bytes::View blob);
void BindText(int index, const std::string& text);
auto Step() -> bool;
auto GetColumnBlob(int index) -> bytes::View;
auto GetColumnText(int index) -> std::string_view;
void Reset();
void ResetAndClearBindings();
void Finalize();
private:
sqlite3* db_;
sqlite3_stmt* stmt_;
};
} // namespace vereign::sqlite
#endif // __VEREIGN_SQLITE_STATEMENT_HH
#ifndef __VEREIGN_TEST_UTIL_ERROR_HH
#define __VEREIGN_TEST_UTIL_ERROR_HH
#include <optional>
namespace vereign::test {
template <class Error, class Fun>
auto CatchError(Fun fn) -> std::optional<Error> {
try {
fn();
return {};
} catch (const Error& err) {
return err;
}
}
} // namespace test
#endif // __VEREIGN_TEST_UTIL_ERROR_HH
......@@ -28,6 +28,8 @@ list(APPEND TESTS_SRC
restapi/client_test.cc
restapi/client_session_test.cc
kvstore/lock_test.cc
kvstore/sqlite_storage_test.cc
kvstore/crypto_storage_test.cc
identity/provider_test.cc
......
......@@ -17,6 +17,7 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
auto storage_path = core::TempFilePath("test_db_");
core::RemoveFileGuard rm{storage_path};
// put value
{
auto kvstorage = kvstore::SqliteStorage(storage_path.string());
kvstore::CryptoStorage storage{kvstorage};
......@@ -26,6 +27,7 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
storage.PutBytes("test", bytes::View(v));
}
// with another storage instance get the value
{
auto kvstorage = kvstore::SqliteStorage(storage_path.string());
kvstore::CryptoStorage storage{kvstorage};
......@@ -36,28 +38,25 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
CHECK(v.View().String() == "test value");
}
}
TEST_CASE("CryptoStorage::PutBytes", "[vereign/kvstore]") {
auto storage_path = core::TempFilePath("test_db_");
core::RemoveFileGuard rm{storage_path};
bytes::Buffer big_value{100000};
crypto::Rand(big_value);
{
auto kvstorage = kvstore::SqliteStorage(storage_path.string());
kvstore::CryptoStorage storage{kvstorage};
auto kvstorage = kvstore::SqliteStorage(storage_path.string());
kvstore::CryptoStorage storage{kvstorage};
storage.Reset("foo");
storage.PutBytes("test", big_value.View());
}
storage.Reset("foo");
storage.PutBytes("test", big_value.View());
{
auto kvstorage = kvstore::SqliteStorage(storage_path.string());
kvstore::CryptoStorage storage{kvstorage};
bytes::Buffer v;
storage.Open("foo");
storage.GetBytes("test", v);
bytes::Buffer v;
storage.GetBytes("test", v);
REQUIRE(v.Size() == big_value.Size());
REQUIRE(v.Size() == big_value.Size());
auto cmp = std::memcmp(v.View().Data(), big_value.View().Data(), v.Size());
CHECK(cmp == 0);
}
auto cmp = std::memcmp(v.View().Data(), big_value.View().Data(), v.Size());
CHECK(cmp == 0);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment