// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #include #include #include #include "bolt_common.hpp" #include "bolt_testdata.hpp" #include "communication/bolt/v1/codes.hpp" #include "communication/bolt/v1/encoder/encoder.hpp" #include "disk_test_utils.hpp" #include "glue/communication.hpp" #include "storage/v2/disk/storage.hpp" #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" #include "utils/temporal.hpp" using memgraph::communication::bolt::Value; /** * TODO (mferencevic): document */ inline constexpr const int SIZE = 131072; uint8_t data[SIZE]; uint64_t GetBigEndianInt(std::vector &v, uint8_t len, uint8_t offset = 1) { uint64_t ret = 0; v.erase(v.begin(), v.begin() + offset); for (int i = 0; i < len; ++i) { ret <<= 8; ret += v[i]; } v.erase(v.begin(), v.begin() + len); return ret; } void CheckTypeSize(std::vector &v, int typ, uint64_t size) { uint64_t len; if ((v[0] & 0xF0) == type_tiny_magic[typ]) { len = v[0] & 0x0F; v.erase(v.begin(), v.begin() + 1); } else if (v[0] == type_8_magic[typ]) { len = GetBigEndianInt(v, 1); } else if (v[0] == type_16_magic[typ]) { len = GetBigEndianInt(v, 2); } else if (v[0] == type_32_magic[typ]) { len = GetBigEndianInt(v, 4); } else { FAIL() << "Got wrong marker!"; } ASSERT_EQ(len, size); } void CheckInt(std::vector &output, int64_t value) { TestOutputStream output_stream; TestBuffer encoder_buffer(output_stream); memgraph::communication::bolt::BaseEncoder bolt_encoder(encoder_buffer); std::vector &encoded = output_stream.output; bolt_encoder.WriteInt(value); CheckOutput(output, encoded.data(), encoded.size(), false); } void CheckRecordHeader(std::vector &v, uint64_t size) { CheckOutput(v, (const uint8_t *)"\xB1\x71", 2, false); CheckTypeSize(v, LIST, size); } TestOutputStream output_stream; TestBuffer encoder_buffer(output_stream); memgraph::communication::bolt::Encoder bolt_encoder(encoder_buffer); std::vector &output = output_stream.output; struct BoltEncoder : ::testing::Test { // In newer gtest library (1.8.1+) this is changed to SetUpTestSuite static void SetUpTestCase() { InitializeData(data, SIZE); } }; TEST_F(BoltEncoder, NullAndBool) { output.clear(); std::vector vals; vals.push_back(Value()); vals.push_back(Value(true)); vals.push_back(Value(false)); bolt_encoder.MessageRecord(vals); CheckRecordHeader(output, 3); CheckOutput(output, (const uint8_t *)"\xC0\xC3\xC2", 3); } TEST_F(BoltEncoder, Int) { int N = 28; output.clear(); std::vector vals; for (int i = 0; i < N; ++i) vals.push_back(Value(int_decoded[i])); bolt_encoder.MessageRecord(vals); CheckRecordHeader(output, N); for (int i = 0; i < N; ++i) CheckOutput(output, int_encoded[i], int_encoded_len[i], false); CheckOutput(output, nullptr, 0); } TEST_F(BoltEncoder, Double) { int N = 4; output.clear(); std::vector vals; for (int i = 0; i < N; ++i) vals.push_back(Value(double_decoded[i])); bolt_encoder.MessageRecord(vals); CheckRecordHeader(output, N); for (int i = 0; i < N; ++i) CheckOutput(output, double_encoded[i], 9, false); CheckOutput(output, nullptr, 0); } TEST_F(BoltEncoder, String) { output.clear(); std::vector vals; for (uint64_t i = 0; i < sizes_num; ++i) vals.push_back(Value(std::string((const char *)data, sizes[i]))); bolt_encoder.MessageRecord(vals); CheckRecordHeader(output, vals.size()); for (uint64_t i = 0; i < sizes_num; ++i) { CheckTypeSize(output, STRING, sizes[i]); CheckOutput(output, data, sizes[i], false); } CheckOutput(output, nullptr, 0); } TEST_F(BoltEncoder, List) { output.clear(); std::vector vals; for (uint64_t i = 0; i < sizes_num; ++i) { std::vector val; for (uint64_t j = 0; j < sizes[i]; ++j) val.push_back(Value(std::string((const char *)&data[j], 1))); vals.push_back(Value(val)); } bolt_encoder.MessageRecord(vals); CheckRecordHeader(output, vals.size()); for (uint64_t i = 0; i < sizes_num; ++i) { CheckTypeSize(output, LIST, sizes[i]); for (uint64_t j = 0; j < sizes[i]; ++j) { CheckTypeSize(output, STRING, 1); CheckOutput(output, &data[j], 1, false); } } CheckOutput(output, nullptr, 0); } TEST_F(BoltEncoder, Map) { output.clear(); std::vector vals; uint8_t buff[10]; for (int i = 0; i < sizes_num; ++i) { std::map val; for (int j = 0; j < sizes[i]; ++j) { sprintf((char *)buff, "%05X", j); std::string tmp((char *)buff, 5); val.insert(std::make_pair(tmp, Value(tmp))); } vals.push_back(Value(val)); } bolt_encoder.MessageRecord(vals); CheckRecordHeader(output, vals.size()); for (int i = 0; i < sizes_num; ++i) { CheckTypeSize(output, MAP, sizes[i]); for (int j = 0; j < sizes[i]; ++j) { sprintf((char *)buff, "%05X", j); CheckTypeSize(output, STRING, 5); CheckOutput(output, buff, 5, false); CheckTypeSize(output, STRING, 5); CheckOutput(output, buff, 5, false); } } CheckOutput(output, nullptr, 0); } void TestVertexAndEdgeWithDifferentStorages(std::unique_ptr &&db) { output.clear(); // create vertex auto dba = db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); auto va1 = dba->CreateVertex(); auto va2 = dba->CreateVertex(); auto l1 = dba->NameToLabel("label1"); auto l2 = dba->NameToLabel("label2"); ASSERT_TRUE(va1.AddLabel(l1).HasValue()); ASSERT_TRUE(va1.AddLabel(l2).HasValue()); auto p1 = dba->NameToProperty("prop1"); auto p2 = dba->NameToProperty("prop2"); memgraph::storage::PropertyValue pv1(12), pv2(200); ASSERT_TRUE(va1.SetProperty(p1, pv1).HasValue()); ASSERT_TRUE(va1.SetProperty(p2, pv2).HasValue()); // create edge auto et = dba->NameToEdgeType("edgetype"); auto ea = dba->CreateEdge(&va1, &va2, et).GetValue(); auto p3 = dba->NameToProperty("prop3"); auto p4 = dba->NameToProperty("prop4"); memgraph::storage::PropertyValue pv3(42), pv4(1234); ASSERT_TRUE(ea.SetProperty(p3, pv3).HasValue()); ASSERT_TRUE(ea.SetProperty(p4, pv4).HasValue()); // check everything std::vector vals; vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va1)), db.get(), memgraph::storage::View::NEW)); vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va2)), db.get(), memgraph::storage::View::NEW)); vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::EdgeAccessor(ea)), db.get(), memgraph::storage::View::NEW)); bolt_encoder.MessageRecord(vals); // The vertexedge_encoded testdata has hardcoded zeros for IDs, // and Memgraph now encodes IDs so we need to check the output // part by part. CheckOutput(output, vertexedge_encoded, 5, false); CheckInt(output, va1.Gid().AsInt()); CheckOutput(output, vertexedge_encoded + 6, 34, false); CheckInt(output, va2.Gid().AsInt()); CheckOutput(output, vertexedge_encoded + 41, 4, false); CheckInt(output, ea.Gid().AsInt()); CheckInt(output, va1.Gid().AsInt()); CheckInt(output, va2.Gid().AsInt()); CheckOutput(output, vertexedge_encoded + 48, 26); } TEST_F(BoltEncoder, VertexAndEdgeInMemoryStorage) { std::unique_ptr db{new memgraph::storage::InMemoryStorage()}; TestVertexAndEdgeWithDifferentStorages(std::move(db)); } TEST_F(BoltEncoder, VertexAndEdgeOnDiskStorage) { const std::string testSuite = "bolt_encoder"; memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr db{new memgraph::storage::DiskStorage(config)}; TestVertexAndEdgeWithDifferentStorages(std::move(db)); disk_test_utils::RemoveRocksDbDirs(testSuite); } TEST_F(BoltEncoder, BoltV1ExampleMessages) { // this test checks example messages from: http://boltprotocol.org/v1/ output.clear(); // record message std::vector rvals; for (int i = 1; i < 4; ++i) rvals.push_back(Value(i)); bolt_encoder.MessageRecord(rvals); CheckOutput(output, (const uint8_t *)"\xB1\x71\x93\x01\x02\x03", 6); // success message std::string sv1("name"), sv2("age"), sk("fields"); std::vector svec; svec.push_back(Value(sv1)); svec.push_back(Value(sv2)); Value slist(svec); std::map svals; svals.insert(std::make_pair(sk, slist)); bolt_encoder.MessageSuccess(svals); CheckOutput(output, (const uint8_t *)"\xB1\x70\xA1\x86\x66\x69\x65\x6C\x64\x73\x92\x84\x6E\x61\x6D\x65\x83\x61\x67\x65", 20); // failure message std::string fv1("Neo.ClientError.Statement.SyntaxError"), fv2("Invalid syntax."); std::string fk1("code"), fk2("message"); Value ftv1(fv1), ftv2(fv2); std::map fvals; fvals.insert(std::make_pair(fk1, ftv1)); fvals.insert(std::make_pair(fk2, ftv2)); bolt_encoder.MessageFailure(fvals); CheckOutput(output, (const uint8_t *) "\xB1\x7F\xA2\x84\x63\x6F\x64\x65\xD0\x25\x4E\x65\x6F\x2E\x43\x6C\x69\x65\x6E\x74\x45\x72\x72\x6F\x72\x2E\x53\x74\x61\x74\x65\x6D\x65\x6E\x74\x2E\x53\x79\x6E\x74\x61\x78\x45\x72\x72\x6F\x72\x87\x6D\x65\x73\x73\x61\x67\x65\x8F\x49\x6E\x76\x61\x6C\x69\x64\x20\x73\x79\x6E\x74\x61\x78\x2E", 71); // ignored message bolt_encoder.MessageIgnored(); CheckOutput(output, (const uint8_t *)"\xB0\x7E", 2); } // Temporal types testing starts here template constexpr uint8_t Cast(T marker) { return static_cast(marker); } TEST_F(BoltEncoder, DateOld) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::Date({1970, 1, 1})); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto &date = value.ValueDate(); const auto days = date.DaysSinceEpoch(); ASSERT_EQ(days, 0); const auto *d_bytes = std::bit_cast(&days); // 0x91 denotes the size of vals (it's 0x91 because it's anded -- see // WriteTypeSize() in base_encoder.hpp). // We reverse the order of d_bytes because after the encoding // it has BigEndian orderring. using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct1), Cast(Sig::Date), d_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); } TEST_F(BoltEncoder, DateRecent) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::Date({2021, 7, 20})); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto &date = value.ValueDate(); const auto days = date.DaysSinceEpoch(); ASSERT_EQ(days, 18828); const auto *d_bytes = std::bit_cast(&days); // 0x91 denotes the size of vals (it's 0x91 because it's anded -- see // WriteTypeSize() in base_encoder.hpp). // We reverse the order of d_bytes because after the encoding // it has BigEndian orderring. using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct1), Cast(Sig::Date), Cast(Marker::Int16), d_bytes[1], d_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); } TEST_F(BoltEncoder, DurationOneSec) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::Duration(1)); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto &dur = value.ValueDuration(); ASSERT_EQ(dur.Days(), 0); ASSERT_EQ(dur.SubDaysAsSeconds(), 0); const auto nanos = dur.SubSecondsAsNanoseconds(); ASSERT_EQ(nanos, 1000); const auto *d_bytes = std::bit_cast(&nanos); // 0x91 denotes the size of vals (it's 0x91 because it's anded -- see // WriteTypeSize in base_encoder.hpp). using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct4), Cast(Sig::Duration), 0x0, 0x0, 0x0, Cast(Marker::Int16), d_bytes[1], d_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); } TEST_F(BoltEncoder, DurationMinusOneSec) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::Duration(-1)); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto &dur = value.ValueDuration(); ASSERT_EQ(dur.Days(), 0); ASSERT_EQ(dur.SubDaysAsSeconds(), 0); const auto nanos = dur.SubSecondsAsNanoseconds(); const auto *d_bytes = std::bit_cast(&nanos); ASSERT_EQ(nanos, -1000); // 0x91 denotes the size of vals (it's 0x91 because it's anded -- see // WriteTypeSize in base_encoder.hpp). using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct4), Cast(Sig::Duration), 0x0, 0x0, 0x0, Cast(Marker::Int16), d_bytes[1], d_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); } TEST_F(BoltEncoder, ArbitraryDuration) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::Duration({15, 1, 2, 3, 5, 0})); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto &dur = value.ValueDuration(); // This is an approximation because of chrono::months to days conversion ASSERT_EQ(dur.Days(), 15); const auto secs = dur.SubDaysAsSeconds(); ASSERT_EQ(secs, 3723); const auto *sec_bytes = std::bit_cast(&secs); const auto nanos = dur.SubSecondsAsNanoseconds(); ASSERT_EQ(nanos, 5000000); const auto *nano_bytes = std::bit_cast(&nanos); // 0x91 denotes the size of vals (it's 0x91 because it's anded -- see // WriteTypeSize in base_encoder.hpp). using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct4), Cast(Sig::Duration), 0x0, 0xF, Cast(Marker::Int16), sec_bytes[1], sec_bytes[0], Cast(Marker::Int32), nano_bytes[3], nano_bytes[2], nano_bytes[1], nano_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); } TEST_F(BoltEncoder, LocalTimeOneMicro) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::LocalTime(1)); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto &local_time = value.ValueLocalTime(); const auto nanos = local_time.NanosecondsSinceEpoch(); ASSERT_EQ(nanos, 1000); const auto *n_bytes = std::bit_cast(&nanos); using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct1), Cast(Sig::LocalTime), Cast(Marker::Int16), n_bytes[1], n_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); } TEST_F(BoltEncoder, LocalTimeOneThousandMicro) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::LocalTime(1000)); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto &local_time = value.ValueLocalTime(); const auto nanos = local_time.NanosecondsSinceEpoch(); ASSERT_EQ(nanos, 1000000); const auto *n_bytes = std::bit_cast(&nanos); using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct1), Cast(Sig::LocalTime), Cast(Marker::Int32), n_bytes[3], n_bytes[2], n_bytes[1], n_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); } TEST_F(BoltEncoder, LocalDateTime) { output.clear(); std::vector vals; const auto value = Value(memgraph::utils::LocalDateTime(memgraph::utils::Date(1), memgraph::utils::LocalTime({0, 0, 30, 1, 0}))); const auto &local_date_time = value.ValueLocalDateTime(); vals.push_back(value); ASSERT_EQ(bolt_encoder.MessageRecord(vals), true); const auto secs = local_date_time.SecondsSinceEpoch(); ASSERT_EQ(secs, 30); const auto *sec_bytes = std::bit_cast(&secs); const auto nanos = local_date_time.SubSecondsAsNanoseconds(); ASSERT_EQ(nanos, 1000000); const auto *nano_bytes = std::bit_cast(&nanos); // 0x91 denotes the size of vals (it's 0x91 because it's anded -- see // WriteTypeSize in base_encoder.hpp). // The rest of the expected results follow logically from LocalTime and Date test cases using Marker = memgraph::communication::bolt::Marker; using Sig = memgraph::communication::bolt::Signature; // clang-format off const auto expected = std::array { Cast(Marker::TinyStruct1), Cast(Sig::Record), 0x91, Cast(Marker::TinyStruct2), Cast(Sig::LocalDateTime), // SuperSeconds sec_bytes[0], // SubSeconds Cast(Marker::Int32), nano_bytes[3], nano_bytes[2], nano_bytes[1], nano_bytes[0] }; // clang-format on CheckOutput(output, expected.data(), expected.size()); }