mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 04:21:51 +00:00
containedIn() only checked that the other index declared the same field keys, never that they held the same values, contradicting its documented contract of containment "with the same value". Dht::query() uses containedIn() to deduplicate projected results. Because every result of a given query shares the same projection schema, any two results compared equal: the first one suppressed all the later ones, and the "lesser" branch even erased the already reported result. A query selecting a field therefore reported a single value regardless of how many matched. Compare the FieldValue as well, and add a regression covering distinct values, equal indexes and a narrower projection contained in a wider one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
217 lines
6.5 KiB
C++
217 lines
6.5 KiB
C++
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "test_value.h"
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
// opendht
|
|
#include "opendht/value.h"
|
|
#include "opendht/crypto.h"
|
|
#include <msgpack.hpp>
|
|
|
|
namespace test {
|
|
CPPUNIT_TEST_SUITE_REGISTRATION(ValueTester);
|
|
|
|
void
|
|
ValueTester::setUp()
|
|
{}
|
|
|
|
void
|
|
ValueTester::testConstructors()
|
|
{
|
|
std::string the_data {"42 cats"};
|
|
dht::Value the_dht_value {(const uint8_t*) the_data.data(), the_data.size()};
|
|
std::string from_value {the_dht_value.data.begin(), the_dht_value.data.end()};
|
|
CPPUNIT_ASSERT_EQUAL(the_data, from_value);
|
|
}
|
|
|
|
void
|
|
ValueTester::testFilter()
|
|
{
|
|
dht::Value::Filter defaultFiler {};
|
|
|
|
auto isPairSize = dht::Value::Filter([](const dht::Value& v) { return v.data.size() % 2 == 0; });
|
|
|
|
auto isUserTypeTest = dht::Value::Filter([](const dht::Value& v) { return v.user_type == "test"; });
|
|
|
|
std::string data1 {"42 cats"};
|
|
dht::Value value1 {(const uint8_t*) data1.data(), data1.size()};
|
|
value1.user_type = "test";
|
|
|
|
std::string data2 {"420 cats"};
|
|
dht::Value value2 {(const uint8_t*) data2.data(), data2.size()};
|
|
dht::Value value3 {(const uint8_t*) data2.data(), data2.size()};
|
|
value3.user_type = "test";
|
|
|
|
CPPUNIT_ASSERT(!isPairSize(value1));
|
|
CPPUNIT_ASSERT(isUserTypeTest(value1));
|
|
|
|
auto isBoth = dht::Value::Filter::chain(isPairSize, isUserTypeTest);
|
|
auto isUserTypeTest2 = dht::Value::Filter::chain(defaultFiler, isUserTypeTest);
|
|
|
|
CPPUNIT_ASSERT(isUserTypeTest2(value1));
|
|
CPPUNIT_ASSERT(!isUserTypeTest2(value2));
|
|
CPPUNIT_ASSERT(!isBoth(value1));
|
|
CPPUNIT_ASSERT(!isBoth(value2));
|
|
CPPUNIT_ASSERT(isBoth(value3));
|
|
}
|
|
|
|
void
|
|
ValueTester::testFieldValueIndexContainedIn()
|
|
{
|
|
dht::Value first {(const uint8_t*) "a", 1};
|
|
first.id = 1;
|
|
dht::Value second {(const uint8_t*) "b", 1};
|
|
second.id = 2;
|
|
|
|
const dht::Select select {dht::Select {}.field(dht::Value::Field::Id)};
|
|
dht::FieldValueIndex first_index {first, select};
|
|
dht::FieldValueIndex second_index {second, select};
|
|
|
|
// Same projected field, different values: neither index contains the other.
|
|
CPPUNIT_ASSERT(not first_index.containedIn(second_index));
|
|
CPPUNIT_ASSERT(not second_index.containedIn(first_index));
|
|
|
|
// An index is always contained in an equal one.
|
|
dht::FieldValueIndex first_copy {first, select};
|
|
CPPUNIT_ASSERT(first_index.containedIn(first_copy));
|
|
CPPUNIT_ASSERT(first_copy.containedIn(first_index));
|
|
|
|
// A narrower projection with a matching value stays contained in a wider one.
|
|
const dht::Select wider {dht::Select {}.field(dht::Value::Field::Id).field(dht::Value::Field::ValueType)};
|
|
dht::FieldValueIndex first_wide {first, wider};
|
|
CPPUNIT_ASSERT(first_index.containedIn(first_wide));
|
|
CPPUNIT_ASSERT(not second_index.containedIn(first_wide));
|
|
}
|
|
|
|
void
|
|
ValueTester::tearDown()
|
|
{}
|
|
|
|
void
|
|
ValueTester::testSequenceFilterMsgpackRoundTrip()
|
|
{
|
|
for (uint64_t sequence : {0u, 1u, 65535u}) {
|
|
dht::FieldValue original(dht::Value::Field::SeqNum, sequence);
|
|
msgpack::sbuffer buffer;
|
|
msgpack::pack(buffer, original);
|
|
|
|
msgpack::unpacked message;
|
|
msgpack::unpack(message, buffer.data(), buffer.size());
|
|
dht::FieldValue restored;
|
|
restored.msgpack_unpack(message.get());
|
|
CPPUNIT_ASSERT(restored.getField() == dht::Value::Field::SeqNum);
|
|
CPPUNIT_ASSERT_EQUAL(sequence, restored.getInt());
|
|
|
|
dht::Value value;
|
|
value.seq = sequence;
|
|
CPPUNIT_ASSERT(restored.getLocalFilter()(value));
|
|
value.seq = sequence == 0 ? 1 : 0;
|
|
CPPUNIT_ASSERT(!restored.getLocalFilter()(value));
|
|
}
|
|
}
|
|
|
|
void
|
|
ValueTester::testPushTypeMsgpackRoundTrip()
|
|
{
|
|
dht::Value original {(const uint8_t*) "hello", 5};
|
|
original.id = 42;
|
|
original.priority = 3;
|
|
original.pushType = "audioCall";
|
|
|
|
msgpack::sbuffer buffer;
|
|
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
|
original.msgpack_pack(pk);
|
|
|
|
msgpack::unpacked msg;
|
|
msgpack::unpack(msg, buffer.data(), buffer.size());
|
|
dht::Value restored(msg.get());
|
|
|
|
CPPUNIT_ASSERT_EQUAL(original.id, restored.id);
|
|
CPPUNIT_ASSERT_EQUAL(original.priority, restored.priority);
|
|
CPPUNIT_ASSERT_EQUAL(original.pushType, restored.pushType);
|
|
}
|
|
|
|
void
|
|
ValueTester::testPushTypeAbsentAfterUnpack()
|
|
{
|
|
dht::Value withPt {(const uint8_t*) "data", 4};
|
|
withPt.id = 1;
|
|
withPt.pushType = "videoCall";
|
|
withPt.priority = 2;
|
|
|
|
msgpack::sbuffer buf1;
|
|
msgpack::packer<msgpack::sbuffer> pk1(&buf1);
|
|
withPt.msgpack_pack(pk1);
|
|
|
|
msgpack::unpacked msg1;
|
|
msgpack::unpack(msg1, buf1.data(), buf1.size());
|
|
|
|
dht::Value reused;
|
|
reused.msgpack_unpack(msg1.get());
|
|
CPPUNIT_ASSERT_EQUAL(std::string("videoCall"), reused.pushType);
|
|
CPPUNIT_ASSERT_EQUAL(2u, reused.priority);
|
|
|
|
dht::Value plain {(const uint8_t*) "data", 4};
|
|
plain.id = 2;
|
|
|
|
msgpack::sbuffer buf2;
|
|
msgpack::packer<msgpack::sbuffer> pk2(&buf2);
|
|
plain.msgpack_pack(pk2);
|
|
|
|
msgpack::unpacked msg2;
|
|
msgpack::unpack(msg2, buf2.data(), buf2.size());
|
|
|
|
reused.msgpack_unpack(msg2.get());
|
|
CPPUNIT_ASSERT_EQUAL(std::string(), reused.pushType);
|
|
CPPUNIT_ASSERT_EQUAL(0u, reused.priority);
|
|
}
|
|
|
|
void
|
|
ValueTester::testPushTypePreservedAfterEncrypt()
|
|
{
|
|
auto key = dht::crypto::PrivateKey::generate();
|
|
const auto& pubkey = key.getPublicKey();
|
|
|
|
dht::Value original {(const uint8_t*) "secret", 6};
|
|
original.id = 99;
|
|
original.priority = 1;
|
|
original.pushType = "audioCall";
|
|
|
|
auto encrypted = original.encrypt(key, pubkey);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(original.pushType, encrypted.pushType);
|
|
CPPUNIT_ASSERT_EQUAL(original.priority, encrypted.priority);
|
|
CPPUNIT_ASSERT_EQUAL(original.id, encrypted.id);
|
|
CPPUNIT_ASSERT(encrypted.isEncrypted());
|
|
}
|
|
|
|
void
|
|
ValueTester::testPushTypeJsonRoundTrip()
|
|
{
|
|
#ifdef OPENDHT_JSONCPP
|
|
dht::Value original {(const uint8_t*) "hello", 5};
|
|
original.id = 42;
|
|
original.priority = 1;
|
|
original.pushType = "videoCall";
|
|
|
|
dht::Value restored(original.toJson());
|
|
|
|
CPPUNIT_ASSERT_EQUAL(original.id, restored.id);
|
|
CPPUNIT_ASSERT_EQUAL(original.priority, restored.priority);
|
|
CPPUNIT_ASSERT_EQUAL(original.pushType, restored.pushType);
|
|
|
|
// No pushType: the field must stay absent from the JSON and empty after parse.
|
|
dht::Value plain {(const uint8_t*) "data", 4};
|
|
plain.id = 7;
|
|
auto json = plain.toJson();
|
|
CPPUNIT_ASSERT(!json.isMember("pt"));
|
|
dht::Value plainRestored(json);
|
|
CPPUNIT_ASSERT_EQUAL(std::string(), plainRestored.pushType);
|
|
#endif
|
|
}
|
|
|
|
} // namespace test
|