/** * Copyright (C) 2015 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #include "mongo/base/data_type_terminated.h" #include "mongo/base/data_range.h" #include "mongo/base/data_range_cursor.h" #include "mongo/unittest/unittest.h" #include "mongo/util/stringutils.h" #include namespace mongo { namespace { // For testing purposes, a type that has a fixed load and store size, and some // arbitrary serialization format of 'd' repeated N times. template struct Dummy { static constexpr size_t extent = N; }; } // namespace // Pop the anonymous namespace to specialize mongo::DataType::Handler. // Template specialization is a drag. template struct DataType::Handler> { using handledType = Dummy; static constexpr size_t extent = handledType::extent; static Status load(handledType* sdata, const char* ptr, size_t length, size_t* advanced, std::ptrdiff_t debug_offset) { if (length < extent) { return Status(ErrorCodes::Overflow, "too short for Dummy"); } for (size_t i = 0; i < extent; ++i) { if (*ptr++ != 'd') { return Status(ErrorCodes::Overflow, "load of invalid Dummy object"); } } *advanced = extent; return Status::OK(); } static Status store(const handledType& sdata, char* ptr, size_t length, size_t* advanced, std::ptrdiff_t debug_offset) { if (length < extent) { return Status(ErrorCodes::Overflow, "insufficient space for Dummy"); } for (size_t i = 0; i < extent; ++i) { *ptr++ = 'd'; } *advanced = extent; return Status::OK(); } static handledType defaultConstruct() { return {}; } }; // Re-push the anonymous namespace. namespace { /** * Tests specifically for Terminated, unrelated to the DataRange * or DataRangeCursor classes that call it. */ TEST(DataTypeTerminated, StringDataNormalStore) { const StringData writes[] = {StringData("a"), StringData("bb"), StringData("ccc")}; std::string buf(100, '\xff'); char* const bufBegin = &*buf.begin(); char* ptr = bufBegin; size_t avail = buf.size(); std::string expected; for (const auto& w : writes) { size_t adv; ASSERT_OK( DataType::store(Terminated<'\0', StringData>(w), ptr, avail, &adv, ptr - bufBegin)); ASSERT_EQ(adv, w.size() + 1); ptr += adv; avail -= adv; expected += w.toString(); expected += '\0'; } ASSERT_EQUALS(expected, buf.substr(0, buf.size() - avail)); } TEST(DataTypeTerminated, StringDataNormalLoad) { const StringData writes[] = {StringData("a"), StringData("bb"), StringData("ccc")}; std::string buf; for (const auto& w : writes) { buf += w.toString(); buf += '\0'; } const char* const bufBegin = &*buf.begin(); const char* ptr = bufBegin; size_t avail = buf.size(); for (const auto& w : writes) { size_t adv; auto term = Terminated<'\0', StringData>{}; ASSERT_OK(DataType::load(&term, ptr, avail, &adv, ptr - bufBegin)); ASSERT_EQ(adv, term.value.size() + 1); ptr += adv; avail -= adv; ASSERT_EQUALS(term.value, w); } } TEST(DataTypeTerminated, LoadStatusOkPropagation) { // Test that the nested type's .load complaints are surfaced. const char buf[] = {'d', 'd', 'd', '\0'}; size_t advanced = 123; auto x = Terminated<'\0', Dummy<3>>(); Status s = DataType::load(&x, buf, sizeof(buf), &advanced, 0); ASSERT_OK(s); ASSERT_EQUALS(advanced, 4u); // OK must overwrite advanced } TEST(DataTypeTerminated, StoreStatusOkAdvanced) { // Test that an OK .store sets proper 'advanced'. char buf[4] = {}; size_t advanced = 123; // should be overwritten Status s = DataType::store(Terminated<'\0', Dummy<3>>(), buf, sizeof(buf), &advanced, 0); ASSERT_OK(s); ASSERT_EQ(StringData(buf, 4), StringData(std::string{'d', 'd', 'd', '\0'})); ASSERT_EQUALS(advanced, 4u); // OK must overwrite advanced } TEST(DataTypeTerminated, ErrorUnterminatedRead) { const char buf[] = {'h', 'e', 'l', 'l', 'o'}; size_t advanced = 123; auto x = Terminated<'\0', StringData>(); Status s = DataType::load(&x, buf, sizeof(buf), &advanced, 0); ASSERT_EQ(s.codeString(), "Overflow"); ASSERT_STRING_CONTAINS(s.reason(), "couldn't locate"); ASSERT_STRING_CONTAINS(s.reason(), "terminal char (\\u0000)"); ASSERT_EQUALS(advanced, 123u); // fails must not overwrite advanced } TEST(DataTypeTerminated, LoadStatusPropagation) { // Test that the nested type's .load complaints are surfaced. const char buf[] = {'d', 'd', '\0'}; size_t advanced = 123; auto x = Terminated<'\0', Dummy<3>>(); Status s = DataType::load(&x, buf, sizeof(buf), &advanced, 0); ASSERT_EQ(s.codeString(), "Overflow"); ASSERT_STRING_CONTAINS(s.reason(), "too short for Dummy"); // ASSERT_STRING_CONTAINS(s.reason(), "terminal char (\\u0000)"); ASSERT_EQUALS(advanced, 123u); // fails must not overwrite advanced } TEST(DataTypeTerminated, StoreStatusPropagation) { // Test that the nested type's .store complaints are surfaced. char in[2]; // Not big enough to hold a Dummy<3>. size_t advanced = 123; Status s = DataType::store(Terminated<'\0', Dummy<3>>(), in, sizeof(in), &advanced, 0); ASSERT_EQ(s.codeString(), "Overflow"); ASSERT_STRING_CONTAINS(s.reason(), "insufficient space for Dummy"); ASSERT_EQUALS(advanced, 123u); // fails must not overwrite advanced } TEST(DataTypeTerminated, ErrorShortRead) { // The span before the '\0' is passed to Dummy<3>'s load. // This consumes only the first 3 bytes, so Terminated complains // about the unconsumed 'X'. const char buf[] = {'d', 'd', 'd', 'X', '\0'}; size_t advanced = 123; auto x = Terminated<'\0', Dummy<3>>(); Status s = DataType::load(&x, buf, sizeof(buf), &advanced, 0); ASSERT_EQ(s.codeString(), "Overflow"); ASSERT_STRING_CONTAINS(s.reason(), "only read"); ASSERT_STRING_CONTAINS(s.reason(), "terminal char (\\u0000)"); ASSERT_EQUALS(advanced, 123u); // fails must not overwrite advanced } TEST(DataTypeTerminated, ErrorShortWrite) { char in[3] = {}; auto x = Terminated<'\0', Dummy<3>>(); size_t advanced = 123; Status s = DataType::store(x, in, sizeof(in), &advanced, 0); ASSERT_EQ(s.codeString(), "Overflow"); ASSERT_STRING_CONTAINS(s.reason(), "couldn't write"); ASSERT_STRING_CONTAINS(s.reason(), "terminal char (\\u0000)"); ASSERT_EQUALS(advanced, 123u); // fails must not overwrite advanced } TEST(DataTypeTerminated, ThroughDataRangeCursor) { char buf[100]; const std::string parts[] = {"a", "bb", "ccc"}; std::string serialized; for (const std::string& s : parts) { serialized += s + '\0'; } { auto buf_writer = DataRangeCursor(buf, buf + sizeof(buf)); for (const std::string& s : parts) { Terminated<'\0', ConstDataRange> tcdr(ConstDataRange(s.data(), s.data() + s.size())); ASSERT_OK(buf_writer.writeAndAdvance(tcdr)); } const auto written = std::string(static_cast(buf), buf_writer.data()); ASSERT_EQUALS(written, serialized); } { auto buf_source = ConstDataRangeCursor(buf, buf + sizeof(buf)); for (const std::string& s : parts) { Terminated<'\0', ConstDataRange> tcdr; ASSERT_OK(buf_source.readAndAdvance(&tcdr)); std::string read(tcdr.value.data(), tcdr.value.data() + tcdr.value.length()); ASSERT_EQUALS(s, read); } } } } // namespace } // namespace mongo