0bc298c3ad
The fields of ROUTE message were not read from the input buffer, thus the input buffer got corrupted. Sending a new message to the server would result reading the remaining fields from the buffer, which means reading some values instead of message signature. Because of this unmet expectation, Memgraph closed the connection. With this fix, the fields of the ROUTE message are properly read and ignored.
472 lines
16 KiB
C++
472 lines
16 KiB
C++
// Copyright 2022 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 <iostream>
|
|
|
|
#include <gflags/gflags.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "communication/bolt/client.hpp"
|
|
#include "io/network/endpoint.hpp"
|
|
#include "io/network/utils.hpp"
|
|
#include "utils/exceptions.hpp"
|
|
|
|
DEFINE_string(address, "127.0.0.1", "Server address");
|
|
DEFINE_int32(port, 7687, "Server port");
|
|
DEFINE_string(username, "", "Username for the database");
|
|
DEFINE_string(password, "", "Password for the database");
|
|
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
|
|
|
using namespace memgraph::communication::bolt;
|
|
|
|
class BoltClient : public ::testing::Test {
|
|
protected:
|
|
virtual void SetUp() { client_.Connect(endpoint_, FLAGS_username, FLAGS_password); }
|
|
|
|
virtual void TearDown() {}
|
|
|
|
bool Execute(const std::string &query, const std::string &message = "") {
|
|
try {
|
|
auto ret = client_.Execute(query, {});
|
|
} catch (const ClientQueryException &e) {
|
|
if (message != "") {
|
|
EXPECT_EQ(e.what(), message);
|
|
}
|
|
throw;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int64_t GetCount() {
|
|
auto ret = client_.Execute("match (n) return count(n)", {});
|
|
EXPECT_EQ(ret.records.size(), 1);
|
|
EXPECT_EQ(ret.records[0].size(), 1);
|
|
EXPECT_TRUE(ret.records[0][0].IsInt());
|
|
return ret.records[0][0].ValueInt();
|
|
}
|
|
|
|
bool TransactionActive() {
|
|
try {
|
|
client_.Execute("begin", {});
|
|
} catch (const ClientQueryException &e) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
memgraph::io::network::Endpoint endpoint_{memgraph::io::network::ResolveHostname(FLAGS_address),
|
|
static_cast<uint16_t>(FLAGS_port)};
|
|
memgraph::communication::ClientContext context_{FLAGS_use_ssl};
|
|
Client client_{context_};
|
|
};
|
|
|
|
const std::string kNoCurrentTransactionToCommit = "No current transaction to commit.";
|
|
const std::string kNoCurrentTransactionToRollback = "No current transaction to rollback.";
|
|
const std::string kNestedTransactions = "Nested transactions are not supported.";
|
|
const std::string kCommitInvalid =
|
|
"Transaction can't be committed because there was a previous error. Please "
|
|
"invoke a rollback instead.";
|
|
|
|
TEST_F(BoltClient, CommitWithoutTransaction) {
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, RollbackWithoutTransaction) {
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, DoubleCommitWithoutTransaction) {
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, DoubleRollbackWithoutTransaction) {
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, DoubleBegin) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, DoubleBeginAndCommit) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, DoubleBeginAndRollback) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndDoubleCommit) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndDoubleRollback) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndCorrectQueriesAndCommit) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
auto count = GetCount();
|
|
EXPECT_TRUE(Execute("create (n)"));
|
|
ASSERT_EQ(GetCount(), count + 1);
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_EQ(GetCount(), count + 1);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndCorrectQueriesAndRollback) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
auto count = GetCount();
|
|
EXPECT_TRUE(Execute("create (n)"));
|
|
ASSERT_EQ(GetCount(), count + 1);
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_EQ(GetCount(), count);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndCorrectQueriesAndBegin) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
auto count = GetCount();
|
|
EXPECT_TRUE(Execute("create (n)"));
|
|
ASSERT_EQ(GetCount(), count + 1);
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_EQ(GetCount(), count);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndWrongQueryAndRollback) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndWrongQueryAndCommit) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, BeginAndWrongQueryAndBegin) {
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CommitAndCorrectQueryAndCommit) {
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_TRUE(Execute("create (n)"));
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CommitAndWrongQueryAndCommit) {
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, RollbackAndCorrectQueryAndRollback) {
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_TRUE(Execute("create (n)"));
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, RollbackAndWrongQueryAndRollback) {
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueryAndBeginAndCommit) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueryAndBeginAndRollback) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueryAndBeginAndBegin) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueryAndBeginAndCommit) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueryAndBeginAndRollback) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueryAndBeginAndBegin) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndCommit) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndRollback) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndBegin) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndCommit) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndRollback) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndBegin) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndCorrectQueriesAndCommit) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndCorrectQueriesAndRollback) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndCorrectQueriesAndBegin) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndCorrectQueriesAndCommit) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("commit"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndCorrectQueriesAndRollback) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("rollback"));
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndCorrectQueriesAndBegin) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_THROW(Execute("begin", kNestedTransactions), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndWrongQueriesAndCommit) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndWrongQueriesAndRollback) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, CorrectQueriesAndBeginAndWrongQueriesAndBegin) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndWrongQueriesAndCommit) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_THROW(Execute("commit", kNoCurrentTransactionToCommit), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndWrongQueriesAndRollback) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_THROW(Execute("rollback", kNoCurrentTransactionToRollback), ClientQueryException);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, WrongQueriesAndBeginAndWrongQueriesAndBegin) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_THROW(Execute("asdasd"), ClientQueryException);
|
|
}
|
|
EXPECT_TRUE(Execute("begin"));
|
|
EXPECT_TRUE(TransactionActive());
|
|
}
|
|
|
|
TEST_F(BoltClient, MixedCaseAndWhitespace) {
|
|
EXPECT_TRUE(Execute(" bEgiN \n\n"));
|
|
auto count = GetCount();
|
|
EXPECT_TRUE(Execute(" cReATe ( n ) \n\n"));
|
|
ASSERT_EQ(GetCount(), count + 1);
|
|
EXPECT_TRUE(Execute(" COMmit "));
|
|
EXPECT_EQ(GetCount(), count + 1);
|
|
EXPECT_FALSE(TransactionActive());
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
|
|
|
memgraph::communication::SSLInit sslInit;
|
|
|
|
return RUN_ALL_TESTS();
|
|
}
|