diff options
author | dec05eba <0xdec05eba@gmail.com> | 2018-05-14 00:20:11 +0200 |
---|---|---|
committer | dec05eba <0xdec05eba@gmail.com> | 2018-05-14 00:27:29 +0200 |
commit | 9af086151e6d9d3fe88f9e3e21797812a3e701ba (patch) | |
tree | a11889f81b8f929c11dccbd2c1c3b3cd74fbb740 /src | |
parent | ebff7aeafded4dd9d245dbcfc80d9c8d83fe1242 (diff) |
Replace files with sqlite
Using sqlite because sqlite has transactions, storing/loading from files
automatically, unloading data that is not accessed often.
Removed cosmetic data (node name, username). They can be added using
addData by the application that uses odhtdb instead.
Database callback functions can now be called with stored data using
database.loadNode function.
TODO: Add local user storage (with password) back, it has been temorary
disabled
Diffstat (limited to 'src')
-rw-r--r-- | src/Database.cpp | 254 | ||||
-rw-r--r-- | src/DatabaseStorage.cpp | 1503 | ||||
-rw-r--r-- | src/Encryption.cpp | 7 | ||||
-rw-r--r-- | src/Group.cpp | 10 | ||||
-rw-r--r-- | src/LocalUser.cpp | 4 | ||||
-rw-r--r-- | src/Signature.cpp | 10 | ||||
-rw-r--r-- | src/User.cpp | 4 | ||||
-rw-r--r-- | src/sql/SqlQuery.cpp | 129 |
8 files changed, 917 insertions, 1004 deletions
diff --git a/src/Database.cpp b/src/Database.cpp index 41d3798..88ac8e4 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -61,7 +61,7 @@ namespace odhtdb return DataView(result, allocationSize); } - DatabaseCreateResponse::DatabaseCreateResponse(LocalUser *_nodeAdminUser, const shared_ptr<OwnedMemory> &_key, const shared_ptr<Hash> &_hash) : + DatabaseCreateResponse::DatabaseCreateResponse(LocalUser *_nodeAdminUser, shared_ptr<OwnedMemory> _key, shared_ptr<Hash> _hash) : nodeAdminUser(_nodeAdminUser), key(_key), hash(_hash) @@ -84,10 +84,10 @@ namespace odhtdb return hash; } - Database::Database(const char *bootstrapNodeAddr, u16 port, const boost::filesystem::path &storageDir) : - onCreateNodeCallbackFunc(nullptr), - onAddNodeCallbackFunc(nullptr), - onAddUserCallbackFunc(nullptr), + Database::Database(const char *bootstrapNodeAddr, u16 port, const boost::filesystem::path &storageDir, DatabaseCallbackFuncs callbackFuncs) : + onCreateNodeCallbackFunc(callbackFuncs.createNodeCallbackFunc), + onAddNodeCallbackFunc(callbackFuncs.addNodeCallbackFunc), + onAddUserCallbackFunc(callbackFuncs.addUserCallbackFunc), databaseStorage(this, storageDir) { node.run(port , { @@ -218,38 +218,35 @@ namespace odhtdb u64 dataStartTimestamp = deserializer.extract<u64>(); u8 requestResponseKey[OPENDHT_INFOHASH_LEN]; deserializer.extract(requestResponseKey, OPENDHT_INFOHASH_LEN); - - auto requestedData = databaseStorage.getStorage(*nodeToSeed.getRequestHash()); - if(!requestedData) - { - Log::debug("No data found for hash %s, unable to serve peer", nodeToSeed.getRequestHash()->toString().c_str()); - return true; - } - InfoHash requestResponseInfoHash(requestResponseKey, OPENDHT_INFOHASH_LEN); if(dataStartTimestamp == 0) { - Log::debug("Request: Sent create packet to requesting peer"); - node.put(requestResponseInfoHash, Value((u8*)requestedData->data.data, requestedData->data.size), [](bool ok) + databaseStorage.fetchNodeRaw(*nodeToSeed.getRequestHash(), [this, requestResponseInfoHash](const DataView rawData) { - if(!ok) - Log::error("Failed to put response for old data for 'create' data"); + Log::debug("Request: Sent create packet to requesting peer"); + Value value((u8*)rawData.data, rawData.size); + node.put(requestResponseInfoHash, move(value), [](bool ok) + { + if(!ok) + Log::error("Failed to put response for old data for 'create' data"); + }); }); } - for(auto requestedObject : requestedData->objects) + databaseStorage.fetchNodeAddDataRaw(*nodeToSeed.getRequestHash(), [this, requestResponseInfoHash](const DataView rawData) { - node.put(requestResponseInfoHash, Value((u8*)requestedObject->data.data, requestedObject->data.size), [](bool ok) + Value value((u8*)rawData.data, rawData.size); + node.put(requestResponseInfoHash, move(value), [](bool ok) { if(!ok) Log::error("Failed to put response for old data for 'add' data"); }); - } + }); } - catch (sibs::DeserializeException &e) + catch (std::exception &e) { - Log::warn("Failed to deserialize 'get old data' request: %s", e.what()); + Log::warn("Failed while serving peer, error: %s", e.what()); } return true; }); @@ -283,19 +280,25 @@ namespace odhtdb seedInfoMap.erase(seedInfoIt); } } + + void Database::loadNode(const Hash &nodeHash) + { + databaseStorage.loadNode(nodeHash); + } - unique_ptr<DatabaseCreateResponse> Database::create(const string &ownerName, const string &nodeName) + unique_ptr<DatabaseCreateResponse> Database::create() { - return create(ownerName, Signature::KeyPair(), nodeName); + return create(Signature::KeyPair()); } - unique_ptr<DatabaseCreateResponse> Database::create(const string &ownerName, const Signature::KeyPair &keyPair, const string &nodeName) + unique_ptr<DatabaseCreateResponse> Database::create(const Signature::KeyPair &creatorKeyPair) { // TODO: Should this be declared static? is there any difference in behavior/performance? boost::uuids::random_generator uuidGen; auto adminGroupId = uuidGen(); - auto adminGroup = new Group("administrator", adminGroupId.data, ADMIN_PERMISSION); - LocalUser *nodeAdminUser = LocalUser::create(keyPair, ownerName, adminGroup); + assert(adminGroupId.size() == GROUP_ID_LENGTH); + auto adminGroup = new Group(adminGroupId.data, ADMIN_PERMISSION); + LocalUser *nodeAdminUser = LocalUser::create(creatorKeyPair, adminGroup); // Header sibs::SafeSerializer serializer; @@ -305,30 +308,18 @@ namespace odhtdb serializer.add((u8*)nodeAdminUser->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); serializer.add(adminGroupId.data, adminGroupId.size()); - // Encrypted body - sibs::SafeSerializer encryptedSerializer; - assert(nodeAdminUser->getName().size() <= 255); - encryptedSerializer.add((u8)nodeAdminUser->getName().size()); - encryptedSerializer.add((u8*)nodeAdminUser->getName().data(), nodeAdminUser->getName().size()); - assert(nodeName.size() <= 255); - encryptedSerializer.add((u8)nodeName.size()); - encryptedSerializer.add((u8*)nodeName.data(), nodeName.size()); - try { - Encryption encryptedBody(DataView(encryptedSerializer.getBuffer().data(), encryptedSerializer.getBuffer().size())); - DataView requestData = combine(serializer, encryptedBody); - shared_ptr<Hash> hashRequestKey = make_shared<Hash>(requestData.data, requestData.size); - databaseStorage.setNodeDecryptionKey(*hashRequestKey, DataView(encryptedBody.getKey().data, encryptedBody.getKey().size)); - databaseStorage.createStorage(*hashRequestKey, adminGroup, timestampCombined, (const u8*)requestData.data, requestData.size, serializer.getBuffer().size()); + unsigned char *encryptionKeyRaw = new unsigned char[ENCRYPTION_KEY_BYTE_SIZE]; + Encryption::generateKey(encryptionKeyRaw); + shared_ptr<OwnedMemory> encryptionKey = make_shared<OwnedMemory>(encryptionKeyRaw, ENCRYPTION_KEY_BYTE_SIZE); + shared_ptr<Hash> hashRequestKey = make_shared<Hash>(serializer.getBuffer().data(), serializer.getBuffer().size()); - assert(encryptedBody.getKey().size == ENCRYPTION_KEY_BYTE_SIZE); - auto key = make_shared<OwnedMemory>(new char[encryptedBody.getKey().size], encryptedBody.getKey().size); - memcpy(key->data, encryptedBody.getKey().data, encryptedBody.getKey().size); + databaseStorage.setNodeDecryptionKey(*hashRequestKey, DataView(encryptionKey->data, encryptionKey->size)); + databaseStorage.createStorage(*hashRequestKey, adminGroup, timestampCombined, (const u8*)serializer.getBuffer().data(), serializer.getBuffer().size()); DhtKey dhtKey(*hashRequestKey); - Value createDataValue((u8*)requestData.data, requestData.size); - delete[] (char*)requestData.data; + Value createDataValue(move(serializer.getBuffer())); node.put(dhtKey.getNewDataListenerKey(), move(createDataValue), [](bool ok) { // TODO: Handle failure to put data @@ -336,7 +327,7 @@ namespace odhtdb Log::warn("Failed to put: %s, what to do?", "Database::create"); }); - return make_unique<DatabaseCreateResponse>(nodeAdminUser, move(key), hashRequestKey); + return make_unique<DatabaseCreateResponse>(nodeAdminUser, encryptionKey, hashRequestKey); } catch (EncryptionException &e) { @@ -354,7 +345,7 @@ namespace odhtdb // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. // How to handle this? string errMsg = "User "; - errMsg += userToPerformActionWith->getName(); + errMsg += userToPerformActionWith->getPublicKey().toString(); errMsg += " is not allowed to perform the operation: ADD_USER"; throw PermissionDeniedException(errMsg); } @@ -372,7 +363,7 @@ namespace odhtdb DataView stagedAddObject = combine(userToPerformActionWith->getPublicKey(), signedRequestData); Hash requestDataHash(stagedAddObject.data, stagedAddObject.size); DataView encryptedDataView((char*)requestData.data + serializer.getBuffer().size(), requestData.size - serializer.getBuffer().size()); - databaseStorage.appendStorage(*nodeInfo.getRequestHash(), requestDataHash, DatabaseOperation::ADD_DATA, userToPerformActionWith, timestampCombined, (u8*)stagedAddObject.data, stagedAddObject.size, encryptedDataView); + databaseStorage.appendStorage(*nodeInfo.getRequestHash(), requestDataHash, DatabaseOperation::ADD_DATA, userToPerformActionWith->getPublicKey(), timestampCombined, (u8*)stagedAddObject.data, stagedAddObject.size, encryptedDataView); delete[] (char*)requestData.data; DhtKey dhtKey(requestDataHash); @@ -391,7 +382,7 @@ namespace odhtdb for(auto group : groups) { const auto &groupPermission = group->getPermission(); - if(groupPermission.getFlag(PermissionType::ADD_USER_LOWER_LEVEL) && groupPermission.getPermissionLevel() < groupToAddUserTo->getPermission().getPermissionLevel()) + if(groupPermission.getFlag(PermissionType::ADD_USER_HIGHER_LEVEL) && groupPermission.getPermissionLevel() < groupToAddUserTo->getPermission().getPermissionLevel()) { return group; } @@ -403,15 +394,15 @@ namespace odhtdb return nullptr; } - void Database::addUser(const DatabaseNode &nodeInfo, const LocalUser *userToPerformActionWith, const string &userToAddName, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo) + void Database::addUser(const DatabaseNode &nodeInfo, const LocalUser *userToPerformActionWith, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo) { auto groupWithAddUserRights = getGroupWithRightsToAddUserToGroup(userToPerformActionWith->getGroups(), groupToAddUserTo); if(!groupWithAddUserRights) { string errMsg = "The user "; - errMsg += userToPerformActionWith->getName(); + errMsg += userToPerformActionWith->getPublicKey().toString(); errMsg += " does not belong to any group that is allowed to add an user to the group "; - errMsg += groupToAddUserTo->getName(); + errMsg += bin2hex((const char*)groupToAddUserTo->getId().data, groupToAddUserTo->getId().size).c_str(); throw PermissionDeniedException(errMsg); } @@ -420,27 +411,22 @@ namespace odhtdb u64 timestampCombined = getSyncedTimestampUtc().getCombined(); serializer.add(timestampCombined); serializer.add(DatabaseOperation::ADD_USER); - - assert(userToAddName.size() <= 255); - usize serializedEncryptedDataOffset = serializer.getBuffer().size(); - serializer.add((u8)userToAddName.size()); - DataView encryptionKey(nodeInfo.getNodeEncryptionKey()->data, ENCRYPTION_KEY_BYTE_SIZE); - Encryption encryptedUserName(DataView((void*)userToAddName.data(), userToAddName.size()), DataView(), encryptionKey); - serializer.add((u8*)encryptedUserName.getNonce().data, ENCRYPTION_NONCE_BYTE_SIZE); - assert(encryptedUserName.getCipherText().size == ENCRYPTION_CHECKSUM_BYTE_SIZE + userToAddName.size()); - serializer.add((u8*)encryptedUserName.getCipherText().data, ENCRYPTION_CHECKSUM_BYTE_SIZE + userToAddName.size()); - usize serializedEncryptedDataSize = serializer.getBuffer().size() - serializedEncryptedDataOffset; + usize additionalDataOffset = serializer.getBuffer().size(); serializer.add((u8*)userToAddPublicKey.getData(), PUBLIC_KEY_NUM_BYTES); serializer.add((uint8_t*)groupToAddUserTo->getId().data, groupToAddUserTo->getId().size); + // TODO: Should this be declared static? is there any difference in behavior/performance? + boost::uuids::random_generator uuidGen; + auto padding = uuidGen(); + assert(padding.size() == 16); + serializer.add(padding.data, padding.size()); + DataView requestData { serializer.getBuffer().data(), serializer.getBuffer().size() }; string signedRequestData = userToPerformActionWith->getPrivateKey().sign(requestData); DataView stagedAddObject = combine(userToPerformActionWith->getPublicKey(), signedRequestData); Hash requestDataHash(stagedAddObject.data, stagedAddObject.size); - DataView encryptedDataView(nullptr, 0); - auto userToAdd = RemoteUser::create(userToAddPublicKey, userToAddName, groupToAddUserTo); - databaseStorage.addUser(*nodeInfo.getRequestHash(), userToAdd); - databaseStorage.appendStorage(*nodeInfo.getRequestHash(), requestDataHash, DatabaseOperation::ADD_USER, userToPerformActionWith, timestampCombined, (u8*)stagedAddObject.data, stagedAddObject.size, encryptedDataView); + DataView additionalDataView((void*)(static_cast<const char*>(requestData.data) + additionalDataOffset), requestData.size - additionalDataOffset); + databaseStorage.appendStorage(*nodeInfo.getRequestHash(), requestDataHash, DatabaseOperation::ADD_USER, userToPerformActionWith->getPublicKey(), timestampCombined, (u8*)stagedAddObject.data, stagedAddObject.size, additionalDataView); DhtKey dhtKey(requestDataHash); Value addDataValue((u8*)stagedAddObject.data, stagedAddObject.size); @@ -506,10 +492,10 @@ namespace odhtdb if(deserializer.getSize() < ENCRYPTION_NONCE_BYTE_SIZE) throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); - auto adminGroup = new Group("administrator", adminGroupId, ADMIN_PERMISSION); + auto adminGroup = new Group(adminGroupId, ADMIN_PERMISSION); // TODO: Username is encrypted, we dont know it... unless we have encryption key, in which case we should modify the user name and set it - auto creatorUser = RemoteUser::create(userPublicKey, "ENCRYPTED USER NAME", adminGroup); - databaseStorage.createStorage(hash, adminGroup, creationDate, value->data.data(), value->data.size(), value->data.size() - deserializer.getSize()); + auto creatorUser = RemoteUser::create(userPublicKey, adminGroup); + databaseStorage.createStorage(hash, adminGroup, creationDate, value->data.data(), value->data.size()); } void Database::deserializeAddRequest(const shared_ptr<dht::Value> &value, const Hash &requestDataHash, const std::shared_ptr<Hash> &nodeHash, const shared_ptr<OwnedMemory> encryptionKey) @@ -548,108 +534,8 @@ namespace odhtdb */ DatabaseOperation operation = deserializerUnsigned.extract<DatabaseOperation>(); -#if 0 - const Hash *node = databaseStorage.getNodeByUserPublicKey(creatorPublicKey); - if(!node) - { - // The user (public key) could belong to a node but we might not have retrieved the node info yet since data may - // not be retrieved in order. - // Data in quarantine is processed when 'create' packet is received or removed after 60 seconds - databaseStorage.addToQuarantine(requestDataHash, creatorPublicKey, creationDate, value->data.data(), value->data.size()); - throw RequestQuarantineException(); - } -#endif - auto creatorUser = databaseStorage.getUserByPublicKey(*nodeHash, creatorPublicKey); - if(!creatorUser) - { - // TODO: Add to quarantine - string errMsg = "User with public key "; - errMsg += creatorPublicKey.toString(); - errMsg += " does not exist in code "; - errMsg += nodeHash->toString(); - throw sibs::DeserializeException(errMsg); - } - - DataView encryptedDataView((void*)deserializerUnsigned.getBuffer(), deserializerUnsigned.getSize()); - - if(operation == DatabaseOperation::ADD_DATA) - { - if(deserializerUnsigned.getSize() < ENCRYPTION_NONCE_BYTE_SIZE) - throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); - - if(!creatorUser->isAllowedToPerformAction(PermissionType::ADD_DATA)) - { - // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, - // or we haven't received the packet that modifies group with the permission to perform the operation. - // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, - // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. - // How to handle this? - string errMsg = "User "; - errMsg += creatorUser->getName(); - errMsg += " is not allowed to add data to node "; - errMsg += nodeHash->toString(); - throw PermissionDeniedException(errMsg); - } - - // TODO: Verify there isn't already data with same timestamp for this node. Same for quarantine. - // TODO: We might receive 'add' data packet before 'create'. If that happens, we should put it in quarantine and process it later. - databaseStorage.appendStorage(*nodeHash, requestDataHash, operation, creatorUser, creationDate, value->data.data(), value->data.size(), encryptedDataView); - } - else if(operation == DatabaseOperation::ADD_USER) - { - u8 nameLength = deserializerUnsigned.extract<u8>(); - - u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; - deserializerUnsigned.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); - DataView dataToDecrypt((void*)deserializerUnsigned.getBuffer(), ENCRYPTION_CHECKSUM_BYTE_SIZE + nameLength); - - sibs::SafeDeserializer deserializerSkippedEncryptedData(deserializerUnsigned.getBuffer() + ENCRYPTION_CHECKSUM_BYTE_SIZE + nameLength, PUBLIC_KEY_NUM_BYTES + GROUP_ID_LENGTH); - - char userToAddPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializerSkippedEncryptedData.extract((u8*)userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey userToAddPublicKey(userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - - uint8_t groupId[GROUP_ID_LENGTH]; - deserializerSkippedEncryptedData.extract(groupId, GROUP_ID_LENGTH); - - auto group = databaseStorage.getGroupById(*nodeHash, groupId); - if(group) - { - auto user = RemoteUser::create(userToAddPublicKey, "ENCRYPTED USER NAME", group); - // TODO: What if we receive packets in wrong order? (maliciously or non-maliciously). You would be able to register a user to a group with given name - // and further registration would be dropped (even if that is the correct one) - if(!databaseStorage.addUser(*nodeHash, user)) return; - - auto creatorUserGroupWithRights = getGroupWithRightsToAddUserToGroup(creatorUser->getGroups(), group); - if(!creatorUserGroupWithRights) - { - // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, - // or we haven't received the packet that modifies group with the permission to perform the operation. - // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, - // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. - // How to handle this? - string errMsg = "User "; - errMsg += creatorUser->getName(); - errMsg += " is not allowed to perform the operation: "; - errMsg += to_string((u8)operation); - throw PermissionDeniedException(errMsg); - } - - // TODO: Verify there isn't already data with same timestamp for this node. Same for quarantine. - // TODO: We might receive 'add' data packet before 'create'. If that happens, we should put it in quarantine and process it later. - databaseStorage.appendStorage(*nodeHash, requestDataHash, operation, creatorUser, creationDate, value->data.data(), value->data.size(), encryptedDataView); - } - else - { - throw sibs::DeserializeException("TODO: Add to quarantine? You can receive ADD_USER packet before you receive ADD_GROUP"); - } - } - else - { - string errMsg = "Got unexpected operation: "; - errMsg += to_string((u8)operation); - throw sibs::DeserializeException(errMsg); - } + DataView additionalDataView((void*)deserializerUnsigned.getBuffer(), deserializerUnsigned.getSize()); + databaseStorage.appendStorage(*nodeHash, requestDataHash, operation, creatorPublicKey, creationDate, value->data.data(), value->data.size(), additionalDataView); } bool Database::listenCreateData(shared_ptr<dht::Value> value, const Hash &hash, const shared_ptr<OwnedMemory> encryptionKey) @@ -657,7 +543,7 @@ namespace odhtdb Log::debug("Got create data"); try { - if(databaseStorage.getStorage(hash)) + if(databaseStorage.doesNodeExist(hash)) throw DatabaseStorageAlreadyExists("Create request hash is equal to hash already in storage (duplicate data?)"); deserializeCreateRequest(value, hash, encryptionKey); } @@ -673,7 +559,7 @@ namespace odhtdb Log::debug("Got add data"); try { - if(databaseStorage.getDataById(requestDataHash)) + if(databaseStorage.doesDataExist(requestDataHash)) throw DatabaseStorageAlreadyExists("Add data request hash is equal to hash already in storage (duplicate data?)"); deserializeAddRequest(value, requestDataHash, nodeHash, encryptionKey); //Log::debug("Got add object, timestamp: %zu", addObject.timestamp); @@ -688,24 +574,4 @@ namespace odhtdb } return true; } - - void Database::setOnCreateNodeCallback(function<void(const DatabaseCreateNodeRequest&)> callbackFunc) - { - onCreateNodeCallbackFunc = callbackFunc; - } - - void Database::setOnAddNodeCallback(function<void(const DatabaseAddNodeRequest&)> callbackFunc) - { - onAddNodeCallbackFunc = callbackFunc; - } - - void Database::setOnAddUserCallback(function<void(const DatabaseAddUserRequest&)> callbackFunc) - { - onAddUserCallbackFunc = callbackFunc; - } - - DatabaseStorage& Database::getStorage() - { - return databaseStorage; - } } diff --git a/src/DatabaseStorage.cpp b/src/DatabaseStorage.cpp index 0e7a1a8..501bd35 100644 --- a/src/DatabaseStorage.cpp +++ b/src/DatabaseStorage.cpp @@ -8,11 +8,13 @@ #include "../include/odhtdb/PasswordHash.hpp" #include "../include/odhtdb/Log.hpp" #include "../include/odhtdb/Database.hpp" +#include "../include/odhtdb/sql/SqlQuery.hpp" #include <cstring> #include <chrono> #include <boost/filesystem/convenience.hpp> #include <sibs/SafeSerializer.hpp> #include <sodium/randombytes.h> +#include <sqlite3.h> using namespace std; @@ -24,15 +26,8 @@ namespace odhtdb STORAGE_TYPE_APPEND }; - enum class DecryptedDataType : u8 - { - STORAGE_CREATE, - STORAGE_ADD_DATA, - STORAGE_ADD_USER - }; - const u64 QUARANTINE_STORAGE_TIME_MICROSECONDS = 60 * 1.0e6; - const u16 STORAGE_VERSION = 2; + const u16 STORAGE_VERSION = 3; DatabaseStorageObject::DatabaseStorageObject(const Hash &_requestHash, DataView &_data, u64 _timestamp, const Signature::PublicKey &_creatorPublicKey) : requestHash(_requestHash), @@ -50,30 +45,124 @@ namespace odhtdb storedTimestamp = chrono::duration_cast<chrono::microseconds>(time).count(); } + static void sqlite_exec_checked(sqlite3 *db, const char *sql) + { + char *dbErrMsg = nullptr; + int rc = sqlite3_exec(db, sql, nullptr, nullptr, &dbErrMsg); + if(rc != SQLITE_OK) + { + string errMsg = "Failed to run sqlite exec, error: "; + errMsg += dbErrMsg; + sqlite3_free(dbErrMsg); + throw DatabaseStorageException(errMsg); + } + } + + static void sqlite_prepare_checked(sqlite3 *db, const char *sql, sqlite3_stmt **stmt) + { + int rc = sqlite3_prepare_v2(db, sql, -1, stmt, nullptr); + if(rc != SQLITE_OK) + { + string errMsg = "Failed to run sqlite prepare statement, error: "; + errMsg += sqlite3_errmsg(db); + throw DatabaseStorageException(errMsg); + } + } + DatabaseStorage::DatabaseStorage(Database *_database, const boost::filesystem::path &storagePath) : database(_database), - groupsFilePath(storagePath / "groups"), - usersFilePath(storagePath / "users"), - dataFilePath(storagePath / "data"), - metadataFilePath(storagePath / "metadata"), - localUsersFilePath(storagePath / "local_users"), - nodeDecryptionKeysFilePath(storagePath / "node_keys"), - decryptedDataFilePath(storagePath / "decrypted_data") + sqliteDb(nullptr), + insertNodeStmt(nullptr), + insertUserStmt(nullptr), + insertGroupStmt(nullptr), + insertNodeAddDataStmt(nullptr), + setNodeDecryptionKeyStmt(nullptr), + getNodeDecryptionKeyStmt(nullptr), + selectNodeStmt(nullptr), + selectNodeAddDataByNodeStmt(nullptr), + selectNodeIdStatement(nullptr), + selectNodeAddDataIdStatement(nullptr), + insertNodeUserGroupAssocStmt(nullptr), + insertNodeRawStmt(nullptr), + insertNodeAddDataRawStmt(nullptr), + insertNodeAddDataAdditionalStmt(nullptr), + insertNodeAddUserDataStmt(nullptr), + selectNodeAddDataAdditionalStmt(nullptr), + selectNodeAddUserDataStmt(nullptr), + setNodeAddDataDecryptedStmt(nullptr), + setNodeAddDataAdditionalDataStmt(nullptr), + metadataFilePath(storagePath / "metadata") + { + try + { + init(storagePath); + } + catch(DatabaseStorageException &e) + { + cleanup(); + throw e; + } + } + + void DatabaseStorage::init(const boost::filesystem::path &storagePath) { boost::filesystem::create_directories(storagePath); + Log::debug("sqlite version: %s", sqlite3_libversion()); + + int rc = sqlite3_open((storagePath / "database.sqlite3").string().c_str(), &sqliteDb); + if(rc != SQLITE_OK) + { + string errMsg = "Failed to open database: "; + errMsg += sqlite3_errmsg(sqliteDb); + throw DatabaseStorageException(errMsg); + } + + sqlite_exec_checked(sqliteDb, + "CREATE TABLE IF NOT EXISTS Node(id INTEGER PRIMARY KEY, nodeHash BLOB UNIQUE NOT NULL, timestamp INTEGER NOT NULL, creatorPublicKey BLOB NOT NULL, adminGroupId BLOB NOT NULL);" + "CREATE TABLE IF NOT EXISTS NodeUser(node BLOB NOT NULL, publicKey BLOB NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash));" + "CREATE TABLE IF NOT EXISTS NodeGroup(node BLOB NOT NULL, groupId BLOB UNIQUE NOT NULL, permissionLevel INT NOT NULL, permissionFlags INTEGER NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash));" + "CREATE TABLE IF NOT EXISTS NodeAddData(id INTEGER PRIMARY KEY, node BLOB NOT NULL, requestHash BLOB UNIQUE NOT NULL, operation INT NOT NULL, timestamp INTEGER NOT NULL, creatorPublicKey BLOB NOT NULL, decrypted INT NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash));" + "CREATE TABLE IF NOT EXISTS NodeAddDataAdditional(id INTEGER PRIMARY KEY, nodeAddDataId INTEGER NOT NULL, data BLOB NOT NULL, FOREIGN KEY(nodeAddDataId) REFERENCES NodeAddData(id));" + "CREATE TABLE IF NOT EXISTS NodeAddUserData(id INTEGER PRIMARY KEY, nodeAddDataId INTEGER NOT NULL, userToAddPublicKey BLOB NOT NULL, groupId BLOB NOT NULL, FOREIGN KEY(nodeAddDataId) REFERENCES NodeAddData(id));" + "CREATE TABLE IF NOT EXISTS NodeDecryptionKey(node BLOB UNIQUE NOT NULL, decryptionKey BLOB NOT NULL);" + "CREATE TABLE IF NOT EXISTS NodeUserGroupAssoc(node BLOB NOT NULL, userPublicKey BLOB NOT NULL, groupId BLOB NOT NULL, FOREIGN KEY(node) REFERENCES Node(nodeHash), FOREIGN KEY(userPublicKey) REFERENCES NodeUser(publicKey), FOREIGN KEY(groupId) REFERENCES NodeGroup(groupId));" + + "CREATE TABLE IF NOT EXISTS NodeRaw(node INTEGER NOT NULL, data BLOB NOT NULL, FOREIGN KEY(node) REFERENCES Node(id));" + "CREATE TABLE IF NOT EXISTS NodeAddDataRaw(node INTEGER NOT NULL, nodeAddData INTEGER NOT NULL, data BLOB NOT NULL, FOREIGN KEY(node) REFERENCES Node(id), FOREIGN KEY(nodeAddData) REFERENCES NodeAddData(id));" + + "CREATE UNIQUE INDEX IF NOT EXISTS UniqueUserInNode ON NodeUser(node, publicKey);" + "CREATE UNIQUE INDEX IF NOT EXISTS UniqueUserGroupAssoc ON NodeUserGroupAssoc(node, userPublicKey, groupId);"); + + sqlite_prepare_checked(sqliteDb, "INSERT INTO Node(nodeHash, timestamp, creatorPublicKey, adminGroupId) VALUES(?, ?, ?, ?)", &insertNodeStmt); + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeUser(node, publicKey) VALUES(?, ?)", &insertUserStmt); + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeGroup(node, groupId, permissionLevel, permissionFlags) VALUES(?, ?, ?, ?)", &insertGroupStmt); + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddData(node, requestHash, operation, timestamp, creatorPublicKey, decrypted) VALUES(?, ?, ?, ?, ?, ?)", &insertNodeAddDataStmt); + sqlite_prepare_checked(sqliteDb, "INSERT OR REPLACE INTO NodeDecryptionKey(node, decryptionKey) VALUES(?, ?)", &setNodeDecryptionKeyStmt); + sqlite_prepare_checked(sqliteDb, "SELECT decryptionKey FROM NodeDecryptionKey WHERE node = ?", &getNodeDecryptionKeyStmt); + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeUserGroupAssoc(node, userPublicKey, groupId) VALUES(?, ?, ?)", &insertNodeUserGroupAssocStmt); + + sqlite_prepare_checked(sqliteDb, "SELECT timestamp, creatorPublicKey, adminGroupId FROM Node WHERE nodeHash = ?", &selectNodeStmt); + sqlite_prepare_checked(sqliteDb, "SELECT id, requestHash, operation, timestamp, creatorPublicKey From NodeAddData WHERE node = ?", &selectNodeAddDataByNodeStmt); + sqlite_prepare_checked(sqliteDb, "SELECT id FROM Node WHERE nodeHash = ?", &selectNodeIdStatement); + sqlite_prepare_checked(sqliteDb, "SELECT id FROM NodeAddData WHERE requestHash = ?", &selectNodeAddDataIdStatement); + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeRaw(node, data) VALUES(?, ?)", &insertNodeRawStmt); + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddDataRaw(node, nodeAddData, data) VALUES(?, ?, ?)", &insertNodeAddDataRawStmt); + + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddDataAdditional(nodeAddDataId, data) VALUES(?, ?)", &insertNodeAddDataAdditionalStmt); + sqlite_prepare_checked(sqliteDb, "INSERT INTO NodeAddUserData(nodeAddDataId, userToAddPublicKey, groupId) VALUES(?, ?, ?)", &insertNodeAddUserDataStmt); + + sqlite_prepare_checked(sqliteDb, "SELECT data From NodeAddDataAdditional WHERE nodeAddDataId = ?", &selectNodeAddDataAdditionalStmt); + sqlite_prepare_checked(sqliteDb, "SELECT userToAddPublicKey, groupId From NodeAddUserData WHERE nodeAddDataId = ?", &selectNodeAddUserDataStmt); + + sqlite_prepare_checked(sqliteDb, "UPDATE NodeAddData SET decrypted = ? WHERE id = ?", &setNodeAddDataDecryptedStmt); + sqlite_prepare_checked(sqliteDb, "UPDATE NodeAddDataAdditional SET data = ? WHERE nodeAddDataId = ?", &setNodeAddDataAdditionalDataStmt); + bool metadataLoaded = false; try { loadMetadataFromFile(); metadataLoaded = true; - loadGroupsFromFile(); - loadUsersFromFile(); - loadDataFromFile(); - loadLocalUsersFromFile(); - loadNodeDecryptionKeysFromFile(); - loadDecryptedDataFromFile(); - //loadQuarantineFromFile(); } catch(FileException &e) { @@ -113,651 +202,474 @@ namespace odhtdb } } - void DatabaseStorage::loadGroupsFromFile() + void DatabaseStorage::cleanup() { - if(!boost::filesystem::exists(groupsFilePath)) return; - - OwnedMemory groupsFileContent = fileGetContent(groupsFilePath); - sibs::SafeDeserializer deserializer((u8*)groupsFileContent.data, groupsFileContent.size); - - while(!deserializer.empty()) - { - Hash nodeHash; - deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); - - DataViewMap<Group*> *groupByIdMap = nullptr; - auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); - if(groupByIdMapIt == nodeGroupByIdMap.end()) - { - groupByIdMap = new DataViewMap<Group*>(); - nodeGroupByIdMap[nodeHash] = groupByIdMap; - } - else - groupByIdMap = groupByIdMapIt->second; - - u8 groupNameSize = deserializer.extract<u8>(); - string groupName; - groupName.resize(groupNameSize); - deserializer.extract((u8*)&groupName[0], groupNameSize); - - u8 groupId[GROUP_ID_LENGTH]; - deserializer.extract(groupId, GROUP_ID_LENGTH); - - u8 permissionLevel = deserializer.extract<u8>(); - u32 permissionFlags = deserializer.extract<u32>(); - - Group *group = new Group(groupName, groupId, { permissionLevel, permissionFlags }); - (*groupByIdMap)[group->getId()] = group; - - string groupIdStr = bin2hex((const char*)groupId, GROUP_ID_LENGTH); - Log::debug("DatabaseStorage: Loaded group %s from file", groupIdStr.c_str()); - } + sqlite3_finalize(insertNodeStmt); + sqlite3_finalize(insertUserStmt); + sqlite3_finalize(insertGroupStmt); + sqlite3_finalize(insertNodeAddDataStmt); + sqlite3_finalize(setNodeDecryptionKeyStmt); + sqlite3_finalize(getNodeDecryptionKeyStmt); + sqlite3_finalize(insertNodeUserGroupAssocStmt); + sqlite3_finalize(selectNodeStmt); + sqlite3_finalize(selectNodeAddDataByNodeStmt); + sqlite3_finalize(selectNodeIdStatement); + sqlite3_finalize(selectNodeAddDataIdStatement); + sqlite3_finalize(insertNodeRawStmt); + sqlite3_finalize(insertNodeAddDataRawStmt); + sqlite3_finalize(insertNodeAddDataAdditionalStmt); + sqlite3_finalize(insertNodeAddUserDataStmt); + sqlite3_finalize(selectNodeAddDataAdditionalStmt); + sqlite3_finalize(selectNodeAddUserDataStmt); + sqlite3_finalize(setNodeAddDataDecryptedStmt); + sqlite3_finalize(setNodeAddDataAdditionalDataStmt); + sqlite3_close(sqliteDb); } - void DatabaseStorage::loadUsersFromFile() + DatabaseStorage::~DatabaseStorage() { - if(!boost::filesystem::exists(usersFilePath)) return; - - OwnedMemory usersFileContent = fileGetContent(usersFilePath); - sibs::SafeDeserializer deserializer((u8*)usersFileContent.data, usersFileContent.size); - - while(!deserializer.empty()) + cleanup(); + } + + void DatabaseStorage::loadNode(const Hash &nodeHash) + { + if(database->onCreateNodeCallbackFunc) { - Hash nodeHash; - deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); - - Signature::MapPublicKey<User*> *publicKeyUserDataMap = nullptr; - auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); - if(publicKeyUserDataMapIt == nodePublicKeyUserDataMap.end()) + SqlQuery nodeQuery(sqliteDb, "SELECT timestamp, creatorPublicKey, adminGroupId From Node WHERE nodeHash = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); + while(nodeQuery.next()) { - publicKeyUserDataMap = new Signature::MapPublicKey<User*>(); - nodePublicKeyUserDataMap[nodeHash] = publicKeyUserDataMap; + u64 timestamp = nodeQuery.getInt64(0); + const DataView creatorPublicKeyRaw = nodeQuery.getBlob(1); + Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw.data, creatorPublicKeyRaw.size); + u8 adminGroupId[GROUP_ID_LENGTH]; + const DataView adminGroupRaw = nodeQuery.getBlob(2); + memcpy(adminGroupId, adminGroupRaw.data, GROUP_ID_LENGTH); + + const DatabaseCreateNodeRequest createNodeRequest(&nodeHash, timestamp, &creatorPublicKey, DataView(adminGroupId, GROUP_ID_LENGTH)); + database->onCreateNodeCallbackFunc(createNodeRequest); } - else - publicKeyUserDataMap = publicKeyUserDataMapIt->second; + } + + SqlQuery nodeQuery(sqliteDb, "SELECT id, requestHash, operation, timestamp, creatorPublicKey From NodeAddData WHERE node = ? AND decrypted = 1", { DataView(nodeHash.getData(), nodeHash.getSize()) }); + while(nodeQuery.next()) + { + i64 rowId = nodeQuery.getInt64(0); - DataViewMap<Group*> *groupByIdMap = nullptr; - auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); - if(groupByIdMapIt == nodeGroupByIdMap.end()) - { - groupByIdMap = new DataViewMap<Group*>(); - nodeGroupByIdMap[nodeHash] = groupByIdMap; - } - else - groupByIdMap = groupByIdMapIt->second; + const DataView requestHashRaw = nodeQuery.getBlob(1); + Hash requestHash; + memcpy(requestHash.getData(), requestHashRaw.data, HASH_BYTE_SIZE); - u8 usernameSize = deserializer.extract<u8>(); - string username; - username.resize(usernameSize); - deserializer.extract((u8*)&username[0], usernameSize); + DatabaseOperation operation = (DatabaseOperation)nodeQuery.getInt(2); - u8 userPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializer.extract(userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey userPublicKey((const char*)userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + u64 timestamp = nodeQuery.getInt64(3); - User *user = RemoteUser::create(userPublicKey, username, nullptr); + const DataView creatorPublicKeyRaw = nodeQuery.getBlob(4); + Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw.data, creatorPublicKeyRaw.size); - u8 numGroups = deserializer.extract<u8>(); - for(int i = 0; i < numGroups; ++i) + if(operation == DatabaseOperation::ADD_DATA) { - u8 groupId[GROUP_ID_LENGTH]; - deserializer.extract(groupId, GROUP_ID_LENGTH); + if(!database->onAddNodeCallbackFunc) continue; + sqlite3_reset(selectNodeAddDataAdditionalStmt); + sqlite3_clear_bindings(selectNodeAddDataAdditionalStmt); + + int rc; + rc = sqlite3_bind_int64(selectNodeAddDataAdditionalStmt, 1, rowId); + bindCheckError(rc); - auto groupIt = groupByIdMap->find(DataView(groupId, GROUP_ID_LENGTH)); - if(groupIt == groupByIdMap->end()) + rc = sqlite3_step(selectNodeAddDataAdditionalStmt); + if(rc != SQLITE_ROW) { - string errMsg = "User group with id "; - errMsg += bin2hex((const char*)groupId, GROUP_ID_LENGTH); - errMsg += " does not exist"; - throw DatabaseStorageCorrupt(errMsg); + string errMsg = "select NodeAddDataAdditional failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + throw DatabaseStorageException(errMsg); } - user->addToGroup(groupIt->second); + + // TODO: There is no need to allocate/deallocate several times, this can be moved outside the while loop + const void *decryptedDataRaw = sqlite3_column_blob(selectNodeAddDataAdditionalStmt, 0); + int decryptedDataSize = sqlite3_column_bytes(selectNodeAddDataAdditionalStmt, 0); + OwnedMemory decryptedData(new u8[decryptedDataSize], decryptedDataSize); + memcpy(decryptedData.data, decryptedDataRaw, decryptedDataSize); + + const DatabaseAddNodeRequest addNodeRequest(&nodeHash, &requestHash, timestamp, &creatorPublicKey, DataView(decryptedData.data, decryptedData.size)); + database->onAddNodeCallbackFunc(addNodeRequest); + } + else if(operation == DatabaseOperation::ADD_USER) + { + if(!database->onAddUserCallbackFunc) continue; + sqlite3_reset(selectNodeAddUserDataStmt); + sqlite3_clear_bindings(selectNodeAddUserDataStmt); + + int rc; + rc = sqlite3_bind_int64(selectNodeAddUserDataStmt, 1, rowId); + bindCheckError(rc); + + rc = sqlite3_step(selectNodeAddUserDataStmt); + if(rc != SQLITE_ROW) + { + string errMsg = "select NodeAddUserData failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + throw DatabaseStorageException(errMsg); + } + + const void *userToAddPublicKeyRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 0); + Signature::PublicKey userToAddPublicKey((const char*)userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + + const void *groupToAddUserToRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 1); + u8 groupToAddUserTo[GROUP_ID_LENGTH]; + memcpy(groupToAddUserTo, groupToAddUserToRaw, GROUP_ID_LENGTH); + + DatabaseAddUserRequest addUserRequest(&nodeHash, &requestHash, timestamp, &creatorPublicKey, &userToAddPublicKey, DataView(groupToAddUserTo, GROUP_ID_LENGTH)); + database->onAddUserCallbackFunc(addUserRequest); } - - (*publicKeyUserDataMap)[user->getPublicKey()] = user; } } - void DatabaseStorage::loadStorageCreate(sibs::SafeDeserializer &deserializer) + void DatabaseStorage::loadMetadataFromFile() { - u64 timestamp = deserializer.extract<u64>(); - - u8 userPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializer.extract(userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey userPublicKey((const char*)userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - - u8 groupId[GROUP_ID_LENGTH]; - deserializer.extract(groupId, GROUP_ID_LENGTH); + OwnedMemory metadataFileContent = fileGetContent(metadataFilePath); + sibs::SafeDeserializer deserializer((u8*)metadataFileContent.data, metadataFileContent.size); - u32 dataSize = deserializer.extract<u32>(); - u8 *data = new u8[dataSize]; - deserializer.extract(data, dataSize); - u32 offsetToEncryptedData = deserializer.extract<u32>(); + u16 storageVersion = deserializer.extract<u16>(); + if(storageVersion != STORAGE_VERSION) + throw std::runtime_error("Wrong storage version!"); - Hash nodeHash; - deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + deserializer.extract(passwordSalt, PASSWORD_SALT_LEN); - auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); - if(groupByIdMapIt == nodeGroupByIdMap.end()) - { - string errMsg = "No groups exists in node "; - errMsg += nodeHash.toString(); - throw DatabaseStorageCorrupt(errMsg); - } + u16 privateKeySize = deserializer.extract<u16>(); + dht::Blob privateKeyRaw; + privateKeyRaw.resize(privateKeySize); + deserializer.extract(&privateKeyRaw[0], privateKeySize); + identity.first = make_shared<dht::crypto::PrivateKey>(privateKeyRaw); - auto groupIt = groupByIdMapIt->second->find(DataView(groupId, GROUP_ID_LENGTH)); - if(groupIt == groupByIdMapIt->second->end()) - { - string errMsg = "User group with id "; - errMsg += bin2hex((const char*)groupId, GROUP_ID_LENGTH); - errMsg += " does not exist in node "; - errMsg += nodeHash.toString(); - throw DatabaseStorageCorrupt(errMsg); - } + u16 certificateSize = deserializer.extract<u16>(); + dht::Blob certificateRaw; + certificateRaw.resize(certificateSize); + deserializer.extract(&certificateRaw[0], certificateSize); + identity.second = make_shared<dht::crypto::Certificate>(certificateRaw); - DatabaseStorageObjectList *databaseStorageObjectList = new DatabaseStorageObjectList(userPublicKey); - databaseStorageObjectList->createdTimestamp = timestamp; - databaseStorageObjectList->groups.push_back(groupIt->second); - databaseStorageObjectList->data = DataView(data, dataSize); - databaseStorageObjectList->offsetToEncryptedData = offsetToEncryptedData; - storageMap[nodeHash] = databaseStorageObjectList; + assert(deserializer.empty()); } - void DatabaseStorage::loadStorageAppend(sibs::SafeDeserializer &deserializer) + static void sqlite_step_rollback_on_failure(sqlite3 *db, sqlite3_stmt *stmt, const char *description) { - u64 timestamp = deserializer.extract<u64>(); - - u8 creatorPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializer.extract(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - - u32 dataSize = deserializer.extract<u32>(); - u8 *data = new u8[dataSize]; - deserializer.extract(data, dataSize); - - Hash nodeHash; - deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); - - Hash dataHash; - deserializer.extract((u8*)dataHash.getData(), HASH_BYTE_SIZE); - - auto storageIt = storageMap.find(nodeHash); - if(storageIt == storageMap.end()) + int rc = sqlite3_step(stmt); + if(rc != SQLITE_DONE) { - string errMsg = "Database storage with hash "; - errMsg += nodeHash.toString(); - errMsg += " not found"; - throw DatabaseStorageCorrupt(errMsg); + string errMsg = description; + errMsg += " failed with error: "; + errMsg += sqlite3_errmsg(db); + sqlite3_exec(db, "ROLLBACK", 0, 0, 0); + if(rc == SQLITE_CONSTRAINT) + throw DatabaseStorageAlreadyExists(errMsg); + else + throw DatabaseStorageException(errMsg); } - - DataView storageData { data, dataSize }; - DatabaseStorageObject *databaseStorageObject = new DatabaseStorageObject(dataHash, storageData, timestamp, creatorPublicKey); - storageIt->second->objects.push_back(databaseStorageObject); - storedDataHash[dataHash] = databaseStorageObject; } - void DatabaseStorage::loadDataFromFile() + void DatabaseStorage::bindCheckError(int sqliteBindResult) { - if(!boost::filesystem::exists(dataFilePath)) return; - - OwnedMemory dataFileContent = fileGetContent(dataFilePath); - sibs::SafeDeserializer deserializer((u8*)dataFileContent.data, dataFileContent.size); - - while(!deserializer.empty()) + if(sqliteBindResult != SQLITE_OK) { - StorageType storageType = deserializer.extract<StorageType>(); - switch(storageType) - { - case STORAGE_TYPE_CREATE: - loadStorageCreate(deserializer); - break; - case STORAGE_TYPE_APPEND: - loadStorageAppend(deserializer); - break; - } + string errMsg = "Failed to bind param, error code: "; + errMsg += to_string(sqliteBindResult); + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw DatabaseStorageException(errMsg); } } - void DatabaseStorage::loadLocalUsersFromFile() + i64 DatabaseStorage::getNodeRowId(const Hash &nodeHash) { - if(!boost::filesystem::exists(localUsersFilePath)) return; - - OwnedMemory localUsersFileContent = fileGetContent(localUsersFilePath); - sibs::SafeDeserializer deserializer((u8*)localUsersFileContent.data, localUsersFileContent.size); + sqlite3_reset(selectNodeIdStatement); + sqlite3_clear_bindings(selectNodeIdStatement); - while(!deserializer.empty()) + int rc; + rc = sqlite3_bind_blob(selectNodeIdStatement, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_step(selectNodeIdStatement); + if(rc != SQLITE_ROW) { - u8 usernameSize = deserializer.extract<u8>(); - string username; - username.resize(usernameSize); - deserializer.extract((u8*)&username[0], usernameSize); - - u8 userPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializer.extract(userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey userPublicKey((const char*)userPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - - EncryptedPrivateKey encryptedPrivateKey; - deserializer.extract(encryptedPrivateKey.nonce, ENCRYPTION_NONCE_BYTE_SIZE); - deserializer.extract(encryptedPrivateKey.encryptedPrivateKey, ENCRYPTION_CHECKSUM_BYTE_SIZE + PRIVATE_KEY_NUM_BYTES); - - nameLocalUsersMap[username] = LocalUserEncrypted::create(userPublicKey, encryptedPrivateKey, username); + string errMsg = "select Node id failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw DatabaseStorageException(errMsg); } + + return sqlite3_column_int64(selectNodeIdStatement, 0); } - void DatabaseStorage::loadNodeDecryptionKeysFromFile() + i64 DatabaseStorage::getNodeAddDataRowId(const Hash &requestHash) { - if(!boost::filesystem::exists(nodeDecryptionKeysFilePath)) return; + sqlite3_reset(selectNodeAddDataIdStatement); + sqlite3_clear_bindings(selectNodeAddDataIdStatement); - OwnedMemory nodeKeysFileContent = fileGetContent(nodeDecryptionKeysFilePath); - sibs::SafeDeserializer deserializer((u8*)nodeKeysFileContent.data, nodeKeysFileContent.size); - - while(!deserializer.empty()) + int rc; + rc = sqlite3_bind_blob(selectNodeAddDataIdStatement, 1, requestHash.getData(), requestHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_step(selectNodeAddDataIdStatement); + if(rc != SQLITE_ROW) { - Hash nodeHash; - deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); - - u8 *nodeKeyRaw = new u8[ENCRYPTION_KEY_BYTE_SIZE]; - deserializer.extract(nodeKeyRaw, ENCRYPTION_KEY_BYTE_SIZE); - nodeDecryptionKeyMap[nodeHash] = make_shared<OwnedMemory>(nodeKeyRaw, ENCRYPTION_KEY_BYTE_SIZE); + string errMsg = "select NodeAddData id failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw DatabaseStorageException(errMsg); } + + return sqlite3_column_int64(selectNodeAddDataIdStatement, 0); } - void DatabaseStorage::loadDecryptedStorageCreate(sibs::SafeDeserializer &deserializer) + bool DatabaseStorage::doesNodeExist(const Hash &nodeHash) const { - Hash nodeHash; - deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); - - auto storageIt = storageMap.find(nodeHash); - if(storageIt == storageMap.end()) - { - string errMsg = "Database storage with hash "; - errMsg += nodeHash.toString(); - errMsg += " not found"; - throw DatabaseStorageCorrupt(errMsg); - } - - u8 creatorNameLength = deserializer.extract<u8>(); - auto creator = getUserByPublicKey(nodeHash, storageIt->second->creatorPublicKey); - if(!creator) - { - string errMsg = "User with public key "; - errMsg += storageIt->second->creatorPublicKey.toString(); - errMsg += " does not exist in node with hash "; - errMsg += nodeHash.toString(); - throw DatabaseStorageCorrupt(errMsg); - } - - creator->name.resize(creatorNameLength); - deserializer.extract((u8*)&creator->name[0], creatorNameLength); - - u8 nodeNameLength = deserializer.extract<u8>(); - storageIt->second->nodeName.resize(nodeNameLength); - deserializer.extract((u8*)&storageIt->second->nodeName[0], nodeNameLength); - storageIt->second->isDecrypted = true; + SqlQuery query(sqliteDb, "SELECT id FROM Node WHERE nodeHash = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); + return query.next(); } - void DatabaseStorage::loadDecryptedStorageAddData(sibs::SafeDeserializer &deserializer) + bool DatabaseStorage::doesDataExist(const Hash &requestHash) const { - Hash requestHash; - deserializer.extract((u8*)requestHash.getData(), HASH_BYTE_SIZE); - - auto storedDataIt = storedDataHash.find(requestHash); - if(storedDataIt == storedDataHash.end()) - { - string errMsg = "Database doesn't contain data with hash "; - errMsg += requestHash.toString(); - throw DatabaseStorageCorrupt(errMsg); - } - - u32 decryptedDataSize = deserializer.extract<u32>(); - u8 *decryptedDataRaw = new u8[decryptedDataSize]; - deserializer.extract(decryptedDataRaw, decryptedDataSize); - - storedDataIt->second->decryptedObject.data.data = decryptedDataRaw; - storedDataIt->second->decryptedObject.data.size = decryptedDataSize; - storedDataIt->second->decryptedObject.operation = DatabaseOperation::ADD_DATA; + SqlQuery query(sqliteDb, "SELECT id FROM NodeAddData WHERE requestHash = ?", { DataView(requestHash.getData(), requestHash.getSize()) }); + return query.next(); } - void DatabaseStorage::loadDecryptedStorageAddUser(sibs::SafeDeserializer &deserializer) + void DatabaseStorage::createStorage(const Hash &hash, Group *creatorGroup, u64 timestamp, const void *data, usize size) { - Hash nodeHash; - deserializer.extract((u8*)nodeHash.getData(), HASH_BYTE_SIZE); - - Hash requestHash; - deserializer.extract((u8*)requestHash.getData(), HASH_BYTE_SIZE); + assert(creatorGroup->getUsers().size() == 1); + User *creator = (User*)creatorGroup->getUsers()[0]; - auto storedDataIt = storedDataHash.find(requestHash); - if(storedDataIt == storedDataHash.end()) + sqlite3_exec(sqliteDb, "BEGIN", 0, 0, 0); { - string errMsg = "Database doesn't contain data with hash "; - errMsg += requestHash.toString(); - throw DatabaseStorageCorrupt(errMsg); + sqlite3_reset(insertNodeStmt); + sqlite3_clear_bindings(insertNodeStmt); + + int rc; + rc = sqlite3_bind_blob(insertNodeStmt, 1, hash.getData(), hash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_bind_int64(insertNodeStmt, 2, timestamp); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeStmt, 3, creator->getPublicKey().getData(), creator->getPublicKey().getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeStmt, 4, creatorGroup->getId().data, GROUP_ID_LENGTH, SQLITE_STATIC); + bindCheckError(rc); + + sqlite_step_rollback_on_failure(sqliteDb, insertNodeStmt, "insert data into Node"); + addGroup(hash, creatorGroup); + addUser(hash, creator->getPublicKey(), creatorGroup->getId()); } - - u8 addedUserPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializer.extract(addedUserPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey addedUserPublicKey((const char*)addedUserPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - auto addedUser = getUserByPublicKey(nodeHash, addedUserPublicKey); - if(!addedUser) { - string errMsg = "User with public key "; - errMsg += bin2hex((const char*)addedUserPublicKeyRaw, PUBLIC_KEY_NUM_BYTES).c_str(); - errMsg += " does not exist in node with hash "; - errMsg += nodeHash.toString(); - throw DatabaseStorageCorrupt(errMsg); + sqlite3_reset(insertNodeRawStmt); + sqlite3_clear_bindings(insertNodeRawStmt); + + int rc; + rc = sqlite3_bind_int64(insertNodeRawStmt, 1, getNodeRowId(hash)); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeRawStmt, 2, data, size, SQLITE_STATIC); + bindCheckError(rc); + + sqlite_step_rollback_on_failure(sqliteDb, insertNodeRawStmt, "insert data into NodeRaw"); } + sqlite3_exec(sqliteDb, "COMMIT", 0, 0, 0); - u32 decryptedUserNameSize = deserializer.extract<u32>(); - addedUser->name.resize(decryptedUserNameSize); - deserializer.extract((u8*)&addedUser->name[0], decryptedUserNameSize); + auto nodeDecryptionKeyResult = getNodeDecryptionKey(hash); + if(nodeDecryptionKeyResult.first) + decryptNodeData(hash, nodeDecryptionKeyResult.second, &creator->getPublicKey(), creatorGroup->getId(), timestamp); } - void DatabaseStorage::loadDecryptedDataFromFile() + void DatabaseStorage::appendStorage(const Hash &nodeHash, const Hash &dataHash, DatabaseOperation operation, const Signature::PublicKey &creatorPublicKey, u64 timestamp, const void *data, usize size, const DataView &additionalDataView) { - if(!boost::filesystem::exists(decryptedDataFilePath)) return; - - OwnedMemory decryptedDataFileContent = fileGetContent(decryptedDataFilePath); - sibs::SafeDeserializer deserializer((u8*)decryptedDataFileContent.data, decryptedDataFileContent.size); - - while(!deserializer.empty()) + sqlite3_exec(sqliteDb, "BEGIN", 0, 0, 0); { - DecryptedDataType decryptedDataType = deserializer.extract<DecryptedDataType>(); - switch(decryptedDataType) - { - case DecryptedDataType::STORAGE_CREATE: - loadDecryptedStorageCreate(deserializer); - break; - case DecryptedDataType::STORAGE_ADD_DATA: - loadDecryptedStorageAddData(deserializer); - break; - case DecryptedDataType::STORAGE_ADD_USER: - loadDecryptedStorageAddUser(deserializer); - break; - } + sqlite3_reset(insertNodeAddDataStmt); + sqlite3_clear_bindings(insertNodeAddDataStmt); + + int rc; + rc = sqlite3_bind_blob(insertNodeAddDataStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeAddDataStmt, 2, dataHash.getData(), dataHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_bind_int(insertNodeAddDataStmt, 3, (u8)operation); + bindCheckError(rc); + + rc = sqlite3_bind_int64(insertNodeAddDataStmt, 4, timestamp); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeAddDataStmt, 5, creatorPublicKey.getData(), creatorPublicKey.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_bind_int(insertNodeAddDataStmt, 6, 0); + bindCheckError(rc); + + sqlite_step_rollback_on_failure(sqliteDb, insertNodeAddDataStmt, "insert data into NodeAddData"); } - } - - void DatabaseStorage::loadMetadataFromFile() - { - OwnedMemory metadataFileContent = fileGetContent(metadataFilePath); - sibs::SafeDeserializer deserializer((u8*)metadataFileContent.data, metadataFileContent.size); - u16 storageVersion = deserializer.extract<u16>(); - if(storageVersion != STORAGE_VERSION) - throw std::runtime_error("Wrong storage version!"); + i64 nodeRowId = getNodeRowId(nodeHash); + i64 nodeAddRowId = getNodeAddDataRowId(dataHash); - deserializer.extract(passwordSalt, PASSWORD_SALT_LEN); - //string passwordSaltStr((const char*)passwordSalt, PASSWORD_SALT_LEN); + Signature::PublicKey userToAddPublicKey; + u8 groupToAddUserTo[GROUP_ID_LENGTH]; - u16 privateKeySize = deserializer.extract<u16>(); - dht::Blob privateKeyRaw; - privateKeyRaw.resize(privateKeySize); - deserializer.extract(&privateKeyRaw[0], privateKeySize); - identity.first = make_shared<dht::crypto::PrivateKey>(privateKeyRaw); - - u16 certificateSize = deserializer.extract<u16>(); - dht::Blob certificateRaw; - certificateRaw.resize(certificateSize); - deserializer.extract(&certificateRaw[0], certificateSize); - identity.second = make_shared<dht::crypto::Certificate>(certificateRaw); - - assert(deserializer.empty()); - } - - void DatabaseStorage::createStorage(const Hash &hash, Group *creatorGroup, u64 timestamp, const u8 *data, usize dataSize, u32 offsetToEncryptedData) - { - if(storageMap.find(hash) != storageMap.end()) + if(operation == DatabaseOperation::ADD_DATA) { - string errMsg = "Database storage with hash "; - errMsg += hash.toString(); - errMsg += " already exists"; - throw DatabaseStorageAlreadyExists(errMsg); + sqlite3_reset(insertNodeAddDataAdditionalStmt); + sqlite3_clear_bindings(insertNodeAddDataAdditionalStmt); + + int rc; + rc = sqlite3_bind_int64(insertNodeAddDataAdditionalStmt, 1, nodeAddRowId); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeAddDataAdditionalStmt, 2, additionalDataView.data, additionalDataView.size, SQLITE_STATIC); + bindCheckError(rc); + + sqlite_step_rollback_on_failure(sqliteDb, insertNodeAddDataAdditionalStmt, "insert data into NodeAddDataAdditional"); } - - addGroup(hash, creatorGroup); - assert(creatorGroup->getUsers().size() == 1); - User *creator = (User*)creatorGroup->getUsers()[0]; - addUser(hash, creator); - - DatabaseStorageObjectList *databaseStorageObjectList = new DatabaseStorageObjectList(creator->getPublicKey()); - databaseStorageObjectList->createdTimestamp = timestamp; - databaseStorageObjectList->groups.push_back(creatorGroup); - databaseStorageObjectList->data = DataView(new u8[dataSize], dataSize); - memcpy(databaseStorageObjectList->data.data, data, dataSize); - databaseStorageObjectList->offsetToEncryptedData = offsetToEncryptedData; - storageMap[hash] = databaseStorageObjectList; - - sibs::SafeSerializer serializer; - serializer.add(STORAGE_TYPE_CREATE); - serializer.add(timestamp); - serializer.add((u8*)creator->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); - serializer.add((u8*)creatorGroup->getId().data, GROUP_ID_LENGTH); - - serializer.add((u32)dataSize); - serializer.add(data, dataSize); - serializer.add(offsetToEncryptedData); - - serializer.add((u8*)hash.getData(), HASH_BYTE_SIZE); - - fileAppend(dataFilePath, { serializer.getBuffer().data(), serializer.getBuffer().size() }); - - auto nodeDecryptionKeyIt = nodeDecryptionKeyMap.find(hash); - if(nodeDecryptionKeyIt != nodeDecryptionKeyMap.end()) - decryptNodeData(hash, databaseStorageObjectList, nodeDecryptionKeyIt->second); - } - - // TODO: Use encryptedDataView to remove duplicate of unsigning request, jumping straight to decrypting encrypted data and calling callback func - void DatabaseStorage::appendStorage(const Hash &nodeHash, const Hash &dataHash, DatabaseOperation operation, const User *creatorUser, u64 timestamp, const u8 *data, usize dataSize, const DataView &encryptedDataView) - { - auto it = storageMap.find(nodeHash); - if(it == storageMap.end()) + else if(operation == DatabaseOperation::ADD_USER) { - string errMsg = "Database storage with hash "; - errMsg += nodeHash.toString(); - errMsg += " not found. Storage for a hash needs to be created before data can be appended to it"; - throw DatabaseStorageNotFound(errMsg); + try + { + sibs::SafeDeserializer deserializer((const u8*)additionalDataView.data, additionalDataView.size); + deserializer.extract((u8*)userToAddPublicKey.getData(), PUBLIC_KEY_NUM_BYTES); + deserializer.extract(groupToAddUserTo, GROUP_ID_LENGTH); + + sqlite3_reset(insertNodeAddUserDataStmt); + sqlite3_clear_bindings(insertNodeAddUserDataStmt); + + int rc; + rc = sqlite3_bind_int64(insertNodeAddUserDataStmt, 1, nodeAddRowId); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeAddUserDataStmt, 2, userToAddPublicKey.getData(), PUBLIC_KEY_NUM_BYTES, SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeAddUserDataStmt, 3, groupToAddUserTo, GROUP_ID_LENGTH, SQLITE_STATIC); + bindCheckError(rc); + + sqlite_step_rollback_on_failure(sqliteDb, insertNodeAddUserDataStmt, "insert data into NodeAddUserData"); + } + catch(sibs::DeserializeException &e) + { + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw e; + } } - - auto storeDataHashResult = storedDataHash.find(dataHash); - if(storeDataHashResult != storedDataHash.end()) + else { - string errMsg = "Database already contains data with hash: "; - errMsg += dataHash.toString(); - throw DatabaseStorageAlreadyExists(errMsg); + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw std::runtime_error("Unexpected operation type"); } - DataView storageData { new u8[dataSize], dataSize }; - memcpy(storageData.data, data, dataSize); - DatabaseStorageObject *databaseStorageObject = new DatabaseStorageObject(dataHash, storageData, timestamp, creatorUser->getPublicKey()); - it->second->objects.push_back(databaseStorageObject); - storedDataHash[dataHash] = databaseStorageObject; - - sibs::SafeSerializer serializer; - serializer.add(STORAGE_TYPE_APPEND); - serializer.add(timestamp); - serializer.add((u8*)creatorUser->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); - - serializer.add((u32)dataSize); - serializer.add(data, dataSize); - - serializer.add((u8*)nodeHash.getData(), HASH_BYTE_SIZE); - serializer.add((u8*)dataHash.getData(), HASH_BYTE_SIZE); - - fileAppend(dataFilePath, { serializer.getBuffer().data(), serializer.getBuffer().size() }); + { + sqlite3_reset(insertNodeAddDataRawStmt); + sqlite3_clear_bindings(insertNodeAddDataRawStmt); + + int rc; + rc = sqlite3_bind_int64(insertNodeAddDataRawStmt, 1, nodeRowId); + bindCheckError(rc); + + rc = sqlite3_bind_int64(insertNodeAddDataRawStmt, 2, nodeAddRowId); + bindCheckError(rc); + + rc = sqlite3_bind_blob(insertNodeAddDataRawStmt, 3, data, size, SQLITE_STATIC); + bindCheckError(rc); + + sqlite_step_rollback_on_failure(sqliteDb, insertNodeAddDataRawStmt, "insert data into NodeAddDataRaw"); + } - auto nodeDecryptionKeyIt = nodeDecryptionKeyMap.find(nodeHash); - if(nodeDecryptionKeyIt != nodeDecryptionKeyMap.end()) - decryptNodeAppendedData(nodeHash, databaseStorageObject, nodeDecryptionKeyIt->second); - } - - void DatabaseStorage::addToQuarantine(const Hash &dataHash, const Signature::PublicKey &creatorPublicKey, u64 timestamp, const u8 *data, usize dataSize) - { - auto storeDataHashResult = storedDataHash.find(dataHash); - if(storeDataHashResult != storedDataHash.end()) + auto nodeDecryptionKeyResult = getNodeDecryptionKey(nodeHash); + if(nodeDecryptionKeyResult.first) { - string errMsg = "Database already contains data with hash: "; - errMsg += dataHash.toString(); - throw DatabaseStorageAlreadyExists(errMsg); + if(operation == DatabaseOperation::ADD_DATA) + decryptNodeAddData(nodeAddRowId, nodeHash, dataHash, timestamp, &creatorPublicKey, additionalDataView, nodeDecryptionKeyResult.second); + else if(operation == DatabaseOperation::ADD_USER) + decryptNodeAddUser(nodeAddRowId, nodeHash, dataHash, timestamp, &creatorPublicKey, &userToAddPublicKey, DataView(groupToAddUserTo, GROUP_ID_LENGTH), nodeDecryptionKeyResult.second); } - DataView storageData { new u8[dataSize], dataSize }; - memcpy(storageData.data, data, dataSize); - DatabaseStorageQuarantineObject *databaseQuarantineStorageObject = new DatabaseStorageQuarantineObject(storageData, timestamp, creatorPublicKey); - quarantineStorageMap[creatorPublicKey].emplace_back(databaseQuarantineStorageObject); - // TODO: Add quarantine object to storedDataHash + sqlite3_exec(sqliteDb, "COMMIT", 0, 0, 0); } - bool DatabaseStorage::addGroup(const Hash &nodeHash, Group *group) + void DatabaseStorage::addGroup(const Hash &nodeHash, Group *group) { - DataViewMap<Group*> *groupByIdMap = nullptr; - auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); - if(groupByIdMapIt == nodeGroupByIdMap.end()) - { - groupByIdMap = new DataViewMap<Group*>(); - nodeGroupByIdMap[nodeHash] = groupByIdMap; - } - else - groupByIdMap = groupByIdMapIt->second; + sqlite3_reset(insertGroupStmt); + sqlite3_clear_bindings(insertGroupStmt); - if(groupByIdMap->find(group->getId()) != groupByIdMap->end()) - return false; - - (*groupByIdMap)[group->getId()] = group; + int rc; + rc = sqlite3_bind_blob(insertGroupStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); - sibs::SafeSerializer serializer; + rc = sqlite3_bind_blob(insertGroupStmt, 2, group->getId().data, GROUP_ID_LENGTH, SQLITE_STATIC); + bindCheckError(rc); - serializer.add((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + rc = sqlite3_bind_int(insertGroupStmt, 3, group->getPermission().getPermissionLevel()); + bindCheckError(rc); - serializer.add((u8)group->getName().size()); - serializer.add((u8*)group->getName().data(), group->getName().size()); + rc = sqlite3_bind_int64(insertGroupStmt, 4, group->getPermission().getPermissionFlags()); + bindCheckError(rc); - serializer.add((u8*)group->getId().data, GROUP_ID_LENGTH); - - serializer.add(group->getPermission().getPermissionLevel()); - serializer.add(group->getPermission().getPermissionFlags()); - - fileAppend(groupsFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); - + sqlite_step_rollback_on_failure(sqliteDb, insertGroupStmt, "insert data into NodeGroup"); Log::debug("Created group %s in node %s", bin2hex((const char*)group->getId().data, GROUP_ID_LENGTH).c_str(), nodeHash.toString().c_str()); - - return true; } - bool DatabaseStorage::addUser(const Hash &nodeHash, User *user) + void DatabaseStorage::addUserToGroup(const Hash &nodeHash, const Signature::PublicKey &userPublicKey, const DataView &groupId) { - Signature::MapPublicKey<User*> *publicKeyUserDataMap = nullptr; - auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); - if(publicKeyUserDataMapIt == nodePublicKeyUserDataMap.end()) - { - publicKeyUserDataMap = new Signature::MapPublicKey<User*>(); - nodePublicKeyUserDataMap[nodeHash] = publicKeyUserDataMap; - } - else - publicKeyUserDataMap = publicKeyUserDataMapIt->second; + sqlite3_reset(insertNodeUserGroupAssocStmt); + sqlite3_clear_bindings(insertNodeUserGroupAssocStmt); - if(publicKeyUserDataMap->find(user->getPublicKey()) != publicKeyUserDataMap->end()) - return false; + int rc; + rc = sqlite3_bind_blob(insertNodeUserGroupAssocStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); - (*publicKeyUserDataMap)[user->getPublicKey()] = user; + rc = sqlite3_bind_blob(insertNodeUserGroupAssocStmt, 2, userPublicKey.getData(), userPublicKey.getSize(), SQLITE_STATIC); + bindCheckError(rc); - // TODO: Instead of directly saving user to file, maybe user should be added to buffer and then save to file after we have several users (performance improvement) + rc = sqlite3_bind_blob(insertNodeUserGroupAssocStmt, 3, groupId.data, groupId.size, SQLITE_STATIC); + bindCheckError(rc); - sibs::SafeSerializer serializer; - - serializer.add((u8*)nodeHash.getData(), HASH_BYTE_SIZE); + sqlite_step_rollback_on_failure(sqliteDb, insertNodeUserGroupAssocStmt, "insert data into NodeUserGroupAssoc"); + } + + void DatabaseStorage::addUser(const Hash &nodeHash, const Signature::PublicKey &userPublicKey, const DataView &groupId) + { + sqlite3_reset(insertUserStmt); + sqlite3_clear_bindings(insertUserStmt); - serializer.add((u8)user->getName().size()); - serializer.add((u8*)user->getName().data(), user->getName().size()); + int rc; + rc = sqlite3_bind_blob(insertUserStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); - serializer.add((u8*)user->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); + rc = sqlite3_bind_blob(insertUserStmt, 2, userPublicKey.getData(), userPublicKey.getSize(), SQLITE_STATIC); + bindCheckError(rc); - serializer.add((u8)user->getGroups().size()); - for(Group *group : user->getGroups()) - { - serializer.add((u8*)group->getId().data, GROUP_ID_LENGTH); - } + sqlite_step_rollback_on_failure(sqliteDb, insertUserStmt, "insert data into NodeUser"); - fileAppend(usersFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); - - Log::debug("Created user %s in node %s", user->getPublicKey().toString().c_str(), nodeHash.toString().c_str()); + addUserToGroup(nodeHash, userPublicKey, groupId); - return true; + Log::debug("Created user %s in node %s", userPublicKey.toString().c_str(), nodeHash.toString().c_str()); } - /* - u64 DatabaseStorage::increaseUserActionCounter(const Hash &nodeHash, const Signature::PublicKey &userPublicKey) + void DatabaseStorage::fetchNodeRaw(const Hash &nodeHash, FetchNodeRawCallbackFunc callbackFunc) { - auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); - if(publicKeyUserDataMapIt != nodePublicKeyUserDataMap.end()) + SqlQuery query(sqliteDb, "SELECT data FROM NodeRaw WHERE node = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); + while(query.next()) { - auto it = publicKeyUserDataMapIt->second->find(userPublicKey); - if(it != publicKeyUserDataMapIt->second->end()) - { - return ++it->second->actionCounter; - } - - string errMsg = "User with id "; - errMsg += userPublicKey.toString(); - errMsg += " doesn't exist in node "; - errMsg += nodeHash.toString(); - throw DatabaseStorageException(errMsg); - } - - string errMsg = "Node with id "; - errMsg += nodeHash.toString(); - errMsg += " doesn't exist"; - throw DatabaseStorageException(errMsg); - } - */ - - const DatabaseStorageObjectList* DatabaseStorage::getStorage(const Hash &hash) const - { - auto it = storageMap.find(hash); - if(it != storageMap.end()) - return it->second; - return nullptr; - } - - const DataViewMap<Group*>* DatabaseStorage::getNodeGroups(const Hash &nodeHash) - { - auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); - if(groupByIdMapIt != nodeGroupByIdMap.end()) - return groupByIdMapIt->second; - return nullptr; - } - - Group* DatabaseStorage::getGroupById(const Hash &nodeHash, uint8_t groupId[GROUP_ID_LENGTH]) const - { - auto groupByIdMapIt = nodeGroupByIdMap.find(nodeHash); - if(groupByIdMapIt != nodeGroupByIdMap.end()) - { - auto it = groupByIdMapIt->second->find(DataView(groupId, GROUP_ID_LENGTH)); - if(it != groupByIdMapIt->second->end()) - return it->second; + const DataView data = query.getBlob(0); + callbackFunc(data); } - return nullptr; } - const Signature::MapPublicKey<User*>* DatabaseStorage::getNodeUsers(const Hash &nodeHash) + void DatabaseStorage::fetchNodeAddDataRaw(const Hash &nodeHash, FetchNodeAddDataRawCallbackFunc callbackFunc) { - auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); - if(publicKeyUserDataMapIt != nodePublicKeyUserDataMap.end()) - return publicKeyUserDataMapIt->second; - return nullptr; - } - - User* DatabaseStorage::getUserByPublicKey(const Hash &nodeHash, const Signature::PublicKey &userPublicKey) const - { - auto publicKeyUserDataMapIt = nodePublicKeyUserDataMap.find(nodeHash); - if(publicKeyUserDataMapIt != nodePublicKeyUserDataMap.end()) + SqlQuery query(sqliteDb, "SELECT data FROM NodeAddDataRaw WHERE node = ?", { DataView(nodeHash.getData(), nodeHash.getSize()) }); + while(query.next()) { - auto it = publicKeyUserDataMapIt->second->find(userPublicKey); - if(it != publicKeyUserDataMapIt->second->end()) - return it->second; + const DataView data = query.getBlob(0); + callbackFunc(data); } - return nullptr; } - +#if 0 bool DatabaseStorage::storeLocalUser(const string &username, const Signature::KeyPair &keyPair, const string &password) { auto it = nameLocalUsersMap.find(username); @@ -774,7 +686,7 @@ namespace odhtdb assert(sizeof(userEncryptedPrivateKey.encryptedPrivateKey) == encryptedPrivateKey.getCipherText().size); memcpy(userEncryptedPrivateKey.encryptedPrivateKey, encryptedPrivateKey.getCipherText().data, encryptedPrivateKey.getCipherText().size); - LocalUserEncrypted *localUserEncrypted = LocalUserEncrypted::create(keyPair.getPublicKey(), userEncryptedPrivateKey, username); + LocalUserEncrypted *localUserEncrypted = LocalUserEncrypted::create(keyPair.getPublicKey(), userEncryptedPrivateKey); nameLocalUsersMap[username] = localUserEncrypted; sibs::SafeSerializer serializer; @@ -817,271 +729,288 @@ namespace odhtdb throw DatabaseStorageWrongPassword(errMsg); } } - - vector<NodeLocalUser> DatabaseStorage::getLocalNodeUsers(const Signature::KeyPair &keyPair) - { - vector<NodeLocalUser> localUsers; - - for(auto nodeIt : nodePublicKeyUserDataMap) - { - auto userIt = nodeIt.second->find(keyPair.getPublicKey()); - if(userIt != nodeIt.second->end()) - { - User *user = userIt->second; - if(user->getType() != User::Type::LOCAL) - { - LocalUser *localUser = LocalUser::create(keyPair, user->getName(), nullptr); - for(Group *group : user->getGroups()) - { - localUser->addToGroup(group); - } - - (*nodeIt.second)[keyPair.getPublicKey()] = localUser; - localUsers.push_back({ nodeIt.first, localUser }); - delete user; - } - else - localUsers.push_back({ nodeIt.first, static_cast<LocalUser*>(user) }); - } - } - - return localUsers; - } - - DatabaseStorageObject* DatabaseStorage::getDataById(const Hash &dataHash) - { - auto storageIt = storedDataHash.find(dataHash); - if(storageIt != storedDataHash.end()) - return storageIt->second; - return nullptr; - } - - std::pair<bool, std::shared_ptr<OwnedMemory>> DatabaseStorage::getNodeDecryptionKey(const Hash &nodeHash) +#endif + pair<bool, shared_ptr<OwnedMemory>> DatabaseStorage::getNodeDecryptionKey(const Hash &nodeHash) { - auto nodeDecryptionKeyIt = nodeDecryptionKeyMap.find(nodeHash); - if(nodeDecryptionKeyIt != nodeDecryptionKeyMap.end()) - return make_pair(true, nodeDecryptionKeyIt->second); - return make_pair(false, make_shared<OwnedMemory>()); + sqlite3_reset(getNodeDecryptionKeyStmt); + sqlite3_clear_bindings(getNodeDecryptionKeyStmt); + + int rc; + rc = sqlite3_bind_blob(getNodeDecryptionKeyStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_step(getNodeDecryptionKeyStmt); + if(rc != SQLITE_ROW) + return make_pair(false, make_shared<OwnedMemory>()); + + const void *decryptionKeyRaw = sqlite3_column_blob(getNodeDecryptionKeyStmt, 0); + u8 *decryptionKeyRawCopy = new u8[ENCRYPTION_KEY_BYTE_SIZE]; + memcpy(decryptionKeyRawCopy, decryptionKeyRaw, ENCRYPTION_KEY_BYTE_SIZE); + shared_ptr<OwnedMemory> decryptionKey = make_shared<OwnedMemory>(decryptionKeyRawCopy, ENCRYPTION_KEY_BYTE_SIZE); + return make_pair(true, decryptionKey); } void DatabaseStorage::setNodeDecryptionKey(const Hash &nodeHash, const DataView &decryptionKeyView) { - bool nodeHasExistingEncryptionKey = nodeDecryptionKeyMap.find(nodeHash) != nodeDecryptionKeyMap.end(); + auto nodeDecryptionKeyResult = getNodeDecryptionKey(nodeHash); + bool nodeHasExistingEncryptionKey = nodeDecryptionKeyResult.first; - char *decryptionKeyRaw = new char[decryptionKeyView.size]; - memcpy(decryptionKeyRaw, decryptionKeyView.data, decryptionKeyView.size); - shared_ptr<OwnedMemory> decryptionKey = make_shared<OwnedMemory>(decryptionKeyRaw, decryptionKeyView.size); - nodeDecryptionKeyMap[nodeHash] = decryptionKey; + sqlite3_reset(setNodeDecryptionKeyStmt); + sqlite3_clear_bindings(setNodeDecryptionKeyStmt); - sibs::SafeSerializer serializer; - serializer.add((const u8*)nodeHash.getData(), HASH_BYTE_SIZE); - serializer.add((const u8*)decryptionKeyView.data, ENCRYPTION_KEY_BYTE_SIZE); - fileAppend(nodeDecryptionKeysFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); + int rc; + rc = sqlite3_bind_blob(setNodeDecryptionKeyStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_bind_blob(setNodeDecryptionKeyStmt, 2, decryptionKeyView.data, decryptionKeyView.size, SQLITE_STATIC); + bindCheckError(rc); - auto storageIt = storageMap.find(nodeHash); - if(storageIt == storageMap.end()) return; + sqlite_step_rollback_on_failure(sqliteDb, setNodeDecryptionKeyStmt, "insert or replace node decryption key"); // When changing existing encryption key, do not decrypt the existing data as it has already been decrypted, // the new key should only be used for new data if(!nodeHasExistingEncryptionKey) - decryptNodeData(nodeHash, storageIt->second, decryptionKey); + decryptNodeData(nodeHash, nodeDecryptionKeyResult.second); } - bool DatabaseStorage::decryptNodeData(const Hash &nodeHash, DatabaseStorageObjectList *databaseCreateObject, const shared_ptr<OwnedMemory> decryptionKey) + bool DatabaseStorage::decryptNodeData(const Hash &nodeHash, const shared_ptr<OwnedMemory> decryptionKey) { - sibs::SafeDeserializer deserializer((u8*)databaseCreateObject->data.data + databaseCreateObject->offsetToEncryptedData, databaseCreateObject->data.size - databaseCreateObject->offsetToEncryptedData); - - u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; - deserializer.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); - - DataView dataToDecrypt((void*)deserializer.getBuffer(), deserializer.getSize()); - Decryption decryptedBody(dataToDecrypt, DataView(nonce, ENCRYPTION_NONCE_BYTE_SIZE), DataView(decryptionKey->data, ENCRYPTION_KEY_BYTE_SIZE)); - sibs::SafeDeserializer bodyDeserializer((const u8*)decryptedBody.getDecryptedText().data, decryptedBody.getDecryptedText().size); - - u8 creatorNameLength = bodyDeserializer.extract<u8>(); - string creatorName; // TODO: Add this user name to storage creator name - creatorName.resize(creatorNameLength); - bodyDeserializer.extract((u8*)&creatorName[0], creatorNameLength); + sqlite3_reset(selectNodeStmt); + sqlite3_clear_bindings(selectNodeStmt); - u8 nameLength = bodyDeserializer.extract<u8>(); - string name; - name.resize(nameLength); - bodyDeserializer.extract((u8*)&name[0], nameLength); - - sibs::SafeSerializer serializer; - serializer.add(DecryptedDataType::STORAGE_CREATE); - serializer.add((const u8*)nodeHash.getData(), HASH_BYTE_SIZE); - serializer.add(creatorNameLength); - serializer.add((const u8*)creatorName.data(), creatorNameLength); - serializer.add(nameLength); - serializer.add((const u8*)name.data(), nameLength); - fileAppend(decryptedDataFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); - - User *creator = getUserByPublicKey(nodeHash, databaseCreateObject->creatorPublicKey); - if(!creator) + int rc; + rc = sqlite3_bind_blob(selectNodeStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + rc = sqlite3_step(selectNodeStmt); + if(rc == SQLITE_DONE) + return true; + else if(rc != SQLITE_ROW) { - Log::error("Creator with public key %s does not exist in node %s", databaseCreateObject->creatorPublicKey.toString().c_str(), nodeHash.toString().c_str()); - return false; + string errMsg = "select node failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + throw DatabaseStorageException(errMsg); } - creator->name = move(creatorName); - databaseCreateObject->nodeName = name; - databaseCreateObject->isDecrypted = true; - Log::debug("Deserialized node create data, name: %s", name.c_str()); - const DatabaseCreateNodeRequest createNodeRequest(&nodeHash, databaseCreateObject->createdTimestamp, creator, move(name)); + u64 timestamp = sqlite3_column_int64(selectNodeStmt, 0); + const void *creatorPublicKeyRaw = sqlite3_column_blob(selectNodeStmt, 1); + Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + const void *adminGroupIdRaw = sqlite3_column_blob(selectNodeStmt, 2); + u8 adminGroup[GROUP_ID_LENGTH]; + memcpy(adminGroup, adminGroupIdRaw, GROUP_ID_LENGTH); + + return decryptNodeData(nodeHash, decryptionKey, &creatorPublicKey, DataView(adminGroup, GROUP_ID_LENGTH), timestamp); + } + + bool DatabaseStorage::decryptNodeData(const Hash &nodeHash, const shared_ptr<OwnedMemory> decryptionKey, const Signature::PublicKey *creatorPublicKey, const DataView &adminGroupId, u64 timestamp) + { + const DatabaseCreateNodeRequest createNodeRequest(&nodeHash, timestamp, creatorPublicKey, adminGroupId); if(database->onCreateNodeCallbackFunc) database->onCreateNodeCallbackFunc(createNodeRequest); + sqlite3_reset(selectNodeAddDataByNodeStmt); + sqlite3_clear_bindings(selectNodeAddDataByNodeStmt); + + int rc; + rc = sqlite3_bind_blob(selectNodeAddDataByNodeStmt, 1, nodeHash.getData(), nodeHash.getSize(), SQLITE_STATIC); + bindCheckError(rc); + + sqlite3_exec(sqliteDb, "BEGIN", 0, 0, 0); bool success = true; - for(auto appendObject : databaseCreateObject->objects) + while(true) { - bool appendObjectResult = decryptNodeAppendedData(nodeHash, appendObject, decryptionKey); - if(!appendObjectResult) - success = false; + rc = sqlite3_step(selectNodeAddDataByNodeStmt); + if(rc == SQLITE_ROW) + { + i64 rowId = sqlite3_column_int64(selectNodeAddDataByNodeStmt, 0); + + const void *requestHashRaw = sqlite3_column_blob(selectNodeAddDataByNodeStmt, 1); + Hash requestHash; + memcpy(requestHash.getData(), requestHashRaw, HASH_BYTE_SIZE); + + DatabaseOperation operation = (DatabaseOperation)sqlite3_column_int(selectNodeAddDataByNodeStmt, 2); + + u64 timestamp = sqlite3_column_int64(selectNodeAddDataByNodeStmt, 3); + + const void *creatorPublicKeyRaw = sqlite3_column_blob(selectNodeAddDataByNodeStmt, 4); + Signature::PublicKey creatorPublicKey((const char*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + + if(operation == DatabaseOperation::ADD_DATA) + { + sqlite3_reset(selectNodeAddDataAdditionalStmt); + sqlite3_clear_bindings(selectNodeAddDataAdditionalStmt); + + int rc; + rc = sqlite3_bind_int64(selectNodeAddDataAdditionalStmt, 1, rowId); + bindCheckError(rc); + + rc = sqlite3_step(selectNodeAddDataAdditionalStmt); + if(rc != SQLITE_ROW) + { + string errMsg = "select NodeAddDataAdditional failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw DatabaseStorageException(errMsg); + } + + // TODO: There is no need to allocate/deallocate several times, this can be moved outside the while loop + const void *encryptedDataRaw = sqlite3_column_blob(selectNodeAddDataAdditionalStmt, 0); + int encryptedDataSize = sqlite3_column_bytes(selectNodeAddDataAdditionalStmt, 0); + OwnedMemory encryptedData(new u8[encryptedDataSize], encryptedDataSize); + memcpy(encryptedData.data, encryptedDataRaw, encryptedDataSize); + + bool appendObjectResult = decryptNodeAddData(rowId, nodeHash, requestHash, timestamp, &creatorPublicKey, DataView(encryptedData.data, encryptedData.size), decryptionKey); + if(!appendObjectResult) + success = false; + } + else if(operation == DatabaseOperation::ADD_USER) + { + sqlite3_reset(selectNodeAddUserDataStmt); + sqlite3_clear_bindings(selectNodeAddUserDataStmt); + + int rc; + rc = sqlite3_bind_int64(selectNodeAddUserDataStmt, 1, rowId); + bindCheckError(rc); + + rc = sqlite3_step(selectNodeAddUserDataStmt); + if(rc != SQLITE_ROW) + { + string errMsg = "select NodeAddUserData failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw DatabaseStorageException(errMsg); + } + + const void *userToAddPublicKeyRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 0); + Signature::PublicKey userToAddPublicKey((const char*)userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + + const void *groupToAddUserToRaw = sqlite3_column_blob(selectNodeAddUserDataStmt, 1); + u8 groupToAddUserTo[GROUP_ID_LENGTH]; + memcpy(groupToAddUserTo, groupToAddUserToRaw, GROUP_ID_LENGTH); + + bool appendObjectResult = decryptNodeAddUser(rowId, nodeHash, requestHash, timestamp, &creatorPublicKey, &userToAddPublicKey, DataView(groupToAddUserTo, GROUP_ID_LENGTH), decryptionKey); + if(!appendObjectResult) + success = false; + } + } + else if(rc == SQLITE_DONE) + { + break; + } + else + { + string errMsg = "select NodeAddData by node failed with error: "; + errMsg += sqlite3_errmsg(sqliteDb); + sqlite3_exec(sqliteDb, "ROLLBACK", 0, 0, 0); + throw DatabaseStorageException(errMsg); + } } + sqlite3_exec(sqliteDb, "COMMIT", 0, 0, 0); + return success; } - bool DatabaseStorage::decryptNodeAppendedData(const Hash &nodeHash, DatabaseStorageObject *databaseAppendObject, const shared_ptr<OwnedMemory> decryptionKey) + void DatabaseStorage::setNodeAddDataDecrypted(i64 rowId) { - sibs::SafeDeserializer deserializer((u8*)databaseAppendObject->data.data, databaseAppendObject->data.size); - char creatorPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializer.extract((u8*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey creatorPublicKey(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + sqlite3_reset(setNodeAddDataDecryptedStmt); + sqlite3_clear_bindings(setNodeAddDataDecryptedStmt); - DataView signedData((void*)deserializer.getBuffer(), deserializer.getSize()); - string unsignedData = creatorPublicKey.unsign(signedData); - sibs::SafeDeserializer deserializerUnsigned((u8*)unsignedData.data(), unsignedData.size()); + int rc; + rc = sqlite3_bind_int(setNodeAddDataDecryptedStmt, 1, 1); + bindCheckError(rc); + + rc = sqlite3_bind_int64(setNodeAddDataDecryptedStmt, 2, rowId); + bindCheckError(rc); - u16 packetStructureVersion = deserializerUnsigned.extract<u16>(); - u64 creationDate = deserializerUnsigned.extract<u64>(); + sqlite_step_rollback_on_failure(sqliteDb, setNodeAddDataDecryptedStmt, "set NodeAddData decrypted"); + } + + void DatabaseStorage::setNodeAddDataDecryptedData(i64 rowId, const DataView &decryptedData) + { + setNodeAddDataDecrypted(rowId); + sqlite3_reset(setNodeAddDataAdditionalDataStmt); + sqlite3_clear_bindings(setNodeAddDataAdditionalDataStmt); - DatabaseOperation operation = deserializerUnsigned.extract<DatabaseOperation>(); + int rc; + rc = sqlite3_bind_blob(setNodeAddDataAdditionalDataStmt, 1, decryptedData.data, decryptedData.size, SQLITE_STATIC); + bindCheckError(rc); - auto creatorUser = getUserByPublicKey(nodeHash, creatorPublicKey); - if(!creatorUser) + rc = sqlite3_bind_int64(setNodeAddDataAdditionalDataStmt, 2, rowId); + bindCheckError(rc); + + sqlite_step_rollback_on_failure(sqliteDb, setNodeAddDataAdditionalDataStmt, "set NodeAddData decrypted"); + } + + bool DatabaseStorage::decryptNodeAddData(i64 rowId, const Hash &nodeHash, const Hash &dataHash, u64 timestamp, const Signature::PublicKey *creatorPublicKey, const DataView &encryptedData, const shared_ptr<OwnedMemory> decryptionKey) + { + SqlQuery queryCreatorGroupWithRightsToAddData(sqliteDb, + "SELECT nodeGroup.rowid FROM NodeUserGroupAssoc AS userGroupAssoc" + " INNER JOIN NodeGroup AS nodeGroup ON nodeGroup.groupId = userGroupAssoc.groupId" + " WHERE userGroupAssoc.node = ? AND userGroupAssoc.userPublicKey = ? AND (nodeGroup.permissionFlags & ?) != 0", + { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)creatorPublicKey->getData(), creatorPublicKey->getSize()), (i64)PermissionType::ADD_DATA }); + if(!queryCreatorGroupWithRightsToAddData.next()) { - Log::error("User with public key %s does not exist in node %s", creatorPublicKey.toString().c_str(), nodeHash.toString().c_str()); + // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, + // or we haven't received the packet that modifies group with the permission to perform the operation. + // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, + // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. + // How to handle this? + Log::error("User %s is not allowed to add data to node %s", creatorPublicKey->toString().c_str(), nodeHash.toString().c_str()); return false; } - - if(operation == DatabaseOperation::ADD_DATA) + + sibs::SafeDeserializer deserializer((const u8*)encryptedData.data, encryptedData.size); + + u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; + deserializer.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); + DataView dataToDecrypt((void*)deserializer.getBuffer(), deserializer.getSize()); + Decryption decryptedBody(dataToDecrypt, DataView(nonce, ENCRYPTION_NONCE_BYTE_SIZE), DataView(decryptionKey->data, ENCRYPTION_KEY_BYTE_SIZE)); + setNodeAddDataDecryptedData(rowId, decryptedBody.getDecryptedText()); + + Log::debug("Got add object, timestamp: %zu, data: %.*s", timestamp, decryptedBody.getDecryptedText().size, decryptedBody.getDecryptedText().data); + const DatabaseAddNodeRequest addNodeRequest(&nodeHash, &dataHash, timestamp, creatorPublicKey, decryptedBody.getDecryptedText()); + if(database->onAddNodeCallbackFunc) + database->onAddNodeCallbackFunc(addNodeRequest); + return true; + } + + bool DatabaseStorage::decryptNodeAddUser(i64 rowId, const Hash &nodeHash, const Hash &dataHash, u64 timestamp, const Signature::PublicKey *creatorPublicKey, const Signature::PublicKey *userToAddPublicKey, const DataView &groupToAddUserTo, const shared_ptr<OwnedMemory> decryptionKey) + { + SqlQuery queryGroupToAdd(sqliteDb, "SELECT permissionLevel FROM NodeGroup WHERE groupId = ?", { groupToAddUserTo }); + if(!queryGroupToAdd.next()) { - if(deserializerUnsigned.getSize() < ENCRYPTION_NONCE_BYTE_SIZE) - { - Log::error("Unsigned encrypted body is too small (unable to extract nonce)"); - return false; - } -#if 0 - if(!creatorUser->isAllowedToPerformAction(PermissionType::ADD_DATA)) - { - // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, - // or we haven't received the packet that modifies group with the permission to perform the operation. - // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, - // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. - // How to handle this? - Log::error("User %s is not allowed to add data to node %s", creatorUser->getName().c_str(), nodeHash.toString().c_str()); - return false; - } -#endif - - u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; - deserializerUnsigned.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); - DataView dataToDecrypt((void*)deserializerUnsigned.getBuffer(), deserializerUnsigned.getSize()); - Decryption decryptedBody(dataToDecrypt, DataView(nonce, ENCRYPTION_NONCE_BYTE_SIZE), DataView(decryptionKey->data, ENCRYPTION_KEY_BYTE_SIZE)); - - sibs::SafeSerializer serializer; - serializer.add(DecryptedDataType::STORAGE_ADD_DATA); - serializer.add((const u8*)databaseAppendObject->requestHash.getData(), HASH_BYTE_SIZE); - serializer.add((u32)decryptedBody.getDecryptedText().size); - serializer.add((const u8*)decryptedBody.getDecryptedText().data, decryptedBody.getDecryptedText().size); - fileAppend(decryptedDataFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); - databaseAppendObject->decryptedObject.data.data = new char[decryptedBody.getDecryptedText().size]; - memcpy(databaseAppendObject->decryptedObject.data.data, decryptedBody.getDecryptedText().data, decryptedBody.getDecryptedText().size); - databaseAppendObject->decryptedObject.data.size = decryptedBody.getDecryptedText().size; - databaseAppendObject->decryptedObject.operation = operation; - - Log::debug("Got add object, timestamp: %zu, data: %.*s", creationDate, decryptedBody.getDecryptedText().size, decryptedBody.getDecryptedText().data); - const DatabaseAddNodeRequest addNodeRequest(&nodeHash, &databaseAppendObject->requestHash, creationDate, creatorUser, decryptedBody.getDecryptedText()); - if(database->onAddNodeCallbackFunc) - database->onAddNodeCallbackFunc(addNodeRequest); - } - else if(operation == DatabaseOperation::ADD_USER) - { - u8 nameLength = deserializerUnsigned.extract<u8>(); - - u8 nonce[ENCRYPTION_NONCE_BYTE_SIZE]; - deserializerUnsigned.extract(nonce, ENCRYPTION_NONCE_BYTE_SIZE); - DataView dataToDecrypt((void*)deserializerUnsigned.getBuffer(), ENCRYPTION_CHECKSUM_BYTE_SIZE + nameLength); - Decryption decryptedUserName(dataToDecrypt, DataView(nonce, ENCRYPTION_NONCE_BYTE_SIZE), DataView(decryptionKey->data, ENCRYPTION_KEY_BYTE_SIZE)); - - string username; - username.resize(nameLength); - assert(decryptedUserName.getDecryptedText().size == nameLength); - memcpy(&username[0], decryptedUserName.getDecryptedText().data, nameLength); - - sibs::SafeDeserializer deserializerSkippedEncryptedData(deserializerUnsigned.getBuffer() + ENCRYPTION_CHECKSUM_BYTE_SIZE + nameLength, PUBLIC_KEY_NUM_BYTES + GROUP_ID_LENGTH); - - char userToAddPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; - deserializerSkippedEncryptedData.extract((u8*)userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey userToAddPublicKey(userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - - uint8_t groupId[GROUP_ID_LENGTH]; - deserializerSkippedEncryptedData.extract(groupId, GROUP_ID_LENGTH); - - auto group = getGroupById(nodeHash, groupId); - if(!group) - { - // TODO: Add to quarantine? - Log::error("There is no group with id %s in node %s", bin2hex((const char*)groupId, GROUP_ID_LENGTH).c_str(), nodeHash.toString().c_str()); - return false; - } - - auto user = getUserByPublicKey(nodeHash, userToAddPublicKey); - if(!user) - { - Log::error("User to add with public key %s doesn't exist in node %s", userToAddPublicKey.toString().c_str(), nodeHash.toString().c_str()); - return false; - } -#if 0 - auto creatorUserGroupWithRights = getGroupWithRightsToAddUserToGroup(creatorUser->getGroups(), group); - if(!creatorUserGroupWithRights) - { - // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, - // or we haven't received the packet that modifies group with the permission to perform the operation. - // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, - // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. - // How to handle this? - string errMsg = "User "; - errMsg += creatorUser->getName(); - errMsg += " is not allowed to perform the operation: "; - errMsg += to_string((u8)operation); - throw PermissionDeniedException(errMsg); - } -#endif - sibs::SafeSerializer serializer; - serializer.add(DecryptedDataType::STORAGE_ADD_USER); - serializer.add((const u8*)nodeHash.getData(), HASH_BYTE_SIZE); - serializer.add((const u8*)databaseAppendObject->requestHash.getData(), HASH_BYTE_SIZE); - serializer.add((const u8*)userToAddPublicKey.getData(), PUBLIC_KEY_NUM_BYTES); - serializer.add((u32)decryptedUserName.getDecryptedText().size); - serializer.add((const u8*)decryptedUserName.getDecryptedText().data, decryptedUserName.getDecryptedText().size); - fileAppend(decryptedDataFilePath, DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); - databaseAppendObject->decryptedObject.data.data = new char[decryptedUserName.getDecryptedText().size]; - memcpy(databaseAppendObject->decryptedObject.data.data, decryptedUserName.getDecryptedText().data, decryptedUserName.getDecryptedText().size); - databaseAppendObject->decryptedObject.data.size = decryptedUserName.getDecryptedText().size; - databaseAppendObject->decryptedObject.operation = operation; - - Log::debug("Got add user object, timestamp: %zu, user added: %.*s", creationDate, nameLength, username.c_str()); - DatabaseAddUserRequest addUserRequest(&nodeHash, &databaseAppendObject->requestHash, creationDate, creatorUser, user, group); - if(database->onAddUserCallbackFunc) - database->onAddUserCallbackFunc(addUserRequest); + // TODO: Add to quarantine? + Log::error("There is no group with id %s in node %s", bin2hex((const char*)groupToAddUserTo.data, groupToAddUserTo.size).c_str(), nodeHash.toString().c_str()); + return false; } - else + + int groupToAddPermissionLevel = queryGroupToAdd.getInt(0); + + SqlQuery queryCreatorGroupWithRightsToAddUserToGroup(sqliteDb, + "SELECT nodeGroup.rowid FROM NodeUserGroupAssoc AS userGroupAssoc" + " INNER JOIN NodeGroup AS nodeGroup ON nodeGroup.groupId = userGroupAssoc.groupId" + " WHERE userGroupAssoc.node = ? AND userGroupAssoc.userPublicKey = ? AND (nodeGroup.permissionLevel = ? AND ((nodeGroup.permissionFlags & ?) != 0) OR (nodeGroup.permissionLevel > ? AND (nodeGroup.permissionFlags & ?) != 0))", + { DataView(nodeHash.getData(), nodeHash.getSize()), DataView((void*)creatorPublicKey->getData(), creatorPublicKey->getSize()), groupToAddPermissionLevel, (i64)PermissionType::ADD_USER_SAME_LEVEL, groupToAddPermissionLevel, (i64)PermissionType::ADD_USER_HIGHER_LEVEL }); + if(!queryCreatorGroupWithRightsToAddUserToGroup.next()) { - Log::error("Got unexpected operation %d", operation); - return false; + // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, + // or we haven't received the packet that modifies group with the permission to perform the operation. + // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, + // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. + // How to handle this? + string errMsg = "User "; + errMsg += creatorPublicKey->toString(); + errMsg += " is not allowed to perform the operation: ADD USER"; + throw PermissionDeniedException(errMsg); } + + addUser(nodeHash, *userToAddPublicKey, groupToAddUserTo); + setNodeAddDataDecrypted(rowId); + + Log::debug("Got add user object, timestamp: %zu, user added: %s", timestamp, userToAddPublicKey->toString().c_str()); + DatabaseAddUserRequest addUserRequest(&nodeHash, &dataHash, timestamp, creatorPublicKey, userToAddPublicKey, groupToAddUserTo); + if(database->onAddUserCallbackFunc) + database->onAddUserCallbackFunc(addUserRequest); return true; } @@ -1092,23 +1021,7 @@ namespace odhtdb void DatabaseStorage::update() { - // TODO: Modify this to iterate backwards. Because list is sorted in order of timestamp, we can remove data in range auto time = chrono::high_resolution_clock::now().time_since_epoch(); auto timeMicroseconds = chrono::duration_cast<chrono::microseconds>(time).count(); - for(auto mapIt = quarantineStorageMap.begin(); mapIt != quarantineStorageMap.end(); ) - { - for(auto vecIt = mapIt->second.begin(); vecIt != mapIt->second.end(); ) - { - if(timeMicroseconds - (*vecIt)->storedTimestamp > QUARANTINE_STORAGE_TIME_MICROSECONDS) - vecIt = mapIt->second.erase(vecIt); - else - ++vecIt; - } - - if(mapIt->second.empty()) - mapIt = quarantineStorageMap.erase(mapIt); - else - ++mapIt; - } } } diff --git a/src/Encryption.cpp b/src/Encryption.cpp index 9000519..ff37270 100644 --- a/src/Encryption.cpp +++ b/src/Encryption.cpp @@ -19,7 +19,7 @@ namespace odhtdb memcpy(key, _key.data, _key.size); } else - crypto_aead_xchacha20poly1305_ietf_keygen(key); + generateKey(key); randombytes_buf(nonce, ENCRYPTION_NONCE_BYTE_SIZE); if(crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, &cipherTextLength, (const unsigned char*)data.data, data.size, (const unsigned char*)additionalData.data, additionalData.size, nullptr, nonce, key) < 0) @@ -46,6 +46,11 @@ namespace odhtdb return DataView((void*)cipherText, cipherTextLength); } + void Encryption::generateKey(unsigned char *output) + { + crypto_aead_xchacha20poly1305_ietf_keygen(output); + } + Decryption::Decryption(const DataView &data, const DataView &nonce, const DataView &key) { decryptedTextLength = data.size; diff --git a/src/Group.cpp b/src/Group.cpp index 4210142..9db3e4e 100644 --- a/src/Group.cpp +++ b/src/Group.cpp @@ -6,12 +6,9 @@ using namespace std; namespace odhtdb { - Group::Group(const string &_name, uint8_t _id[GROUP_ID_LENGTH], const Permission &_permission) : - name(_name), + Group::Group(uint8_t _id[GROUP_ID_LENGTH], const Permission &_permission) : permission(_permission) { - if(name.size() > 255) - throw GroupNameTooLongException(name); memcpy(id, _id, GROUP_ID_LENGTH); } @@ -37,11 +34,6 @@ namespace odhtdb } return false; } - - const string& Group::getName() const - { - return name; - } DataView Group::getId() const { diff --git a/src/LocalUser.cpp b/src/LocalUser.cpp index 665f05d..950c953 100644 --- a/src/LocalUser.cpp +++ b/src/LocalUser.cpp @@ -2,8 +2,8 @@ namespace odhtdb { - LocalUser::LocalUser(const Signature::KeyPair &_keyPair, const std::string &name, Group *group) : - User(User::Type::LOCAL, name, group), + LocalUser::LocalUser(const Signature::KeyPair &_keyPair, Group *group) : + User(User::Type::LOCAL, group), keyPair(_keyPair) { diff --git a/src/Signature.cpp b/src/Signature.cpp index c64af01..ba3ebde 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -12,6 +12,11 @@ namespace odhtdb { const PublicKey PublicKey::ZERO("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", PUBLIC_KEY_NUM_BYTES); + PublicKey::PublicKey() + { + memset(data, 0, PUBLIC_KEY_NUM_BYTES); + } + PublicKey::PublicKey(const char *_data, size_t size) { if(size != PUBLIC_KEY_NUM_BYTES) @@ -71,6 +76,11 @@ namespace odhtdb const PrivateKey PrivateKey::ZERO("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", PRIVATE_KEY_NUM_BYTES); + PrivateKey::PrivateKey() + { + memset(data, 0, PRIVATE_KEY_NUM_BYTES); + } + PrivateKey::PrivateKey(const char *_data, size_t size) { if(size != PRIVATE_KEY_NUM_BYTES) diff --git a/src/User.cpp b/src/User.cpp index 58e350a..559f8db 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -3,10 +3,8 @@ namespace odhtdb { - User::User(Type _type, const std::string &_name, Group *group) : type(_type), name(_name) + User::User(Type _type, Group *group) : type(_type) { - if(name.size() > 255) - throw UserNameTooLongException(name); addToGroup(group); } diff --git a/src/sql/SqlQuery.cpp b/src/sql/SqlQuery.cpp new file mode 100644 index 0000000..47f1463 --- /dev/null +++ b/src/sql/SqlQuery.cpp @@ -0,0 +1,129 @@ +#include "../../include/odhtdb/sql/SqlQuery.hpp" +#include <sqlite3.h> + +namespace odhtdb +{ + int SqlArg::bind(sqlite3_stmt *stmt, int paramIndex) const + { + switch(type) + { + case Type::DATA_VIEW: + return sqlite3_bind_blob(stmt, paramIndex, dataView.data, dataView.size, SQLITE_STATIC); + case Type::INT: + return sqlite3_bind_int(stmt, paramIndex, integer); + case Type::INT64: + return sqlite3_bind_int64(stmt, paramIndex, integer64); + } + return SQLITE_OK; + } + + SqlQuery::SqlQuery(sqlite3 *_db, const char *sql, std::initializer_list<SqlArg> args) : + db(_db), + stmt(nullptr), + numColumns(0) + { + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); + if(rc != SQLITE_OK) + { + std::string errMsg = "Failed to prepare sqlite statement, error: "; + errMsg += sqlite3_errmsg(db); + sqlite3_exec(db, "ROLLBACK", 0, 0, 0); + throw SqlQueryException(errMsg); + } + + int numParams = sqlite3_bind_parameter_count(stmt); + if(args.size() != numParams) + { + std::string errMsg = "Failed to prepare sqlite statement, error: Sql has "; + errMsg += std::to_string(numParams); + errMsg += " parameters, got "; + errMsg += std::to_string(args.size()); + errMsg += " arguments"; + sqlite3_finalize(stmt); + stmt = nullptr; + sqlite3_exec(db, "ROLLBACK", 0, 0, 0); + throw SqlQueryException(errMsg); + } + + int paramIndex = 1; + for(const SqlArg &arg : args) + { + rc = arg.bind(stmt, paramIndex); + if(rc != SQLITE_OK) + { + std::string errMsg = "Failed to bind param, error code: "; + errMsg += std::to_string(rc); + sqlite3_finalize(stmt); + stmt = nullptr; + sqlite3_exec(db, "ROLLBACK", 0, 0, 0); + throw SqlQueryException(errMsg); + } + ++paramIndex; + } + } + + SqlQuery::~SqlQuery() + { + sqlite3_finalize(stmt); + } + + bool SqlQuery::next() + { + int rc = sqlite3_step(stmt); + if(rc == SQLITE_DONE) + return false; + else if(rc != SQLITE_ROW) + { + std::string errMsg = "Failed to perform sql select, error: "; + errMsg += sqlite3_errmsg(db); + sqlite3_exec(db, "ROLLBACK", 0, 0, 0); + throw SqlQueryException(errMsg); + } + + numColumns = sqlite3_data_count(stmt); + return true; + } + + void SqlQuery::checkColumnIndex(int index) + { + if(index < 0 || index >= numColumns) + { + std::string errMsg; + if(numColumns == 0) + { + errMsg += "Attempt to get column "; + errMsg += std::to_string(index); + errMsg += " but result does not have any columns"; + } + else + { + errMsg += "Column index "; + errMsg += std::to_string(index); + errMsg += " has to be between 0 and "; + errMsg += std::to_string(numColumns - 1); + } + sqlite3_exec(db, "ROLLBACK", 0, 0, 0); + throw SqlQueryException(errMsg); + } + } + + int SqlQuery::getInt(int index) + { + checkColumnIndex(index); + return sqlite3_column_int(stmt, index); + } + + i64 SqlQuery::getInt64(int index) + { + checkColumnIndex(index); + return sqlite3_column_int64(stmt, index); + } + + const DataView SqlQuery::getBlob(int index) + { + checkColumnIndex(index); + const void *data = sqlite3_column_blob(stmt, index); + int size = sqlite3_column_bytes(stmt, index); + return { (void*)data, (usize)size }; + } +} |