diff options
| author | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-18 17:02:53 -0300 |
|---|---|---|
| committer | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-18 17:02:53 -0300 |
| commit | 959575a5ca598bf5f37fb5cebe7ed1d80d3d71f7 (patch) | |
| tree | acc8d60aedb12b70048e676e8a7349deb0010db8 /src/mongo/bson | |
| parent | 76588293975fc059cf076779e4283e6ffaf8afff (diff) | |
New upstream version 6.0.20upstream
Diffstat (limited to 'src/mongo/bson')
| -rw-r--r-- | src/mongo/bson/SConscript | 17 | ||||
| -rw-r--r-- | src/mongo/bson/bson_validate.cpp | 129 | ||||
| -rw-r--r-- | src/mongo/bson/bson_validate.h | 22 | ||||
| -rw-r--r-- | src/mongo/bson/bson_validate.idl | 61 | ||||
| -rw-r--r-- | src/mongo/bson/bson_validate_test.cpp | 23 | ||||
| -rw-r--r-- | src/mongo/bson/bsonelement.cpp | 59 | ||||
| -rw-r--r-- | src/mongo/bson/bsonelement.h | 7 | ||||
| -rw-r--r-- | src/mongo/bson/bsonelement_test.cpp | 50 | ||||
| -rw-r--r-- | src/mongo/bson/bsonobj.cpp | 70 | ||||
| -rw-r--r-- | src/mongo/bson/bsonobj.h | 11 | ||||
| -rw-r--r-- | src/mongo/bson/bsonobjbuilder.cpp | 2 | ||||
| -rw-r--r-- | src/mongo/bson/bsonobjbuilder.h | 62 | ||||
| -rw-r--r-- | src/mongo/bson/bsontypes.cpp | 3 | ||||
| -rw-r--r-- | src/mongo/bson/bsontypes.h | 5 | ||||
| -rw-r--r-- | src/mongo/bson/simple_bsonobj_comparator.h | 4 | ||||
| -rw-r--r-- | src/mongo/bson/timestamp.h | 2 | ||||
| -rw-r--r-- | src/mongo/bson/util/builder.h | 44 | ||||
| -rw-r--r-- | src/mongo/bson/util/builder_test.cpp | 33 | ||||
| -rw-r--r-- | src/mongo/bson/util/simple8b_test.cpp | 3 |
19 files changed, 452 insertions, 155 deletions
diff --git a/src/mongo/bson/SConscript b/src/mongo/bson/SConscript index 31eb607823c..472aed01ad3 100644 --- a/src/mongo/bson/SConscript +++ b/src/mongo/bson/SConscript @@ -28,6 +28,7 @@ env.CppUnitTest( ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/bson/bson_validate', '$BUILD_DIR/mongo/bson/util/bson_column', ], ) @@ -39,6 +40,7 @@ env.Benchmark( ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/bson/bson_validate', ], ) @@ -50,6 +52,21 @@ env.CppLibfuzzerTest( ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/bson/bson_validate', + ], +) + +env.Library( + target='bson_validate', + source=[ + 'bson_validate.cpp', + 'bson_validate.idl', + ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/bson/util/bson_column', + '$BUILD_DIR/mongo/idl/idl_parser', + '$BUILD_DIR/mongo/idl/server_parameter', ], ) diff --git a/src/mongo/bson/bson_validate.cpp b/src/mongo/bson/bson_validate.cpp index 726a12b818c..da4b54ef194 100644 --- a/src/mongo/bson/bson_validate.cpp +++ b/src/mongo/bson/bson_validate.cpp @@ -83,9 +83,15 @@ constexpr ErrorCodes::Error InvalidBSON = ErrorCodes::InvalidBSON; constexpr ErrorCodes::Error NonConformantBSON = ErrorCodes::NonConformantBSON; template <bool precise> +Status _doValidateColumn(const char* originalBuffer, + uint64_t maxLength, + ValidationVersion validationVersion); + +template <bool precise> class ValidateBuffer { public: - ValidateBuffer(const char* data, uint64_t maxLength) : _data(data), _maxLength(maxLength) { + ValidateBuffer(const char* data, uint64_t maxLength, ValidationVersion validationVersion) + : _data(data), _maxLength(maxLength), _validationVersion(validationVersion) { if constexpr (precise) _frames.resize(BSONDepth::getMaxAllowableDepth() + 1); } @@ -127,30 +133,21 @@ public: // Handle one element without using iterative loop, and without expecting // multiple instances or an EOO. Only resume with the iterative loop if - // the frame stack has been incremented, meaning we have nested objects - - // Save pointer to currFrame->end so we can fill it in once we know the size - const char** preEnd = &(_currFrame->end); - const char* ptr = _validateElem(Cursor{_data + 2, _data + _maxLength}, *_data); + // we have nested objects + _currElem = _data; + const char* ptr = _validateElem<false>(Cursor{_data + 2, _data + _maxLength}, *_data); - if (_currFrame != _frames.begin()) { - // We know that type was kObject or kArray, so size is fieldname, type, - // and a stored int + if (_firstFrameUpdated) { + // We know that type was kObject/kArray/kCodeWScope + // Size is fieldname, type, and a stored int int64_t size = static_cast<int64_t>(ConstDataView(_data + 2).read<LittleEndian<int32_t>>()) + 2; uassert(InvalidBSON, "BSON literal content exceeds buffer size", (size_t)size <= _maxLength); - *preEnd = _data + size; - const char* internalEnd = _currFrame->end; - _popFrame(); - uassert(InvalidBSON, - "BSON literal nested content does not end at external end", - _currFrame->end == internalEnd); _validateIterative(Cursor{ptr, _data + size}); return size; } else { - *preEnd = ptr; return ptr - _data; } } @@ -216,7 +213,10 @@ private: uassert(ErrorCodes::Overflow, "BSONObj exceeds maximum nested object depth", ++_currFrame != _frames.end()); + return _updateFrame(cursor); + } + const char* _updateFrame(Cursor cursor) { auto obj = cursor.ptr; auto len = cursor.template read<int32_t>(); uassert(ErrorCodes::InvalidBSON, "Nested BSON object has to be at least 5 bytes", len >= 5); @@ -236,18 +236,19 @@ private: return true; } - static const char* _validateSpecial(Cursor cursor, uint8_t type) { + const char* _validateSpecial(Cursor cursor, uint8_t type) { switch (type) { case BSONType::BinData: { auto count = cursor.template read<uint32_t>(); auto subtype = cursor.template read<uint8_t>(); const char* columnStart = cursor.ptr; cursor.skip(count); - if (subtype == BinDataType::Column) { + if (subtype == BinDataType::Column && _validationVersion >= V2_Column) { /* do not pass down cursor; we want to reset the nesting depth */ - uassert(NonConformantBSON, - "Invalid BSON column", - validateBSONColumn(columnStart, count).isOK()); + uassert( + NonConformantBSON, + "Invalid BSON column", + _doValidateColumn<precise>(columnStart, count, _validationVersion).isOK()); } break; } @@ -274,10 +275,15 @@ private: return cursor.ptr; } + template <bool nestedFrame> const char* _pushCodeWithScope(Cursor cursor) { - cursor.ptr = _pushFrame(cursor); // Push a dummy frame to check the CodeWScope size. - cursor.skipString(); // Now skip the BSON UTF8 string containing the code. - _currElem = cursor.ptr - 1; // Use the terminating NUL as adummy scope element. + // Push a dummy frame to check the CodeWScope size. + if constexpr (nestedFrame) + cursor.ptr = _pushFrame(cursor); + else + cursor.ptr = _updateFrame(cursor); + cursor.skipString(); // Now skip the BSON UTF8 string containing the code. + _currElem = cursor.ptr - 1; // Use the terminating NUL as a dummy scope element. return _pushFrame(cursor); } @@ -291,21 +297,30 @@ private: } } + template <bool nestedFrame> const char* _validateElem(Cursor cursor, uint8_t type) { if (MONGO_unlikely(type > JSTypeMax)) return _validateSpecial(cursor, type); auto style = kTypeInfoTable[type]; - if (MONGO_likely(style <= kSkip16)) + if (MONGO_likely(style <= kSkip16)) { cursor.skip(style * 4); - else if (MONGO_likely(style == kString)) + } else if (MONGO_likely(style == kString)) { cursor.skipString(); - else if (MONGO_likely(style == kObjectOrArray)) - cursor.ptr = _pushFrame(cursor); - else if (MONGO_unlikely(precise && type == CodeWScope)) - cursor.ptr = _pushCodeWithScope(cursor); - else + } else if (MONGO_likely(style == kObjectOrArray)) { + if constexpr (nestedFrame) { + cursor.ptr = _pushFrame(cursor); + } else { + cursor.ptr = _updateFrame(cursor); + _firstFrameUpdated = true; + } + } else if (MONGO_unlikely(precise && type == CodeWScope)) { + cursor.ptr = _pushCodeWithScope<nestedFrame>(cursor); + if constexpr (!nestedFrame) + _firstFrameUpdated = true; + } else { cursor.ptr = _validateSpecial(cursor, type); + } return cursor.ptr; } @@ -319,7 +334,7 @@ private: uint8_t type = *cursor.ptr; _currElem = cursor.ptr; cursor.ptr += len + 1; - cursor.ptr = _validateElem(cursor, type); + cursor.ptr = _validateElem<true>(cursor, type); if constexpr (precise) { // See if the _id field was just validated. If so, set the global scope element. @@ -361,11 +376,16 @@ private: const char* _currElem = nullptr; // Element to validate: only the name is known to be good. typename Frames::iterator _currFrame; // Frame currently being validated. Frames _frames; // Has end pointers to check and the containing element for precise mode. + bool _firstFrameUpdated = false; // Has the first frame received nested while measuring an elem + ValidationVersion _validationVersion; }; +template <bool precise> class ColumnValidator { public: - static Status doValidateBSONColumn(const char* originalBuffer, int maxLength) noexcept { + static Status doValidateBSONColumn(const char* originalBuffer, + int maxLength, + ValidationVersion validationVersion) noexcept { // run control pointer through to end of buffer // run over literal data as directed by lengths from control // check formatting of Simple8B blocks @@ -398,7 +418,8 @@ public: return Status::OK(); } } else if (bsoncolumn::isUncompressedLiteralControlByte(control)) { - ptr += ValidateBuffer<false>(ptr, end - ptr).validateAndMeasureElem(); + ptr += ValidateBuffer<precise>(ptr, end - ptr, validationVersion) + .validateAndMeasureElem(); } else if (bsoncolumn::isInterleavedStartControlByte(control)) { // interleaved objects begin with a reference object, and then a series // of diff blocks for followup objects, ending with an EOO. Nesting interleaved @@ -430,20 +451,50 @@ public: return Status(NonConformantBSON, "Missing terminating EOO"); } }; + +template <bool precise> +Status _doValidateColumn(const char* originalBuffer, + uint64_t maxLength, + ValidationVersion validationVersion) { + if constexpr (precise) { + // First try validating using the fast but less precise version. That version will return + // a not-OK status for objects with CodeWScope or nesting exceeding 32 levels. These cases + // and actual failures will rerun the precise version that gives a detailed error context. + if (MONGO_likely(ColumnValidator<false>::doValidateBSONColumn( + originalBuffer, maxLength, validationVersion) + .isOK())) + return Status::OK(); + + return ColumnValidator<true>::doValidateBSONColumn( + originalBuffer, maxLength, validationVersion); + } else { + return ColumnValidator<false>::doValidateBSONColumn( + originalBuffer, maxLength, validationVersion); + } +} } // namespace -Status validateBSON(const char* originalBuffer, uint64_t maxLength) noexcept { +Status validateBSON(const char* originalBuffer, + uint64_t maxLength, + ValidationVersion validationVersion) noexcept { // First try validating using the fast but less precise version. That version will return // a not-OK status for objects with CodeWScope or nesting exceeding 32 levels. These cases and // actual failures will rerun the precise version that gives a detailed error context. - if (MONGO_likely(ValidateBuffer<false>(originalBuffer, maxLength).validate().isOK())) + if (MONGO_likely( + ValidateBuffer<false>(originalBuffer, maxLength, validationVersion).validate().isOK())) return Status::OK(); - return ValidateBuffer<true>(originalBuffer, maxLength).validate(); + return ValidateBuffer<true>(originalBuffer, maxLength, validationVersion).validate(); +} + +Status validateBSON(const BSONObj& obj, ValidationVersion validationVersion) noexcept { + return validateBSON(obj.objdata(), obj.objsize(), validationVersion); } -Status validateBSONColumn(const char* originalBuffer, int maxLength) noexcept { - return ColumnValidator::doValidateBSONColumn(originalBuffer, maxLength); +Status validateBSONColumn(const char* originalBuffer, + int maxLength, + ValidationVersion validationVersion) noexcept { + return _doValidateColumn<true>(originalBuffer, maxLength, validationVersion); } } // namespace mongo diff --git a/src/mongo/bson/bson_validate.h b/src/mongo/bson/bson_validate.h index a737f61b129..add1c18b55b 100644 --- a/src/mongo/bson/bson_validate.h +++ b/src/mongo/bson/bson_validate.h @@ -36,6 +36,17 @@ namespace mongo { +enum ValidationVersion { + /* Original validator */ + V1_Original = 1, + /* Adds validation for the content of Column-typed BinData */ + V2_Column = 2 +}; + +// When adding new versions of BSON validation, update both this and the range and the +// default for the server parameter in src/mongo/bson/bson_validate.idl +static constexpr ValidationVersion currentValidationVersion = V2_Column; + /** * Checks that the buf holds a BSON object as defined in http://bsonspec.org/spec.html. * Note that maxLength is the buffer size, NOT the BSON size. @@ -49,8 +60,15 @@ namespace mongo { * validity, code validity, correct length and formatting of binary subtypes, etc. * Length is only limited by the buffer's maxLength and the inherent 2GB - 1 format limitation. */ -Status validateBSON(const char* buf, uint64_t maxLength) noexcept; +Status validateBSON(const char* buf, + uint64_t maxLength, + ValidationVersion validationVersion = currentValidationVersion) noexcept; + +Status validateBSON(const BSONObj& obj, + ValidationVersion validationVersion = currentValidationVersion) noexcept; -Status validateBSONColumn(const char* buf, int maxLength) noexcept; +Status validateBSONColumn(const char* buf, + int maxLength, + ValidationVersion validationVersion = currentValidationVersion) noexcept; } // namespace mongo diff --git a/src/mongo/bson/bson_validate.idl b/src/mongo/bson/bson_validate.idl new file mode 100644 index 00000000000..9d4bb7b9fbe --- /dev/null +++ b/src/mongo/bson/bson_validate.idl @@ -0,0 +1,61 @@ +# Copyright (C) 2023-present MongoDB, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Server Side Public License, version 1, +# as published by MongoDB, Inc. +# +# 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 +# Server Side Public License for more details. +# +# You should have received a copy of the Server Side Public License +# along with this program. If not, see +# <http://www.mongodb.com/licensing/server-side-public-license>. +# +# 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 Server Side 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. +# + +# BSON validate modes + +global: + cpp_namespace: "mongo" + +imports: + - "mongo/idl/basic_types.idl" + +enums: + BSONValidateMode: + description: "The validate mode that dbCheck will run for BSON consistency checks" + type: string + # kDefault: Only fast structural BSON consistency checks + # kExtended: Structural BSON consistency and extra fast checks on BSON specifications. + # kFull: Structural BSON consistency and extra comprehensive checks on BSON specifications. + values: + kDefault: "kDefault" + kExtended: "kExtended" + kFull: "kFull" + +# Server paramaters for validation. +# Range and default value should be kept in sync with src/mongo/bson/bson_validate.h + +server_parameters: + bsonTestValidationVersion: + description: "The version of the BSON validator that will be used to check correctness of data in test environments" + set_at: startup + cpp_vartype: int + cpp_varname: bsonTestValidationVersion + validator: + gte: 1 + lte: 2 + default: 2
\ No newline at end of file diff --git a/src/mongo/bson/bson_validate_test.cpp b/src/mongo/bson/bson_validate_test.cpp index b105e0f58e2..29e5f1536d2 100644 --- a/src/mongo/bson/bson_validate_test.cpp +++ b/src/mongo/bson/bson_validate_test.cpp @@ -51,17 +51,17 @@ using std::unique_ptr; void appendInvalidStringElement(const char* fieldName, BufBuilder* bb) { // like a BSONObj string, but without a NUL terminator. bb->appendChar(String); - bb->appendStr(fieldName, /*withNUL*/ true); + bb->appendCStr(fieldName); bb->appendNum(4); - bb->appendStr("asdf", /*withNUL*/ false); + bb->appendStrBytes("asdf"); // Missing required final NUL. } TEST(BSONValidate, Basic) { BSONObj x; - ASSERT_TRUE(x.valid()); + ASSERT_TRUE(validateBSON(x).isOK()); x = BSON("x" << 1); - ASSERT_TRUE(x.valid()); + ASSERT_TRUE(validateBSON(x).isOK()); } TEST(BSONValidate, RandomData) { @@ -87,7 +87,7 @@ TEST(BSONValidate, RandomData) { ASSERT_EQUALS(size, o.objsize()); - if (o.valid()) { + if (validateBSON(o).isOK()) { numValid++; jsonSize += o.jsonString().size(); ASSERT_OK(validateBSON(o.objdata(), o.objsize())); @@ -138,7 +138,7 @@ TEST(BSONValidate, MuckingData1) { data[i] = 0xc8U; numToRun++; - if (mine.valid()) { + if (validateBSON(mine).isOK()) { numValid++; jsonSize += mine.jsonString().size(); ASSERT_OK(validateBSON(mine.objdata(), mine.objsize())); @@ -368,7 +368,7 @@ TEST(BSONValidateFast, StringHasSomething) { BufBuilder bb; BSONObjBuilder ob(bb); bb.appendChar(String); - bb.appendStr("x", /*withNUL*/ true); + bb.appendCStr("x"); bb.appendNum(0); const BSONObj x = ob.done(); ASSERT_EQUALS(5 // overhead @@ -483,6 +483,7 @@ TEST_F(BSONValidateColumn, BSONColumnInBSON) { TEST_F(BSONValidateColumn, BSONColumnMissingEOO) { BSONColumnBuilder cb(""); + cb.append(BSON("a" << "deadbeef") .getField("a")); @@ -694,4 +695,12 @@ TEST_F(BSONValidateColumn, BSONColumnBadExtendedSelector) { ASSERT_OK(validateBSONColumn((char*)columnData.data, columnData.length)); } +TEST(BSONValidateColumn, BSONColumnWithCodeWScope) { + BSONObj obj = BSON("a" << BSONCodeWScope("code", BSON("c" << 1))); + BSONColumnBuilder cb(""); + cb.append(obj.getField("a")); + BSONBinData columnData = cb.finalize(); + ASSERT_OK(validateBSONColumn((char*)columnData.data, columnData.length)); +} + } // namespace diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp index 2409084bcfc..0990b1a18c7 100644 --- a/src/mongo/bson/bsonelement.cpp +++ b/src/mongo/bson/bsonelement.cpp @@ -46,6 +46,7 @@ #include "mongo/logv2/log.h" #include "mongo/platform/strnlen.h" #include "mongo/util/base64.h" +#include "mongo/util/decimal_counter.h" #include "mongo/util/duration.h" #include "mongo/util/hex.h" #include "mongo/util/scopeguard.h" @@ -444,6 +445,7 @@ int BSONElement::compareElements(const BSONElement& l, */ std::vector<BSONElement> BSONElement::Array() const { chk(mongo::Array); + std::vector<BSONElement> v; BSONObjIterator i(Obj()); while (i.more()) { @@ -464,6 +466,23 @@ std::vector<BSONElement> BSONElement::Array() const { return v; } +std::vector<BSONElement> BSONElement::ArrayVerifyIndexes() const { + chk(mongo::Array); + + std::vector<BSONElement> v; + DecimalCounter<std::uint32_t> counter(0); + for (auto element : Obj()) { + auto fieldName = element.fieldNameStringData(); + uassert(ErrorCodes::BadValue, + fmt::format( + "Invalid array index field name: \"{}\", expected \"{}\"", fieldName, counter), + fieldName == counter); + counter++; + v.push_back(element); + } + return v; +} + int BSONElement::woCompare(const BSONElement& elem, ComparisonRulesSet rules, const StringData::ComparatorInterface* comparator) const { @@ -646,41 +665,13 @@ BSONElement BSONElement::operator[](StringData field) const { } namespace { -MONGO_COMPILER_NOINLINE void msgAssertedBadType [[noreturn]] (const char* data) { - // We intentionally read memory that may be out of the allocated memory's boundary, so do not - // do this when the address sanitizer is enabled. We do this in an attempt to log as much - // context about the failure, even if that risks undefined behavior or a segmentation fault. -#if !__has_feature(address_sanitizer) - bool logMemory = true; -#else - bool logMemory = false; -#endif - str::stream output; - if (!logMemory) { - output << fmt::format("BSONElement: bad type {0:d} @ {1:p}", *data, data); - } else { - // To reduce the risk of a segmentation fault, only print the bytes in the 32-bit aligned - // block in which the address is located (i.e. round down to the lowest multiple of 32). The - // hope is that it's safe to read memory that may fall within the same cache line. Generate - // a mask to zero-out the last bits for a block-aligned address. - // Ex: Inverse of 0x1F (32 - 1) looks like 0xFFFFFFE0, and ANDed with the pointer, zeroes - // the lowest 5 bits, giving the starting address of a 32-bit block. - const size_t blockSize = 32; - const size_t mask = ~(blockSize - 1); - const char* startAddr = - reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(data) & mask); - const size_t offset = data - startAddr; - - output << fmt::format( - "BSONElement: bad type {0:d} @ {1:p} at offset {2:d} in block: ", *data, data, offset); - - for (size_t i = 0; i < blockSize; i++) { - output << fmt::format("{0:#x} ", static_cast<uint8_t>(startAddr[i])); - } - } - msgasserted(10320, output); +MONGO_COMPILER_NOINLINE void msgAssertedBadType [[noreturn]] (int8_t type) { + int err = 10320; // work around linter + LOGV2_ERROR(err, "BSONElement: bad type", "type"_attr = zeroPaddedHex(type)); + uasserted(err, "BSONElement: bad type"); } + } // namespace int BSONElement::computeSize(int8_t type, const char* elem, int fieldNameSize, int bufSize) { @@ -751,7 +742,7 @@ int BSONElement::computeSize(int8_t type, const char* elem, int fieldNameSize, i if (type == MaxKey || type == MinKey) return fieldNameSize + 1; if (type != BSONType::RegEx) - msgAssertedBadType(elem); + msgAssertedBadType(type); // RegEx is two c-strings back-to-back. const char* p = elem + fieldNameSize + 1; diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h index 251ed41dd2d..f319c2981fb 100644 --- a/src/mongo/bson/bsonelement.h +++ b/src/mongo/bson/bsonelement.h @@ -143,7 +143,14 @@ public: bool Bool() const { return chk(mongo::Bool).boolean(); } + std::vector<BSONElement> Array() const; // see implementation for detailed comments + + /** + * Like Array() above, but, if the array keys are not in sequential order or are otherwise + * invalid, an exception is thrown. + */ + std::vector<BSONElement> ArrayVerifyIndexes() const; mongo::OID OID() const { return chk(jstOID).__oid(); } diff --git a/src/mongo/bson/bsonelement_test.cpp b/src/mongo/bson/bsonelement_test.cpp index 4e5bc3f6f07..0921823f92f 100644 --- a/src/mongo/bson/bsonelement_test.cpp +++ b/src/mongo/bson/bsonelement_test.cpp @@ -446,5 +446,55 @@ TEST(BSONElementTryCoeceToLongLongTest, CoerceFails) { ASSERT_NOT_OK(result) << " for input document " << testCase.toString(); } } + +TEST(BSONElement, ArrayToVectorFunctionsBehaveCorrectlyWithValidArray) { + // Create a valid array by creating a BSONObj with contiguous array indexes that is then + // passed to the BSONArray ctor. + BSONObj updateArrAsObj = BSON("0" + << "foo" + << "1" + << "bar"); + BSONArray updateArr(updateArrAsObj); + BSONObj parentObj = BSON("arr" << updateArr); + auto arrElem = parentObj.getField("arr"); + + // Both 'Array()' and 'ArrayVerifyIndexes()' will not throw, and instead create vectors of + // size 2. + auto elementVector = arrElem.Array(); + ASSERT_EQ(elementVector.size(), 2); + ASSERT(elementVector[0].binaryEqual(updateArrAsObj.getField("0"))); + ASSERT(elementVector[1].binaryEqual(updateArrAsObj.getField("1"))); + + auto verifiedVector = arrElem.ArrayVerifyIndexes(); + ASSERT_EQ(verifiedVector.size(), 2); + + // The two vectors should have the same elements. + ASSERT(elementVector[0].binaryEqual(verifiedVector[0])); + ASSERT(elementVector[1].binaryEqual(verifiedVector[1])); +} + +TEST(BSONElement, ArrayVerifyIndexesThrowsOnInvalidArrayIndexes) { + // Create our invalid array by creating a BSONObj with non contiguous array indexes that is then + // passed to the BSONArray ctor. + BSONObj updateArrAsObj = BSON("0" + << "foo" + << "2" + << "bar"); + BSONArray updateArr(updateArrAsObj); + BSONObj parentObj = BSON("badArray" << updateArr); + auto arrElem = parentObj.getField("badArray"); + + // The regular 'Array()' will not throw, and instead create an EOO BSONElement at index 1. + auto elementVector = arrElem.Array(); + ASSERT_EQ(elementVector.size(), 3); + + ASSERT(elementVector[0].binaryEqual(updateArrAsObj.getField("0"))); + ASSERT(elementVector[1].binaryEqual(BSONElement())); + ASSERT(elementVector[2].binaryEqual(updateArrAsObj.getField("2"))); + + // 'ArrayVerifyIndexes()', on the other hand, will throw. + ASSERT_THROWS(arrElem.ArrayVerifyIndexes(), ExceptionFor<ErrorCodes::BadValue>); +} + } // namespace } // namespace mongo diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp index 7b5fa7dae04..f0c5946565c 100644 --- a/src/mongo/bson/bsonobj.cpp +++ b/src/mongo/bson/bsonobj.cpp @@ -138,41 +138,71 @@ BSONObj BSONObj::getOwned(const BSONObj& obj) { return obj.getOwned(); } -BSONObj BSONObj::redact(bool onlyEncryptedFields) const { +BSONObj BSONObj::redact(RedactLevel level, + std::function<std::string(const BSONElement&)> fieldNameRedactor) const { _validateUnownedSize(objsize()); // Helper to get an "internal function" to be able to do recursion struct redactor { - void appendRedactedElem(BSONObjBuilder& builder, const BSONElement& e, bool appendMask) { + void appendRedactedElem(BSONObjBuilder& builder, + const StringData& fieldNameString, + bool appendMask) { if (appendMask) { - builder.append(e.fieldNameStringData(), "###"_sd); + builder.append(fieldNameString, "###"_sd); } else { - builder.appendNull(e.fieldNameStringData()); + builder.appendNull(fieldNameString); } } void operator()(BSONObjBuilder& builder, const BSONObj& obj, bool appendMask, - bool onlyEncryptedFields) { + RedactLevel level, + std::function<std::string(const BSONElement&)> fieldNameRedactor) { for (BSONElement e : obj) { + StringData fieldNameString; + // Temporarily allocated string that must live long enough to be copied by builder. + std::string tempString; + if (!fieldNameRedactor) { + fieldNameString = e.fieldNameStringData(); + } else { + tempString = fieldNameRedactor(e); + fieldNameString = {tempString}; + } if (e.type() == Object) { - BSONObjBuilder subBuilder = builder.subobjStart(e.fieldNameStringData()); - operator()(subBuilder, e.Obj(), appendMask, onlyEncryptedFields); + BSONObjBuilder subBuilder = builder.subobjStart(fieldNameString); + operator()(subBuilder, e.Obj(), appendMask, level, fieldNameRedactor); subBuilder.done(); } else if (e.type() == Array) { - BSONObjBuilder subBuilder = builder.subarrayStart(e.fieldNameStringData()); - operator()(subBuilder, e.Obj(), appendMask, onlyEncryptedFields); + BSONObjBuilder subBuilder = builder.subarrayStart(fieldNameString); + operator()(subBuilder, e.Obj(), appendMask, level, fieldNameRedactor); subBuilder.done(); } else { - if (onlyEncryptedFields) { - if (e.type() == BinData && e.binDataType() == BinDataType::Encrypt) { - appendRedactedElem(builder, e, appendMask); - } else { - builder.append(e); + // SERVER-79068 Templatizing this could be a good opportunity for performance + // improvements. + switch (level) { + case RedactLevel::all: { + appendRedactedElem(builder, fieldNameString, appendMask); + break; + } + case RedactLevel::encryptedAndSensitive: { + if (e.type() == BinData && + (e.binDataType() == BinDataType::Encrypt || + e.binDataType() == BinDataType::Sensitive)) { + appendRedactedElem(builder, fieldNameString, appendMask); + } else { + builder.append(e); + } + break; + } + case RedactLevel::sensitiveOnly: { + if (e.type() == BinData && e.binDataType() == BinDataType::Sensitive) { + appendRedactedElem(builder, fieldNameString, appendMask); + } else { + builder.append(e); + } + break; } - } else { - appendRedactedElem(builder, e, appendMask); } } } @@ -181,7 +211,7 @@ BSONObj BSONObj::redact(bool onlyEncryptedFields) const { try { BSONObjBuilder builder; - redactor()(builder, *this, /*appendMask=*/true, onlyEncryptedFields); + redactor()(builder, *this, /*appendMask=*/true, level, fieldNameRedactor); return builder.obj(); } catch (const ExceptionFor<ErrorCodes::BSONObjectTooLarge>&) { } @@ -191,7 +221,7 @@ BSONObj BSONObj::redact(bool onlyEncryptedFields) const { // we use BSONType::jstNull, which ensures the redacted object will not be larger than the // original. BSONObjBuilder builder; - redactor()(builder, *this, /*appendMask=*/false, onlyEncryptedFields); + redactor()(builder, *this, /*appendMask=*/false, level, fieldNameRedactor); return builder.obj(); } @@ -298,10 +328,6 @@ BSONObj BSONObj::jsonStringBuffer(JsonStringFormat format, } } -bool BSONObj::valid() const { - return validateBSON(objdata(), objsize()).isOK(); -} - int BSONObj::woCompare(const BSONObj& r, const Ordering& o, ComparisonRulesSet rules, diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h index a5270adfd25..5793f9e5212 100644 --- a/src/mongo/bson/bsonobj.h +++ b/src/mongo/bson/bsonobj.h @@ -264,10 +264,14 @@ public: */ BSONObj copy() const; + enum class RedactLevel : int8_t { all, encryptedAndSensitive, sensitiveOnly }; + /** * @return a new full (and owned) redacted copy of the object. */ - BSONObj redact(bool onlyEncryptedFields = false) const; + BSONObj redact( + RedactLevel level = RedactLevel::all, + std::function<std::string(const BSONElement&)> fieldNameRedactor = nullptr) const; /** * Readable representation of a BSON object in an extended JSON-style notation. @@ -638,11 +642,6 @@ public: bool hasFieldNames() const; /** - * Returns true if this object is valid and returns false otherwise. - */ - bool valid() const; - - /** * add all elements of the object to the specified vector */ void elems(std::vector<BSONElement>&) const; diff --git a/src/mongo/bson/bsonobjbuilder.cpp b/src/mongo/bson/bsonobjbuilder.cpp index e8e50fd38a7..096ba297e99 100644 --- a/src/mongo/bson/bsonobjbuilder.cpp +++ b/src/mongo/bson/bsonobjbuilder.cpp @@ -182,7 +182,7 @@ Derived& BSONObjBuilderBase<Derived, B>::appendMaxForType(StringData fieldName, template <class Derived, class B> Derived& BSONObjBuilderBase<Derived, B>::appendDate(StringData fieldName, Date_t dt) { _b.appendNum((char)Date); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum(dt.toMillisSinceEpoch()); return static_cast<Derived&>(*this); } diff --git a/src/mongo/bson/bsonobjbuilder.h b/src/mongo/bson/bsonobjbuilder.h index be826d5a824..008ad7953fb 100644 --- a/src/mongo/bson/bsonobjbuilder.h +++ b/src/mongo/bson/bsonobjbuilder.h @@ -153,7 +153,7 @@ public: // do not append eoo, that would corrupt us. the builder auto appends when done() is called. verify(!e.eoo()); _b.appendNum((char)e.type()); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendBuf((void*)e.value(), e.valuesize()); return static_cast<Derived&>(*this); } @@ -161,7 +161,7 @@ public: /** add a subobject as a member */ Derived& append(StringData fieldName, BSONObj subObj) { _b.appendNum((char)Object); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendBuf((void*)subObj.objdata(), subObj.objsize()); return static_cast<Derived&>(*this); } @@ -176,7 +176,7 @@ public: verify(size > 4 && size < 100000000); _b.appendNum((char)Object); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendBuf((void*)objdata, size); return static_cast<Derived&>(*this); } @@ -194,7 +194,7 @@ public: */ B& subobjStart(StringData fieldName) { _b.appendNum((char)Object); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); return _b; } @@ -203,7 +203,7 @@ public: */ Derived& appendArray(StringData fieldName, const BSONObj& subObj) { _b.appendNum((char)Array); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendBuf((void*)subObj.objdata(), subObj.objsize()); return static_cast<Derived&>(*this); @@ -216,14 +216,14 @@ public: the subarray's body */ B& subarrayStart(StringData fieldName) { _b.appendNum((char)Array); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); return _b; } /** Append a boolean element */ Derived& appendBool(StringData fieldName, int val) { _b.appendNum((char)Bool); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum((char)(val ? 1 : 0)); return static_cast<Derived&>(*this); } @@ -233,7 +233,7 @@ public: Derived& append(StringData fieldName, const T& n) { constexpr BSONType type = BSONObjAppendFormat<T>::value; _b.appendNum(static_cast<char>(type)); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); if constexpr (type == Bool) { _b.appendNum(static_cast<char>(n)); } else if constexpr (type == NumberInt) { @@ -284,7 +284,7 @@ public: */ Derived& appendOID(StringData fieldName, OID* oid = nullptr, bool generateIfBlank = false) { _b.appendNum((char)jstOID); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); if (oid) _b.appendBuf(oid->view().view(), OID::kOIDSize); else { @@ -305,7 +305,7 @@ public: */ Derived& append(StringData fieldName, OID oid) { _b.appendNum((char)jstOID); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendBuf(oid.view().view(), OID::kOIDSize); return static_cast<Derived&>(*this); } @@ -324,7 +324,7 @@ public: */ Derived& appendTimeT(StringData fieldName, time_t dt) { _b.appendNum((char)Date); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum(static_cast<unsigned long long>(dt) * 1000); return static_cast<Derived&>(*this); } @@ -343,9 +343,9 @@ public: */ Derived& appendRegex(StringData fieldName, StringData regex, StringData options = "") { _b.appendNum((char)RegEx); - _b.appendStr(fieldName); - _b.appendStr(regex); - _b.appendStr(options); + _b.appendCStr(fieldName); + _b.appendCStr(regex); + _b.appendCStr(options); return static_cast<Derived&>(*this); } @@ -356,9 +356,9 @@ public: Derived& appendCode(StringData fieldName, StringData code) { _b.appendNum((char)Code); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum((int)code.size() + 1); - _b.appendStr(code); + _b.appendStrBytesAndNul(code); return static_cast<Derived&>(*this); } @@ -370,7 +370,7 @@ public: @param sz size includes terminating null character */ Derived& append(StringData fieldName, const char* str, int sz) { _b.appendNum((char)String); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum((int)sz); _b.appendBuf(str, sz); @@ -383,17 +383,17 @@ public: /** Append a string element */ Derived& append(StringData fieldName, StringData str) { _b.appendNum((char)String); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum((int)str.size() + 1); - _b.appendStr(str, true); + _b.appendStrBytesAndNul(str); return static_cast<Derived&>(*this); } Derived& appendSymbol(StringData fieldName, StringData symbol) { _b.appendNum((char)Symbol); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum((int)symbol.size() + 1); - _b.appendStr(symbol); + _b.appendStrBytesAndNul(symbol); return static_cast<Derived&>(*this); } @@ -404,7 +404,7 @@ public: /** Append a Null element to the object */ Derived& appendNull(StringData fieldName) { _b.appendNum((char)jstNULL); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); return static_cast<Derived&>(*this); } @@ -412,13 +412,13 @@ public: // Append an element that is less than all other keys. Derived& appendMinKey(StringData fieldName) { _b.appendNum((char)MinKey); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); return static_cast<Derived&>(*this); } // Append an element that is greater than all other keys. Derived& appendMaxKey(StringData fieldName) { _b.appendNum((char)MaxKey); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); return static_cast<Derived&>(*this); } @@ -439,9 +439,9 @@ public: */ Derived& appendDBRef(StringData fieldName, StringData ns, const OID& oid) { _b.appendNum((char)DBRef); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum((int)ns.size() + 1); - _b.appendStr(ns); + _b.appendStrBytesAndNul(ns); _b.appendBuf(oid.view().view(), OID::kOIDSize); return static_cast<Derived&>(*this); @@ -460,7 +460,7 @@ public: */ Derived& appendBinData(StringData fieldName, int len, BinDataType type, const void* data) { _b.appendNum((char)BinData); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum(len); _b.appendNum((char)type); _b.appendBuf(data, len); @@ -480,7 +480,7 @@ public: */ Derived& appendBinDataArrayDeprecated(const char* fieldName, const void* data, int len) { _b.appendNum((char)BinData); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum(len + 4); _b.appendNum((char)0x2); _b.appendNum(len); @@ -494,10 +494,10 @@ public: */ Derived& appendCodeWScope(StringData fieldName, StringData code, const BSONObj& scope) { _b.appendNum((char)CodeWScope); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); _b.appendNum((int)(4 + 4 + code.size() + 1 + scope.objsize())); _b.appendNum((int)code.size() + 1); - _b.appendStr(code); + _b.appendStrBytesAndNul(code); _b.appendBuf((void*)scope.objdata(), scope.objsize()); return static_cast<Derived&>(*this); @@ -509,7 +509,7 @@ public: Derived& appendUndefined(StringData fieldName) { _b.appendNum((char)Undefined); - _b.appendStr(fieldName); + _b.appendCStr(fieldName); return static_cast<Derived&>(*this); } diff --git a/src/mongo/bson/bsontypes.cpp b/src/mongo/bson/bsontypes.cpp index 8be3c5e1d04..1f9112f2dc4 100644 --- a/src/mongo/bson/bsontypes.cpp +++ b/src/mongo/bson/bsontypes.cpp @@ -199,6 +199,8 @@ const char* typeName(BinDataType type) { return "encrypt"; case Column: return "column"; + case Sensitive: + return "sensitive"; case bdtCustom: return "Custom"; default: @@ -217,6 +219,7 @@ bool isValidBinDataType(int type) { case Encrypt: case Column: case bdtCustom: + case Sensitive: return true; default: return false; diff --git a/src/mongo/bson/bsontypes.h b/src/mongo/bson/bsontypes.h index 0dc2fe8a4ab..5204f1c3f72 100644 --- a/src/mongo/bson/bsontypes.h +++ b/src/mongo/bson/bsontypes.h @@ -198,8 +198,9 @@ enum BinDataType { bdtUUID = 3, /* deprecated */ newUUID = 4, /* language-independent UUID format across all drivers */ MD5Type = 5, - Encrypt = 6, /* encryption placeholder or encrypted data */ - Column = 7, /* compressed column */ + Encrypt = 6, /* encryption placeholder or encrypted data */ + Column = 7, /* compressed column */ + Sensitive = 8, /* data that should be redacted and protected from unnecessary exposure */ bdtCustom = 128 }; diff --git a/src/mongo/bson/simple_bsonobj_comparator.h b/src/mongo/bson/simple_bsonobj_comparator.h index ed08cbdd939..b073491747f 100644 --- a/src/mongo/bson/simple_bsonobj_comparator.h +++ b/src/mongo/bson/simple_bsonobj_comparator.h @@ -95,6 +95,10 @@ public: }; }; +inline auto simpleHash(const BSONObj& obj) { + return SimpleBSONObjComparator::kInstance.hash(obj); +} + /** * A set of BSONObjs that performs comparisons with simple binary semantics. */ diff --git a/src/mongo/bson/timestamp.h b/src/mongo/bson/timestamp.h index 3d0befd7450..28bf9cb31cd 100644 --- a/src/mongo/bson/timestamp.h +++ b/src/mongo/bson/timestamp.h @@ -139,7 +139,7 @@ public: // No endian conversions needed, since we store in-memory representation // in little endian format, regardless of target endian. builder.appendNum(static_cast<char>(bsonTimestamp)); - builder.appendStr(fieldName); + builder.appendCStr(fieldName); builder.appendNum(asULL()); } BSONObj toBSON() const; diff --git a/src/mongo/bson/util/builder.h b/src/mongo/bson/util/builder.h index 5d7d2d5673c..883b19a0ffe 100644 --- a/src/mongo/bson/util/builder.h +++ b/src/mongo/bson/util/builder.h @@ -57,6 +57,7 @@ #include "mongo/util/itoa.h" #include "mongo/util/shared_buffer.h" #include "mongo/util/shared_buffer_fragment.h" +#include "mongo/util/str_basic.h" namespace mongo { @@ -325,7 +326,7 @@ public: @return point to region that was skipped. pointer may change later (on realloc), so for immediate use only */ - char* skip(int n) { + char* skip(size_t n) { return grow(n); } @@ -394,7 +395,7 @@ public: } void appendBuf(const void* src, size_t len) { if (len) - memcpy(grow((int)len), src, len); + memcpy(grow(len), src, len); } template <class T> @@ -402,9 +403,36 @@ public: appendBuf(&s, sizeof(T)); } - void appendStr(StringData str, bool includeEndingNull = true) { - const int len = str.size() + (includeEndingNull ? 1 : 0); - str.copyTo(grow(len), includeEndingNull); + /** + * Appends the raw bytes of str with no NUL terminator. + */ + void appendStrBytes(StringData str) { + str.copy(grow(str.size()), str.size()); + } + + /** + * Appends the raw bytes of str followed by a final NUL byte. + * + * WARNING: only use this method for formats with explicit string lengths where the NUL byte is + * not used to find the end. This method does not check for embedded NUL bytes, so they can + * trick a parser into thinking the string has ended. Use appendCStr() instead for that use + * case. + */ + void appendStrBytesAndNul(StringData str) { + auto dest = grow(str.size() + 1); + dest += str.copy(dest, str.size()); + *dest = '\0'; + } + + /** + * Appends the raw bytes of str followed by a final NUL byte, throwing if str already has an + * embedded NUL byte. + * + * This method is intended to pair with BufReader::readCStr() on the parse side. + */ + void appendCStr(StringData str) { + str::uassertNoEmbeddedNulBytes(str); + appendStrBytesAndNul(str); } /** Returns the length of data in the current buffer */ @@ -423,8 +451,8 @@ public: } /* returns the pre-grow write position */ - char* grow(int by) { - if (MONGO_likely(by <= _end - _nextByte)) { + char* grow(size_t by) { + if (MONGO_likely(by <= static_cast<size_t>(_end - _nextByte))) { char* oldNextByte = _nextByte; _nextByte += by; return oldNextByte; @@ -751,7 +779,7 @@ public: } void append(StringData str) { - str.copyTo(_buf.grow(str.size()), false); + _buf.appendStrBytes(str); } void reset(int maxSize = 0) { diff --git a/src/mongo/bson/util/builder_test.cpp b/src/mongo/bson/util/builder_test.cpp index a4ed4c87115..0ad7518ffe5 100644 --- a/src/mongo/bson/util/builder_test.cpp +++ b/src/mongo/bson/util/builder_test.cpp @@ -38,12 +38,43 @@ TEST(Builder, String1) { ASSERT_EQUALS(small, "eliot"); BufBuilder bb; - bb.appendStr(small); + bb.appendCStr(small); + + ASSERT_EQUALS(bb.len(), small.size() + 1); + ASSERT_EQUALS(bb.buf()[small.size()], 0); ASSERT_EQUALS(0, strcmp(bb.buf(), "eliot")); ASSERT_EQUALS(0, strcmp("eliot", bb.buf())); } +TEST(Builder, StringNulByteHandling) { + auto hasNulByte = "hello\0world"_sd; + + { + // appendCStr() throws without changing bb; + BufBuilder bb; + ASSERT_THROWS_CODE(bb.appendCStr(hasNulByte), DBException, 9527900); + ASSERT_EQ(bb.len(), 0); + } + + { + // appendStrBytes appends embedded NUL without terminator. + BufBuilder bb; + bb.appendStrBytes(hasNulByte); + ASSERT_EQ(StringData(bb.buf(), bb.len()), hasNulByte); + } + + { + // appendStrBytesAndNul appends embedded NUL and NUL terminator. + BufBuilder bb; + bb.appendStrBytesAndNul(hasNulByte); + // Since hasNulByte points to a string literal, we know that + // *(hasNulByte.data() + hasNulByte.size()) is valid and == '\0' + ASSERT_EQ(StringData(bb.buf(), bb.len()), + StringData(hasNulByte.rawData(), hasNulByte.size() + 1)); + } +} + TEST(Builder, StringBuilderAddress) { const void* longPtr = reinterpret_cast<const void*>(-1); const void* shortPtr = reinterpret_cast<const void*>(static_cast<uintptr_t>(0xDEADBEEF)); diff --git a/src/mongo/bson/util/simple8b_test.cpp b/src/mongo/bson/util/simple8b_test.cpp index c612c221aec..df8c849f73b 100644 --- a/src/mongo/bson/util/simple8b_test.cpp +++ b/src/mongo/bson/util/simple8b_test.cpp @@ -1437,6 +1437,7 @@ TEST(Simple8b, ResetRLEAfterLargeValue) { // The second block should be an RLE block ASSERT_GT(size, 16); - uint64_t secondBlock = *((uint64_t*)(data.get() + sizeof(uint64_t))); + uint64_t secondBlock = + ConstDataView(data.get() + sizeof(uint64_t)).read<LittleEndian<uint64_t>>(); ASSERT_TRUE((secondBlock & kBaseSelectorMask) == kRleSelector); } |
