void AppInterfaceImpl::groupUpdateSendDone(const string& groupId) { LOGGER(DEBUGGING, __func__, " -->"); unique_lock<mutex> lck(currentChangeSetLock); if (!updateInProgress) { return; } memset(updateIdGlobal, 0, sizeof(updateIdGlobal)); // We've sent out the new change set to all devices. This change set contains // the current status of the group attributes (with vector clocks) and the collapsed // information of new and removed members. The current change set now becomes the pending // change set, waiting for ACKs PtrChangeSet changeSet = getGroupChangeSet(groupId); if (!changeSet) { return; } // Remove old pending change set, makes sure we have only _one_ pending change // set at a time removeFromPendingChangeSets(groupId); // Add it to pending change set cache map and save in persistent store pendingChangeSets.insert(pair<string, PtrChangeSet>(groupId, changeSet)); changeSet->SerializeAsString(); store_->insertChangeSet(groupId, changeSet->SerializeAsString()); // Remove as the the current change set currentChangeSets.erase(groupId); updateInProgress = false; LOGGER(DEBUGGING, __func__, " <-- ", groupId); }
TEST_F(ChangeSetTestsFixtureSimple, NewGroupTestsEmpty) { string groupId = appInterface_1->createNewGroup(Empty, Empty); ASSERT_FALSE(groupId.empty()); PtrChangeSet changeSet = getCurrentGroupChangeSet(groupId, *store); ASSERT_TRUE((bool)changeSet); ASSERT_FALSE(changeSet->has_updatename()); }
TEST_F(ChangeSetTestsFixtureSimple, NewGroupTests) { string groupId = appInterface_1->createNewGroup(groupName_1, Empty); ASSERT_FALSE(groupId.empty()); PtrChangeSet changeSet = getCurrentGroupChangeSet(groupId, *store); ASSERT_TRUE((bool)changeSet); ASSERT_TRUE(changeSet->has_updatename()); ASSERT_EQ(groupName_1, changeSet->updatename().name()); // cancel and remove the changes appInterface_1->cancelGroupChangeSet(groupId); changeSet = getCurrentGroupChangeSet(groupId, *store); ASSERT_FALSE((bool)changeSet); }
static int32_t addMissingMetaData(PtrChangeSet changeSet, const string& groupId, const string& binDeviceId, const uint8_t *updateId, SQLiteStoreConv &store) { int32_t result; auto groupShared = store.listGroup(groupId, &result); auto group = groupShared.get(); if (!changeSet->has_updatename()) { string name = Utilities::getJsonString(group, GROUP_NAME, ""); changeSet->mutable_updatename()->set_name(name); result = prepareChangeSetClocks(groupId, binDeviceId, changeSet, GROUP_SET_NAME, updateId, store, false); if (result < 0) { return result; } } if (!changeSet->has_updateavatar()) { string avatar = Utilities::getJsonString(group, GROUP_AVATAR, ""); changeSet->mutable_updateavatar()->set_avatar(avatar); result = prepareChangeSetClocks(groupId, binDeviceId, changeSet, GROUP_SET_AVATAR, updateId, store, false); if (result < 0) { return result; } } if (!changeSet->has_updateburn()) { auto sec = static_cast<uint64_t>(Utilities::getJsonInt(group, GROUP_BURN_SEC, 0)); int32_t mode = Utilities::getJsonInt(group, GROUP_BURN_MODE, 0); changeSet->mutable_updateburn()->set_burn_ttl_sec(sec); changeSet->mutable_updateburn()->set_burn_mode((GroupUpdateSetBurn_BurnMode)mode); result = prepareChangeSetClocks(groupId, binDeviceId, changeSet, GROUP_SET_BURN, updateId, store, false); if (result < 0) { return result; } } return SUCCESS; }
// This function removes an remove member from the group update // Function assumes the change set is locked static bool removeRmNameFromChangeSet(PtrChangeSet& changeSet, const string &name) { if (!changeSet->has_updatermmember()) { return true; } GroupUpdateRmMember *updateRmMember = changeSet->mutable_updatermmember(); int32_t numberNames = updateRmMember->rmmember_size(); // Search for a name and remove it. Because repeated fields do not provide // a direct Remove(index) we first swap the found element with the last element // and then remove the last element. for (int32_t i = 0; i < numberNames; ++i) { if (name == updateRmMember->rmmember(i).user_id()) { updateRmMember->mutable_rmmember()->SwapElements(i, numberNames-1); updateRmMember->mutable_rmmember()->RemoveLast(); break; } } return true; }
// Function checks for duplicates and ignores them, otherwise adds the name to the group update // assumes the change set is locked static bool addRemoveNameToChangeSet(PtrChangeSet& changeSet, const string &name) { GroupUpdateRmMember *updateRmMember = changeSet->mutable_updatermmember(); int32_t numberNames = updateRmMember->rmmember_size(); // Check and silently ignore duplicate names for (int i = 0; i < numberNames; i++) { if (name == updateRmMember->rmmember(i).user_id()) { return true; } } Member *member = updateRmMember->add_rmmember(); member->set_user_id(name); return true; }
static int32_t serializeChangeSet(PtrChangeSet& changeSet, cJSON *root, string *newAttributes) { string serialized; if (!changeSet->SerializeToString(&serialized)) { return GENERIC_ERROR; } auto b64Size = static_cast<size_t>(serialized.size() * 2); unique_ptr<char[]> b64Buffer(new char[b64Size]); if (b64Encode(reinterpret_cast<const uint8_t *>(serialized.data()), serialized.size(), b64Buffer.get(), b64Size) == 0) { return GENERIC_ERROR; } cJSON_AddStringToObject(root, GROUP_CHANGE_SET, b64Buffer.get()); CharUnique out(cJSON_PrintUnformatted(root)); newAttributes->assign(out.get()); return SUCCESS; }
static int32_t prepareChangeSetClocks(const string &groupId, const string &binDeviceId, PtrChangeSet& changeSet, GroupUpdateType type, const uint8_t *updateId, SQLiteStoreConv &store, bool updateClocks = true) { LocalVClock lvc; VectorClock<string> vc; int32_t result = readLocalVectorClock(store, groupId, type, &lvc); if (result == SUCCESS) { // we may not yet have a vector clock for this group update type, thus deserialize on SUCCESS only deserializeVectorClock(lvc.vclock(), &vc); } // In a first step read the local vector clock for this (group id, update type) tuple // increment the clock for our device. // // In the second step set this new clock to the appropriate update change set. if (updateClocks) { vc.incrementNodeClock(binDeviceId); } switch (type) { case GROUP_SET_NAME: changeSet->mutable_updatename()->set_update_id(updateId, UPDATE_ID_LENGTH); serializeVectorClock(vc, changeSet->mutable_updatename()->mutable_vclock()); break; case GROUP_SET_AVATAR: changeSet->mutable_updateavatar()->set_update_id(updateId, UPDATE_ID_LENGTH); serializeVectorClock(vc, changeSet->mutable_updateavatar()->mutable_vclock()); break; case GROUP_SET_BURN: changeSet->mutable_updateburn()->set_update_id(updateId, UPDATE_ID_LENGTH); serializeVectorClock(vc, changeSet->mutable_updateburn()->mutable_vclock()); break; default: return ILLEGAL_ARGUMENT; } if (updateClocks) { // Now update and persist the local vector clock lvc.set_update_id(updateId, UPDATE_ID_LENGTH); serializeVectorClock(vc, lvc.mutable_vclock()); return storeLocalVectorClock(store, groupId, type, lvc); } return SUCCESS; }
// Function checks for duplicates and ignores them, otherwise adds the name to the group update // assumes the change set is locked static bool addAddNameToChangeSet(PtrChangeSet& changeSet, const string &name, const string &changerId) { GroupUpdateAddMember *updateAddMember = changeSet->mutable_updateaddmember(); int32_t numberNames = updateAddMember->addmember_size(); // Check and silently ignore duplicate names for (int i = 0; i < numberNames; i++) { if (name == updateAddMember->addmember(i).user_id()) { return true; } } Member *member = updateAddMember->add_addmember(); member->set_user_id(name); if (!changerId.empty()) { updateAddMember->set_user_id(changerId); } return true; }
// This is a fairly complex test case. I runs thru a complete cycle: // - add new members, set group data to create a change set // - prepare the generic part of change set, update the database // - prepare the device specific change set // // - add other new members, remove a member, change some group data // - prepare the generic part of the new change set, update the database // - prepare the device specific change set, this time it should also have data from the // previous change set because we don't ACK the data TEST_F(ChangeSetTestsFixtureMembers, CreateChangeSetTests) { // Own user is already in the change set, added while creating the group, // has index 0 in add member update PtrChangeSet changeSet = getCurrentGroupChangeSet(groupId, *store); string binDeviceId; makeBinaryDeviceId(appInterface_1->getOwnDeviceId(), &binDeviceId); // At first add more members appInterface_1->addUser(groupId, memberId_1); appInterface_1->addUser(groupId, otherMemberId_1); // At this point we have a new group with three members: ownName, memberId_1, otherMemberId_1 // in this order (alphabetically) // Set some group meta data ASSERT_EQ(SUCCESS, appInterface_1->setGroupName(groupId, &groupName_1)); ASSERT_EQ(SUCCESS, appInterface_1->setGroupBurnTime(groupId, 500, 1)); ASSERT_EQ(SUCCESS, appInterface_1->setGroupAvatar(groupId, &avatar_1)); // Prepare the change set to apply updates and create non-device specific change set, check data ASSERT_EQ(SUCCESS, appInterface_1->prepareChangeSetSend(groupId)); ASSERT_TRUE(changeSet->has_updatename()); ASSERT_EQ(1, changeSet->updatename().vclock_size()); ASSERT_EQ(1, changeSet->updatename().vclock(0).value()); ASSERT_EQ(binDeviceId, changeSet->updatename().vclock(0).device_id()); ASSERT_TRUE(changeSet->has_updateavatar()); ASSERT_EQ(1, changeSet->updateavatar().vclock_size()); ASSERT_EQ(1, changeSet->updateavatar().vclock(0).value()); ASSERT_EQ(binDeviceId, changeSet->updateavatar().vclock(0).device_id()); ASSERT_TRUE(changeSet->has_updateburn()); ASSERT_EQ(1, changeSet->updateburn().vclock_size()); ASSERT_EQ(1, changeSet->updateburn().vclock(0).value()); ASSERT_EQ(binDeviceId, changeSet->updateburn().vclock(0).device_id()); ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(3, changeSet->updateaddmember().addmember_size()); // Preparing the change set also updates the database, adding group data and members ASSERT_TRUE(store->hasGroup(groupId, nullptr)); int32_t result; shared_ptr<cJSON> group = store->listGroup(groupId, &result); ASSERT_FALSE(SQL_FAIL(result)) << store->getLastError(); ASSERT_TRUE((bool)group); cJSON *root = group.get(); ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); ASSERT_EQ(avatar_1, string(Utilities::getJsonString(root, GROUP_AVATAR, ""))); ASSERT_EQ(500, Utilities::getJsonInt(root, GROUP_BURN_SEC, -1)); ASSERT_EQ(1, Utilities::getJsonInt(root, GROUP_BURN_MODE, -1)); // List all members of a group, should return a list with size 3 and the correct data list<JsonUnique> members; result = store->getAllGroupMembers(groupId, members); ASSERT_FALSE(SQL_FAIL(result)) << store->getLastError(); ASSERT_EQ(3, members.size()); root = members.front().get(); ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); ASSERT_EQ(ownName, string(Utilities::getJsonString(root, MEMBER_ID, ""))); members.pop_front(); root = members.front().get(); ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); ASSERT_EQ(memberId_1, string(Utilities::getJsonString(root, MEMBER_ID, ""))); members.pop_front(); root = members.front().get(); ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); ASSERT_EQ(otherMemberId_1, string(Utilities::getJsonString(root, MEMBER_ID, ""))); // Now we have a prepare change set, tested, database looks good // Create the device specific change set. Because we do not have another older change set // yet, the current 'old' change does not change. There is no data to collapse. string attributes; string newAttributes; appInterface_1->createChangeSetDevice(groupId, longDevId_2, attributes, &newAttributes); ASSERT_FALSE(newAttributes.empty()); // cerr << "new attributes: " << newAttributes << endl; appInterface_1->groupUpdateSendDone(groupId); // Get the now pending change set, check if it has the expected data // Return no change set if no change set for a group id. PtrChangeSet pendingChangeSet = getPendingGroupChangeSet(groupId_1, *store); ASSERT_FALSE((bool)pendingChangeSet); pendingChangeSet = getPendingGroupChangeSet(groupId, *store); ASSERT_TRUE((bool)pendingChangeSet); ASSERT_TRUE(pendingChangeSet->has_updatename()); ASSERT_EQ(1, pendingChangeSet->updatename().vclock_size()); ASSERT_EQ(1, pendingChangeSet->updatename().vclock(0).value()); ASSERT_EQ(binDeviceId, pendingChangeSet->updatename().vclock(0).device_id()); ASSERT_TRUE(pendingChangeSet->has_updateavatar()); ASSERT_EQ(1, pendingChangeSet->updateavatar().vclock_size()); ASSERT_EQ(1, pendingChangeSet->updateavatar().vclock(0).value()); ASSERT_EQ(binDeviceId, pendingChangeSet->updateavatar().vclock(0).device_id()); ASSERT_TRUE(pendingChangeSet->has_updateburn()); ASSERT_EQ(1, pendingChangeSet->updateburn().vclock_size()); ASSERT_EQ(1, pendingChangeSet->updateburn().vclock(0).value()); ASSERT_EQ(binDeviceId, pendingChangeSet->updateburn().vclock(0).device_id()); ASSERT_TRUE(pendingChangeSet->has_updateaddmember()); ASSERT_EQ(3, pendingChangeSet->updateaddmember().addmember_size()); // ******************************************** // OK, ready for the second part // ******************************************** // At first one more member appInterface_1->addUser(groupId, otherMemberId_2); changeSet = getCurrentGroupChangeSet(groupId, *store); ASSERT_TRUE((bool)changeSet); ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(1, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(otherMemberId_2, changeSet->updateaddmember().addmember(0).user_id()); // Now remove a known member (not myself) appInterface_1->removeUser(groupId, otherMemberId_1); ASSERT_TRUE(changeSet->has_updatermmember()); ASSERT_EQ(1, changeSet->updatermmember().rmmember_size()); ASSERT_EQ(otherMemberId_1, changeSet->updatermmember().rmmember(0).user_id()); // At this point we have a new group with three members: ownName, memberId_1, otherMemberId_1, otherMemberId_2 // in this order (alphabetically) // // Set some group meta data - don't set, done automatically by the prepare change set function // in case we have an add member // ASSERT_EQ(SUCCESS, appInterface_1->setGroupName(groupId, &groupName_2)); // ASSERT_EQ(SUCCESS, appInterface_1->setGroupBurnTime(groupId, 600, 1)); // ASSERT_EQ(SUCCESS, appInterface_1->setGroupAvatar(groupId, &avatar_2)); // // // Prepare the change set to apply updates and create non-device specific change set, check data ASSERT_EQ(SUCCESS, appInterface_1->prepareChangeSetSend(groupId)); // // // The vector clock now must have a value of 2 // ASSERT_TRUE(changeSet->has_updatename()); // ASSERT_EQ(1, changeSet->updatename().vclock_size()); // ASSERT_EQ(2, changeSet->updatename().vclock(0).value()); // ASSERT_EQ(binDeviceId, changeSet->updatename().vclock(0).device_id()); // // ASSERT_TRUE(changeSet->has_updateavatar()); // ASSERT_EQ(1, changeSet->updateavatar().vclock_size()); // ASSERT_EQ(2, changeSet->updateavatar().vclock(0).value()); // ASSERT_EQ(binDeviceId, changeSet->updateavatar().vclock(0).device_id()); // // ASSERT_TRUE(changeSet->has_updateburn()); // ASSERT_EQ(1, changeSet->updateburn().vclock_size()); // ASSERT_EQ(2, changeSet->updateburn().vclock(0).value()); // ASSERT_EQ(binDeviceId, changeSet->updateburn().vclock(0).device_id()); // // ASSERT_TRUE(changeSet->has_updateaddmember()); // ASSERT_EQ(1, changeSet->updateaddmember().addmember_size()); // // ASSERT_TRUE(changeSet->has_updateaddmember()); // ASSERT_EQ(1, changeSet->updatermmember().rmmember_size()); // // // Preparing the change set also updates the database, adding/removing group data and members // ASSERT_TRUE(store->hasGroup(groupId, nullptr)); // // group = store->listGroup(groupId, &result); // ASSERT_FALSE(SQL_FAIL(result)) << store->getLastError(); // ASSERT_TRUE((bool)group); // // root = group.get(); // ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); // ASSERT_EQ(avatar_2, string(Utilities::getJsonString(root, GROUP_AVATAR, ""))); // ASSERT_EQ(600, Utilities::getJsonInt(root, GROUP_BURN_SEC, -1)); // ASSERT_EQ(1, Utilities::getJsonInt(root, GROUP_BURN_MODE, -1)); // List all members of a group, should return a list with size 3 and the correct data members.clear(); result = store->getAllGroupMembers(groupId, members); ASSERT_FALSE(SQL_FAIL(result)) << store->getLastError(); ASSERT_EQ(3, members.size()); root = members.front().get(); ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); ASSERT_EQ(ownName, string(Utilities::getJsonString(root, MEMBER_ID, ""))); members.pop_front(); root = members.front().get(); ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); ASSERT_EQ(memberId_1, string(Utilities::getJsonString(root, MEMBER_ID, ""))); members.pop_front(); root = members.front().get(); ASSERT_EQ(groupId, string(Utilities::getJsonString(root, GROUP_ID, ""))); ASSERT_EQ(otherMemberId_2, string(Utilities::getJsonString(root, MEMBER_ID, ""))); appInterface_1->createChangeSetDevice(groupId, longDevId_2, attributes, &newAttributes); ASSERT_FALSE(newAttributes.empty()); // cerr << "new attributes: " << newAttributes << endl; // Finish creation of change set, manage pending change sets appInterface_1->groupUpdateSendDone(groupId); // This clears the cache, simulating a fresh start ASSERT_TRUE(removeGroupFromPendingChangeSet(groupId)); // Get the pending change set, check if it has the expected data pendingChangeSet = getPendingGroupChangeSet(groupId, *store); ASSERT_TRUE((bool)pendingChangeSet); // Because we haven't changed the group's metadata the clocks still have value 1 ASSERT_TRUE(pendingChangeSet->has_updatename()); ASSERT_EQ(1, pendingChangeSet->updatename().vclock_size()); ASSERT_EQ(1, pendingChangeSet->updatename().vclock(0).value()); ASSERT_EQ(binDeviceId, pendingChangeSet->updatename().vclock(0).device_id()); ASSERT_TRUE(pendingChangeSet->has_updateavatar()); ASSERT_EQ(1, pendingChangeSet->updateavatar().vclock_size()); ASSERT_EQ(1, pendingChangeSet->updateavatar().vclock(0).value()); ASSERT_EQ(binDeviceId, pendingChangeSet->updateavatar().vclock(0).device_id()); ASSERT_TRUE(pendingChangeSet->has_updateburn()); ASSERT_EQ(1, pendingChangeSet->updateburn().vclock_size()); ASSERT_EQ(1, pendingChangeSet->updateburn().vclock(0).value()); ASSERT_EQ(binDeviceId, pendingChangeSet->updateburn().vclock(0).device_id()); ASSERT_TRUE(pendingChangeSet->has_updateaddmember()); ASSERT_EQ(4, pendingChangeSet->updateaddmember().addmember_size()); ASSERT_TRUE(pendingChangeSet->has_updatermmember()); ASSERT_EQ(1, pendingChangeSet->updatermmember().rmmember_size()); }
TEST_F(ChangeSetTestsFixtureMembers, AddRemoveMemberTests) { // Own user is already in the change set, added while creating the group, has index 0 PtrChangeSet changeSet = getCurrentGroupChangeSet(groupId, *store); // Own user is already in the change set, added while creating the group, has index 0 // At first add a member, check data ASSERT_EQ(SUCCESS, appInterface_1->addUser(groupId, memberId_1)); ASSERT_EQ(2, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(memberId_1, changeSet->updateaddmember().addmember(1).user_id()); // add a second member ASSERT_EQ(SUCCESS, appInterface_1->addUser(groupId, otherMemberId_1)); ASSERT_EQ(3, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(otherMemberId_1, changeSet->updateaddmember().addmember(2).user_id()); // Now remove the first added member // expect that it is in remove update, and removed from add update thus it is down to 2 ASSERT_EQ(SUCCESS, appInterface_1->removeUser(groupId, memberId_1)); ASSERT_TRUE(changeSet->has_updatermmember()); ASSERT_EQ(1, changeSet->updatermmember().rmmember_size()); ASSERT_EQ(memberId_1, changeSet->updatermmember().rmmember(0).user_id()); // check the add update data ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(2, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(otherMemberId_1, changeSet->updateaddmember().addmember(1).user_id()); // Now remove another member ASSERT_EQ(SUCCESS, appInterface_1->removeUser(groupId, otherMemberId_2)); ASSERT_TRUE(changeSet->has_updatermmember()); ASSERT_EQ(2, changeSet->updatermmember().rmmember_size()); ASSERT_EQ(otherMemberId_2, changeSet->updatermmember().rmmember(1).user_id()); // now re-add the first member. It should be re-added to add update, removed from // remove update ASSERT_EQ(SUCCESS, appInterface_1->addUser(groupId, memberId_1)); ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(3, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(memberId_1, changeSet->updateaddmember().addmember(2).user_id()); // remove update down to one ASSERT_TRUE(changeSet->has_updatermmember()); ASSERT_EQ(1, changeSet->updatermmember().rmmember_size()); ASSERT_EQ(otherMemberId_2, changeSet->updatermmember().rmmember(0).user_id()); }
TEST_F(ChangeSetTestsFixtureMembers, RemoveMemberTests) { PtrChangeSet changeSet = getCurrentGroupChangeSet(groupId, *store); ASSERT_TRUE((bool)changeSet); ASSERT_FALSE(changeSet->has_updatermmember()); ASSERT_EQ(DATA_MISSING, appInterface_1->removeUser(Empty, Empty)); ASSERT_EQ(DATA_MISSING, appInterface_1->removeUser(groupId, Empty)); ASSERT_EQ(DATA_MISSING, appInterface_1->removeUser(Empty, otherMemberId_1)); ASSERT_EQ(SUCCESS, appInterface_1->removeUser(groupId, otherMemberId_1)); ASSERT_TRUE(changeSet->has_updatermmember()); ASSERT_EQ(1, changeSet->updatermmember().rmmember_size()); ASSERT_EQ(otherMemberId_1, changeSet->updatermmember().rmmember(0).user_id()); ASSERT_EQ(SUCCESS, appInterface_1->removeUser(groupId, otherMemberId_2)); ASSERT_TRUE(changeSet->has_updatermmember()); ASSERT_EQ(2, changeSet->updatermmember().rmmember_size()); ASSERT_EQ(otherMemberId_2, changeSet->updatermmember().rmmember(1).user_id()); // removing a name a second time, ignore silently, no changes in change set ASSERT_EQ(SUCCESS, appInterface_1->removeUser(groupId, otherMemberId_2)); ASSERT_TRUE(changeSet->has_updatermmember()); ASSERT_EQ(2, changeSet->updatermmember().rmmember_size()); ASSERT_EQ(otherMemberId_2, changeSet->updatermmember().rmmember(1).user_id()); }
TEST_F(ChangeSetTestsFixtureMembers, AddMemberTests) { // Test for illegal parameters ASSERT_EQ(DATA_MISSING, appInterface_1->addUser(Empty, Empty)); ASSERT_EQ(DATA_MISSING, appInterface_1->addUser(groupId, Empty)); ASSERT_EQ(DATA_MISSING, appInterface_1->addUser(Empty, memberId_1)); // Invite a user (addUser), own user is already in the change set, added while creating the group, has index 0 PtrChangeSet changeSet = getCurrentGroupChangeSet(groupId, *store); ASSERT_TRUE((bool)changeSet); ASSERT_EQ(SUCCESS, appInterface_1->addUser(groupId, memberId_1)); ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(2, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(memberId_1, changeSet->updateaddmember().addmember(1).user_id()); ASSERT_EQ(SUCCESS, appInterface_1->addUser(groupId, otherMemberId_1)); ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(3, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(otherMemberId_1, changeSet->updateaddmember().addmember(2).user_id()); ASSERT_EQ(SUCCESS, appInterface_1->addUser(groupId, otherMemberId_2)); ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(4, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(otherMemberId_2, changeSet->updateaddmember().addmember(3).user_id()); // adding a name a second time, ignore silently, no changes in change set ASSERT_EQ(SUCCESS, appInterface_1->addUser(groupId, otherMemberId_2)); ASSERT_TRUE(changeSet->has_updateaddmember()); ASSERT_EQ(4, changeSet->updateaddmember().addmember_size()); ASSERT_EQ(otherMemberId_2, changeSet->updateaddmember().addmember(3).user_id()); }
TEST_F(ChangeSetTestsFixtureSimple, ExistingGroupTests) { int32_t result = store->insertGroup(groupId_1, groupName_1, appInterface_1->getOwnUser(), Empty, 0); ASSERT_FALSE(SQL_FAIL(result)); PtrChangeSet changeSet = getCurrentGroupChangeSet(groupId_1, *store); ASSERT_TRUE((bool)changeSet); ASSERT_EQ(SUCCESS, appInterface_1->setGroupName(groupId_1, &groupName_1)); ASSERT_TRUE(changeSet->has_updatename()); ASSERT_EQ(groupName_1, changeSet->updatename().name()); ASSERT_EQ(SUCCESS, appInterface_1->setGroupName(groupId_1, &groupName_2)); ASSERT_TRUE(changeSet->has_updatename()); ASSERT_EQ(groupName_2, changeSet->updatename().name()); // Empty group name remove the name update ASSERT_EQ(SUCCESS, appInterface_1->setGroupName(groupId_1, nullptr)); ASSERT_FALSE(changeSet->has_updatename()); // Use some data to set avatar info ASSERT_EQ(SUCCESS, appInterface_1->setGroupAvatar(groupId_1, &groupName_2)); ASSERT_TRUE(changeSet->has_updateavatar()); ASSERT_EQ(groupName_2, changeSet->updateavatar().avatar()); // change the data ASSERT_EQ(SUCCESS, appInterface_1->setGroupAvatar(groupId_1, &groupName_1)); ASSERT_TRUE(changeSet->has_updateavatar()); ASSERT_EQ(groupName_1, changeSet->updateavatar().avatar()); // Empty avatar info ASSERT_EQ(SUCCESS, appInterface_1->setGroupAvatar(groupId_1, nullptr)); ASSERT_FALSE(changeSet->has_updateavatar()); // Burn time updates ASSERT_EQ(SUCCESS, appInterface_1->setGroupBurnTime(groupId_1, 500, 1)); ASSERT_TRUE(changeSet->has_updateburn()); ASSERT_EQ(500, changeSet->updateburn().burn_ttl_sec()); ASSERT_EQ(1, changeSet->updateburn().burn_mode()); ASSERT_EQ(SUCCESS, appInterface_1->setGroupBurnTime(groupId_1, 1000, 1)); ASSERT_TRUE(changeSet->has_updateburn()); ASSERT_EQ(1000, changeSet->updateburn().burn_ttl_sec()); ASSERT_EQ(1, changeSet->updateburn().burn_mode()); ASSERT_EQ(ILLEGAL_ARGUMENT, appInterface_1->setGroupBurnTime(groupId_1, 500, 0)); }
int32_t AppInterfaceImpl::createChangeSetDevice(const string &groupId, const string &deviceId, const string &attributes, string *newAttributes) { LOGGER(DEBUGGING, __func__, " -->"); if (groupId.empty() || deviceId.empty()) { return DATA_MISSING; } // The attributes string has a serialized change set already, don't process and add the current change set // This may happen if ZINA sends an ACK set back to a device JsonUnique sharedRoot(!attributes.empty() ? cJSON_Parse(attributes.c_str()) : cJSON_CreateObject()); cJSON* root = sharedRoot.get(); if (Utilities::hasJsonKey(root, GROUP_CHANGE_SET)) { return SUCCESS; } unique_lock<mutex> lck(currentChangeSetLock); PtrChangeSet changeSet; string binDeviceId; makeBinaryDeviceId(deviceId, &binDeviceId); if (updateInProgress) { changeSet = getGroupChangeSet(groupId); if (!changeSet) { return GROUP_UPDATE_INCONSISTENT; } // Do we have any updates? If not, remove from current change set map and just return if (!changeSet->has_updatename() && !changeSet->has_updateavatar() && !changeSet->has_updateburn() && !changeSet->has_updateaddmember() && !changeSet->has_updatermmember() && !changeSet->has_burnmessage()) { removeGroupFromChangeSet(groupId); return SUCCESS; } } else { changeSet = getPendingGroupChangeSet(groupId, *store_); if (!changeSet) { return SUCCESS; } // Resend a change set only if a device has pending ACKs for this group. return store_->hasWaitAckGroupDevice(groupId, binDeviceId, nullptr) ? serializeChangeSet(changeSet, root, newAttributes) : SUCCESS; } string updateIdString(reinterpret_cast<const char*>(updateIdGlobal), UPDATE_ID_LENGTH); auto oldChangeSet = getPendingGroupChangeSet(groupId, *store_); if (oldChangeSet) { // Collapse older add/remove member group updates into the current one. // If the old change set has add new member _and_ the device has not ACK'd it, copy // the old member into current change set. // Thus if at least one device has not ACK'ed the previous changes then the new changes // get these changes as well. If all devices ACK'ed the previous changes, then the new // change set will not get the old changes. if (oldChangeSet->has_updateaddmember() && store_->hasWaitAck(groupId, binDeviceId, oldChangeSet->updateaddmember().update_id(), GROUP_ADD_MEMBER, nullptr)) { // Use the own addAddName function: skips duplicate names, checks the remove member data const int32_t size = oldChangeSet->updateaddmember().addmember_size(); for (int i = 0; i < size; i++) { addAddNameToChangeSet(changeSet, oldChangeSet->updateaddmember().addmember(i).user_id(), ""); } } if (oldChangeSet->has_updatermmember() && store_->hasWaitAck(groupId, binDeviceId, oldChangeSet->updatermmember().update_id(), GROUP_REMOVE_MEMBER, nullptr)) { // Use the own addRemoveName function: skips duplicate names, checks the add member data const int32_t size = oldChangeSet->updatermmember().rmmember_size(); for (int i = 0; i < size; i++) { addRemoveNameToChangeSet(changeSet, oldChangeSet->updatermmember().rmmember(i).user_id()); } } } // We may now have an add member update: may have added names from old change set, thus add // meta data if necessary. if (changeSet->has_updateaddmember()) { addMissingMetaData(changeSet, groupId, binDeviceId, updateIdGlobal, *store_); } int32_t result = serializeChangeSet(changeSet, root, newAttributes); if (result != SUCCESS) { errorCode_ = result; return result; } // Add WaitForAck records for the enw updates, remove old WaitForAck records for // attributes and data that's collapsed into the new change set. // Because we send a new group update we can remove older group updates from wait-for-ack. // The recent update overwrites older updates. ZINA ignores ACKs for the older updates. // Then store a new wait-for-ack record with the current update id. if (changeSet->has_updatename()) { store_->removeWaitAckWithType(groupId, binDeviceId, GROUP_SET_NAME); store_->insertWaitAck(groupId, binDeviceId, updateIdString, GROUP_SET_NAME); } if (changeSet->has_updateavatar()) { store_->removeWaitAckWithType(groupId, binDeviceId, GROUP_SET_AVATAR); store_->insertWaitAck(groupId, binDeviceId, updateIdString, GROUP_SET_AVATAR); } if (changeSet->has_updateburn()) { store_->removeWaitAckWithType(groupId, binDeviceId, GROUP_SET_BURN); store_->insertWaitAck(groupId, binDeviceId, updateIdString, GROUP_SET_BURN); } // Wait for ACK for each message burn, we don't collapse message burn changes because // this does not overwrite older burn message commands if (changeSet->has_burnmessage()) { store_->insertWaitAck(groupId, binDeviceId, updateIdString, GROUP_BURN_MESSSAGE); } // Add wait-for-ack records for add/remove group updates, remove old records. // Names are collapsed into new change set. if (changeSet->has_updateaddmember()) { store_->removeWaitAckWithType(groupId, binDeviceId, GROUP_ADD_MEMBER); store_->insertWaitAck(groupId, binDeviceId, updateIdString, GROUP_ADD_MEMBER); } if (changeSet->has_updatermmember()) { store_->removeWaitAckWithType(groupId, binDeviceId, GROUP_REMOVE_MEMBER); store_->insertWaitAck(groupId, binDeviceId, updateIdString, GROUP_REMOVE_MEMBER); } LOGGER(DEBUGGING, __func__, " <-- "); return result; }