diff options
author | Billy Donahue <billy.donahue@mongodb.com> | 2020-09-09 21:32:04 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-09-15 00:42:05 +0000 |
commit | 58828b0ce9556ee9cb38c484d1226663a0dcd993 (patch) | |
tree | dfea448799d9bd4328114199a9767dd18d045be3 /src/mongo | |
parent | 22a77301a5b63b9bb7ef6dd73eabb4865c63a921 (diff) | |
download | mongo-58828b0ce9556ee9cb38c484d1226663a0dcd993.tar.gz |
SERVER-43909 clarify and repair util/hex.h API
- hexblob namespace
- Throwy hexblob::decode (nee fromHex)
- StringData overloads of hex codec ops
- add unsignedHex<T> and zeroPaddedHex<T>
Diffstat (limited to 'src/mongo')
40 files changed, 308 insertions, 360 deletions
diff --git a/src/mongo/bson/bson_validate_fuzzer.cpp b/src/mongo/bson/bson_validate_fuzzer.cpp index f79b50c84da..081354218b1 100644 --- a/src/mongo/bson/bson_validate_fuzzer.cpp +++ b/src/mongo/bson/bson_validate_fuzzer.cpp @@ -45,7 +45,7 @@ extern "C" int LLVMFuzzerTestOneInput(const char* Data, size_t Size) { LOGV2_DEBUG(4496700, 2, "validateBSON return code has changed", - "input"_attr = toHex(Data, Size), + "input"_attr = hexblob::encode(Data, Size), "ret"_attr = ret, "oldRet"_attr = oldRet); // We trust the old implementation's OK, so dump the object to hopefully give a hint. diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp index 1abc796f09b..1191f82ecf2 100644 --- a/src/mongo/bson/bsonelement.cpp +++ b/src/mongo/bson/bsonelement.cpp @@ -859,25 +859,21 @@ void BSONElement::toString( const char* data = binDataClean(len); // If the BinData is a correctly sized newUUID, display it as such. if (binDataType() == newUUID && len == 16) { + using namespace fmt::literals; + StringData sd(data, len); // 4 Octets - 2 Octets - 2 Octets - 2 Octets - 6 Octets - s << "UUID(\""; - s << toHexLower(&data[0], 4); - s << "-"; - s << toHexLower(&data[4], 2); - s << "-"; - s << toHexLower(&data[6], 2); - s << "-"; - s << toHexLower(&data[8], 2); - s << "-"; - s << toHexLower(&data[10], 6); - s << "\")"; + s << "UUID(\"{}-{}-{}-{}-{}\")"_format(hexblob::encodeLower(sd.substr(0, 4)), + hexblob::encodeLower(sd.substr(4, 2)), + hexblob::encodeLower(sd.substr(6, 2)), + hexblob::encodeLower(sd.substr(8, 2)), + hexblob::encodeLower(sd.substr(10, 6))); break; } s << "BinData(" << binDataType() << ", "; if (!full && len > 80) { - s << toHex(data, 70) << "...)"; + s << hexblob::encode(data, 70) << "...)"; } else { - s << toHex(data, len) << ")"; + s << hexblob::encode(data, len) << ")"; } } break; diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp index 27963db4611..48a3102b605 100644 --- a/src/mongo/bson/bsonobj.cpp +++ b/src/mongo/bson/bsonobj.cpp @@ -90,9 +90,8 @@ int compareObjects(const BSONObj& firstObj, void BSONObj::_assertInvalid(int maxSize) const { StringBuilder ss; int os = objsize(); - ss << "BSONObj size: " << os << " (0x" << integerToHex(os) << ") is invalid. " - << "Size must be between 0 and " << BSONObjMaxInternalSize << "(" - << (maxSize / (1024 * 1024)) << "MB)"; + ss << "BSONObj size: " << os << " (0x" << unsignedHex(os) << ") is invalid. " + << "Size must be between 0 and " << maxSize << "(" << (maxSize / (1024 * 1024)) << "MB)"; try { BSONElement e = firstElement(); ss << " First element: " << e.toString(); diff --git a/src/mongo/bson/json.cpp b/src/mongo/bson/json.cpp index fa9441ce38d..a0ba4f0ec4c 100644 --- a/src/mongo/bson/json.cpp +++ b/src/mongo/bson/json.cpp @@ -477,13 +477,7 @@ Status JParse::binaryObject(StringData fieldName, BSONObjBuilder& builder) { "single byte"); } - // The fromHex function returns a signed char, but the highest - // BinDataType value is 128, which can only be represented as an - // unsigned char. If we don't coerce it to an unsigned char before - // wrapping it in a BinDataType (currently implicitly a signed - // integer), we get undefined behavior. - const auto binDataTypeNumeric = - static_cast<unsigned char>(uassertStatusOK(fromHex(binDataType))); + const auto binDataTypeNumeric = hexblob::decodePair(binDataType); builder.appendBinData( fieldName, binData.length(), BinDataType(binDataTypeNumeric), binData.data()); @@ -1305,8 +1299,8 @@ Status JParse::chars(std::string* result, const char* terminalSet, const char* a if (!isHexString(StringData(q, 4))) { return parseError("Expecting 4 hex digits"); } - unsigned char first = uassertStatusOK(fromHex(q)); - unsigned char second = uassertStatusOK(fromHex(q += 2)); + unsigned char first = hexblob::decodePair(StringData(q, 2)); + unsigned char second = hexblob::decodePair(StringData(q += 2, 2)); const std::string& utf8str = encodeUTF8(first, second); for (unsigned int i = 0; i < utf8str.size(); i++) { result->push_back(utf8str[i]); diff --git a/src/mongo/bson/oid.cpp b/src/mongo/bson/oid.cpp index ebceefa04fa..70c8b056a70 100644 --- a/src/mongo/bson/oid.cpp +++ b/src/mongo/bson/oid.cpp @@ -36,6 +36,7 @@ #include <memory> #include "mongo/base/init.h" +#include "mongo/base/string_data.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/platform/atomic_word.h" #include "mongo/platform/random.h" @@ -149,11 +150,8 @@ void OID::initFromTermNumber(int64_t term) { void OID::init(const std::string& s) { verify(s.size() == 24); - const char* p = s.c_str(); - for (std::size_t i = 0; i < kOIDSize; i++) { - _data[i] = uassertStatusOK(fromHex(p)); - p += 2; - } + std::string blob = hexblob::decode(StringData(s).substr(0, 2 * kOIDSize)); + std::copy(blob.begin(), blob.end(), _data); } void OID::init(Date_t date, bool max) { @@ -167,11 +165,11 @@ time_t OID::asTimeT() const { } std::string OID::toString() const { - return toHexLower(_data, kOIDSize); + return hexblob::encodeLower(_data, kOIDSize); } std::string OID::toIncString() const { - return toHexLower(getIncrement().bytes, kIncrementSize); + return hexblob::encodeLower(getIncrement().bytes, kIncrementSize); } } // namespace mongo diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp index d9153cb3c02..ae8322c086c 100644 --- a/src/mongo/client/mongo_uri.cpp +++ b/src/mongo/client/mongo_uri.cpp @@ -46,6 +46,7 @@ #include "mongo/db/auth/sasl_command_constants.h" #include "mongo/db/namespace_string.h" #include "mongo/stdx/utility.h" +#include "mongo/util/assert_util.h" #include "mongo/util/dns_name.h" #include "mongo/util/dns_query.h" #include "mongo/util/hex.h" @@ -85,24 +86,22 @@ void mongo::uriEncode(std::ostream& ss, StringData toEncode, StringData passthro mongo::StatusWith<std::string> mongo::uriDecode(StringData toDecode) { StringBuilder out; for (size_t i = 0; i < toDecode.size(); ++i) { - const char c = toDecode[i]; + char c = toDecode[i]; if (c == '%') { if (i + 2 >= toDecode.size()) { return Status(ErrorCodes::FailedToParse, "Encountered partial escape sequence at end of string"); } - auto swHex = fromHex(toDecode.substr(i + 1, 2)); - if (swHex.isOK()) { - out << swHex.getValue(); - } else { + try { + c = hexblob::decodePair(toDecode.substr(i + 1, 2)); + } catch (const ExceptionFor<ErrorCodes::FailedToParse>&) { return Status(ErrorCodes::Error(51040), "The characters after the % do not form a hex value. Please escape " "the % or pass a valid hex value. "); } i += 2; - } else { - out << c; } + out << c; } return out.str(); } diff --git a/src/mongo/crypto/hash_block.h b/src/mongo/crypto/hash_block.h index 880b9a6832d..da2c3c3be6c 100644 --- a/src/mongo/crypto/hash_block.h +++ b/src/mongo/crypto/hash_block.h @@ -125,12 +125,12 @@ public: } static StatusWith<HashBlock> fromHexStringNoThrow(StringData hex) { - if (!isValidHex(hex)) { + if (!hexblob::validate(hex)) { return {ErrorCodes::BadValue, "Hash input is not a hex string"}; } BufBuilder buf; - mongo::fromHexString(hex, &buf); + hexblob::decode(hex, &buf); return fromBuffer(reinterpret_cast<const uint8_t*>(buf.buf()), buf.len()); } @@ -274,7 +274,7 @@ public: * Hex encoded hash block. */ std::string toHexString() const { - return toHex(_hash.data(), _hash.size()); + return hexblob::encode(_hash.data(), _hash.size()); } bool operator==(const HashBlock& other) const { diff --git a/src/mongo/db/cst/c_node.cpp b/src/mongo/db/cst/c_node.cpp index b1587b5efee..9bc64646e79 100644 --- a/src/mongo/db/cst/c_node.cpp +++ b/src/mongo/db/cst/c_node.cpp @@ -96,7 +96,7 @@ auto printValue(const T& payload) { }, [](const UserBinary& userBinary) { return "<UserBinary "s + typeName(userBinary.type) + ", " + - toHex(userBinary.data, userBinary.length) + ">"; + hexblob::encode(userBinary.data, userBinary.length) + ">"; }, [](const UserUndefined& userUndefined) { return "<UserUndefined>"s; }, [](const UserObjectId& userObjectId) { diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 26d598c6b08..d0671be2d00 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -333,7 +333,7 @@ void CurOp::reportCurrentOpForClient(OperationContext* opCtx, /** This branch becomes useful again with SERVER-44091 for (const auto& frame : diagnostic->makeStackTrace().frames) { BSONObjBuilder backtraceObj(backtraceBuilder.subobjStart()); - backtraceObj.append("addr", integerToHex(frame.instructionOffset)); + backtraceObj.append("addr", unsignedHex(frame.instructionOffset)); backtraceObj.append("path", frame.objectPath); } */ @@ -860,9 +860,9 @@ string OpDebug::report(OperationContext* opCtx, const SingleThreadedLockStats* l OPDEBUG_TOSTRING_HELP(nreturned); if (queryHash) { - s << " queryHash:" << unsignedIntToFixedLengthHex(*queryHash); + s << " queryHash:" << zeroPaddedHex(*queryHash); invariant(planCacheKey); - s << " planCacheKey:" << unsignedIntToFixedLengthHex(*planCacheKey); + s << " planCacheKey:" << zeroPaddedHex(*planCacheKey); } if (!errInfo.isOK()) { @@ -1029,9 +1029,9 @@ void OpDebug::report(OperationContext* opCtx, OPDEBUG_TOATTR_HELP(nreturned); if (queryHash) { - pAttrs->addDeepCopy("queryHash", unsignedIntToFixedLengthHex(*queryHash)); + pAttrs->addDeepCopy("queryHash", zeroPaddedHex(*queryHash)); invariant(planCacheKey); - pAttrs->addDeepCopy("planCacheKey", unsignedIntToFixedLengthHex(*planCacheKey)); + pAttrs->addDeepCopy("planCacheKey", zeroPaddedHex(*planCacheKey)); } if (!errInfo.isOK()) { @@ -1156,9 +1156,9 @@ void OpDebug::append(OperationContext* opCtx, OPDEBUG_APPEND_NUMBER(b, nreturned); if (queryHash) { - b.append("queryHash", unsignedIntToFixedLengthHex(*queryHash)); + b.append("queryHash", zeroPaddedHex(*queryHash)); invariant(planCacheKey); - b.append("planCacheKey", unsignedIntToFixedLengthHex(*planCacheKey)); + b.append("planCacheKey", zeroPaddedHex(*planCacheKey)); } { @@ -1409,12 +1409,12 @@ std::function<BSONObj(ProfileFilter::Args)> OpDebug::appendStaged(StringSet requ addIfNeeded("queryHash", [](auto field, auto args, auto& b) { if (args.op.queryHash) { - b.append(field, unsignedIntToFixedLengthHex(*args.op.queryHash)); + b.append(field, zeroPaddedHex(*args.op.queryHash)); } }); addIfNeeded("planCacheKey", [](auto field, auto args, auto& b) { if (args.op.planCacheKey) { - b.append(field, unsignedIntToFixedLengthHex(*args.op.planCacheKey)); + b.append(field, zeroPaddedHex(*args.op.planCacheKey)); } }); diff --git a/src/mongo/db/exec/document_value/value.cpp b/src/mongo/db/exec/document_value/value.cpp index 7afef48204d..cb45afeda99 100644 --- a/src/mongo/db/exec/document_value/value.cpp +++ b/src/mongo/db/exec/document_value/value.cpp @@ -1203,8 +1203,7 @@ ostream& operator<<(ostream& out, const Value& val) { case BinData: return out << "BinData(" << val._storage.binDataType() << ", \"" - << toHex(val._storage.getString().rawData(), val._storage.getString().size()) - << "\")"; + << hexblob::encode(val._storage.getString()) << "\")"; case DBRef: return out << "DBRef(\"" << val._storage.getDBRef()->ns << "\", " diff --git a/src/mongo/db/fts/fts_index_format.cpp b/src/mongo/db/fts/fts_index_format.cpp index 1993f518115..a6f12f6cef5 100644 --- a/src/mongo/db/fts/fts_index_format.cpp +++ b/src/mongo/db/fts/fts_index_format.cpp @@ -201,7 +201,7 @@ void FTSIndexFormat::_appendIndexKey(KeyStringBuilder& keyString, } t; uint32_t seed = 0; MurmurHash3_x64_128(term.data(), term.size(), seed, t.hash); - string keySuffix = mongo::toHexLower(t.data, sizeof(t.data)); + string keySuffix = hexblob::encodeLower(t.data, sizeof(t.data)); invariant(termKeySuffixLengthV2 == keySuffix.size()); keyString.appendString(term.substr(0, termKeyPrefixLengthV2) + keySuffix); } diff --git a/src/mongo/db/pipeline/resume_token.cpp b/src/mongo/db/pipeline/resume_token.cpp index e6b876769f7..2272bf1d434 100644 --- a/src/mongo/db/pipeline/resume_token.cpp +++ b/src/mongo/db/pipeline/resume_token.cpp @@ -119,7 +119,7 @@ ResumeToken::ResumeToken(const ResumeTokenData& data) { data.documentKey.addToBsonObj(&builder, ""); auto keyObj = builder.obj(); KeyString::Builder encodedToken(KeyString::Version::V1, keyObj, Ordering::make(BSONObj())); - _hexKeyString = toHex(encodedToken.getBuffer(), encodedToken.getSize()); + _hexKeyString = hexblob::encode(encodedToken.getBuffer(), encodedToken.getSize()); const auto& typeBits = encodedToken.getTypeBits(); if (!typeBits.isAllZeros()) _typeBits = Value( @@ -146,10 +146,10 @@ ResumeTokenData ResumeToken::getData() const { uassert(ErrorCodes::FailedToParse, "resume token string was not a valid hex string", - isValidHex(_hexKeyString)); + hexblob::validate(_hexKeyString)); BufBuilder hexDecodeBuf; // Keep this in scope until we've decoded the bytes. - fromHexString(_hexKeyString, &hexDecodeBuf); + hexblob::decode(_hexKeyString, &hexDecodeBuf); BSONBinData keyStringBinData = BSONBinData(hexDecodeBuf.buf(), hexDecodeBuf.len(), BinDataType::BinDataGeneral); auto internalBson = KeyString::toBsonSafe(static_cast<const char*>(keyStringBinData.data), diff --git a/src/mongo/db/pipeline/resume_token_test.cpp b/src/mongo/db/pipeline/resume_token_test.cpp index 7f9e5010ab8..88be9cdc940 100644 --- a/src/mongo/db/pipeline/resume_token_test.cpp +++ b/src/mongo/db/pipeline/resume_token_test.cpp @@ -160,17 +160,17 @@ TEST(ResumeToken, FailsToDecodeInvalidKeyString) { const unsigned char nonsense[] = {165, 85, 77, 86, 255}; // Data of correct type, but empty. - const auto emptyToken = ResumeToken::parse(Document{{"_data"_sd, toHex(zeroes, 0)}}); + const auto emptyToken = ResumeToken::parse(Document{{"_data"_sd, hexblob::encode(zeroes, 0)}}); ASSERT_THROWS_CODE(emptyToken.getData(), AssertionException, 40649); // Data of correct type with a bunch of zeros. const auto zeroesToken = - ResumeToken::parse(Document{{"_data"_sd, toHex(zeroes, sizeof(zeroes))}}); + ResumeToken::parse(Document{{"_data"_sd, hexblob::encode(zeroes, sizeof(zeroes))}}); ASSERT_THROWS_CODE(zeroesToken.getData(), AssertionException, 50811); // Data of correct type with a bunch of nonsense. const auto nonsenseToken = - ResumeToken::parse(Document{{"_data"_sd, toHex(nonsense, sizeof(nonsense))}}); + ResumeToken::parse(Document{{"_data"_sd, hexblob::encode(nonsense, sizeof(nonsense))}}); ASSERT_THROWS_CODE(nonsenseToken.getData(), AssertionException, 50811); // Valid data, bad typeBits; note that an all-zeros typebits is valid so it is not tested here. @@ -183,8 +183,8 @@ TEST(ResumeToken, FailsToDecodeInvalidKeyString) { 60, // CType::kStringLike 55, // Non-null terminated }; - auto invalidStringToken = - ResumeToken::parse(Document{{"_data"_sd, toHex(invalidString, sizeof(invalidString))}}); + auto invalidStringToken = ResumeToken::parse( + Document{{"_data"_sd, hexblob::encode(invalidString, sizeof(invalidString))}}); // invalidStringToken.getData(); ASSERT_THROWS_WITH_CHECK( invalidStringToken.getData(), AssertionException, [](const AssertionException& exception) { diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp index d0deb055438..255a1fbd4f2 100644 --- a/src/mongo/db/query/explain.cpp +++ b/src/mongo/db/query/explain.cpp @@ -642,11 +642,11 @@ void Explain::generatePlannerInfo(PlanExecutor* exec, } if (queryHash) { - plannerBob.append("queryHash", unsignedIntToFixedLengthHex(*queryHash)); + plannerBob.append("queryHash", zeroPaddedHex(*queryHash)); } if (planCacheKeyHash) { - plannerBob.append("planCacheKey", unsignedIntToFixedLengthHex(*planCacheKeyHash)); + plannerBob.append("planCacheKey", zeroPaddedHex(*planCacheKeyHash)); } if (!extraInfo.isEmpty()) { @@ -910,8 +910,8 @@ void Explain::planCacheEntryToBSON(const PlanCacheEntry& entry, BSONObjBuilder* shapeBuilder.append("collation", entry.collation); } shapeBuilder.doneFast(); - out->append("queryHash", unsignedIntToFixedLengthHex(entry.queryHash)); - out->append("planCacheKey", unsignedIntToFixedLengthHex(entry.planCacheKey)); + out->append("queryHash", zeroPaddedHex(entry.queryHash)); + out->append("planCacheKey", zeroPaddedHex(entry.planCacheKey)); // Append whether or not the entry is active. out->append("isActive", entry.isActive); diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp index 4c61cac98cd..917f53b3261 100644 --- a/src/mongo/db/query/plan_cache.cpp +++ b/src/mongo/db/query/plan_cache.cpp @@ -461,8 +461,8 @@ PlanCache::NewEntryState PlanCache::getNewEntryState(const CanonicalQuery& query 1, "Creating inactive cache entry for query", "query"_attr = redact(query.toStringShort()), - "queryHash"_attr = unsignedIntToFixedLengthHex(queryHash), - "planCacheKey"_attr = unsignedIntToFixedLengthHex(planCacheKey), + "queryHash"_attr = zeroPaddedHex(queryHash), + "planCacheKey"_attr = zeroPaddedHex(planCacheKey), "newWorks"_attr = newWorks); res.shouldBeCreated = true; res.shouldBeActive = false; @@ -477,8 +477,8 @@ PlanCache::NewEntryState PlanCache::getNewEntryState(const CanonicalQuery& query 1, "Replacing active cache entry for query", "query"_attr = redact(query.toStringShort()), - "queryHash"_attr = unsignedIntToFixedLengthHex(queryHash), - "planCacheKey"_attr = unsignedIntToFixedLengthHex(planCacheKey), + "queryHash"_attr = zeroPaddedHex(queryHash), + "planCacheKey"_attr = zeroPaddedHex(planCacheKey), "oldWorks"_attr = oldEntry->works, "newWorks"_attr = newWorks); res.shouldBeCreated = true; @@ -489,8 +489,8 @@ PlanCache::NewEntryState PlanCache::getNewEntryState(const CanonicalQuery& query "Attempt to write to the planCache resulted in a noop, since there's already " "an active cache entry with a lower works value", "query"_attr = redact(query.toStringShort()), - "queryHash"_attr = unsignedIntToFixedLengthHex(queryHash), - "planCacheKey"_attr = unsignedIntToFixedLengthHex(planCacheKey), + "queryHash"_attr = zeroPaddedHex(queryHash), + "planCacheKey"_attr = zeroPaddedHex(planCacheKey), "newWorks"_attr = newWorks, "oldWorks"_attr = oldEntry->works); // There is already an active cache entry with a lower works value. @@ -512,8 +512,8 @@ PlanCache::NewEntryState PlanCache::getNewEntryState(const CanonicalQuery& query 1, "Increasing work value associated with cache entry", "query"_attr = redact(query.toStringShort()), - "queryHash"_attr = unsignedIntToFixedLengthHex(queryHash), - "planCacheKey"_attr = unsignedIntToFixedLengthHex(planCacheKey), + "queryHash"_attr = zeroPaddedHex(queryHash), + "planCacheKey"_attr = zeroPaddedHex(planCacheKey), "oldWorks"_attr = oldEntry->works, "increasedWorks"_attr = increasedWorks); oldEntry->works = increasedWorks; @@ -528,8 +528,8 @@ PlanCache::NewEntryState PlanCache::getNewEntryState(const CanonicalQuery& query 1, "Inactive cache entry for query is being promoted to active entry", "query"_attr = redact(query.toStringShort()), - "queryHash"_attr = unsignedIntToFixedLengthHex(queryHash), - "planCacheKey"_attr = unsignedIntToFixedLengthHex(planCacheKey), + "queryHash"_attr = zeroPaddedHex(queryHash), + "planCacheKey"_attr = zeroPaddedHex(planCacheKey), "oldWorks"_attr = oldEntry->works, "newWorks"_attr = newWorks); // We'll replace the old inactive entry with an active entry. diff --git a/src/mongo/db/repl/topology_coordinator.cpp b/src/mongo/db/repl/topology_coordinator.cpp index 60d4860220b..c74d23b5ff9 100644 --- a/src/mongo/db/repl/topology_coordinator.cpp +++ b/src/mongo/db/repl/topology_coordinator.cpp @@ -2447,9 +2447,9 @@ std::string TopologyCoordinator::_getUnelectableReasonString(const UnelectableRe LOGV2_FATAL(26011, "Invalid UnelectableReasonMask value 0x{value}", "Invalid UnelectableReasonMask value", - "value"_attr = integerToHex(ur)); + "value"_attr = unsignedHex(ur)); } - ss << " (mask 0x" << integerToHex(ur) << ")"; + ss << " (mask 0x" << unsignedHex(ur) << ")"; return ss; } diff --git a/src/mongo/db/storage/index_entry_comparison.cpp b/src/mongo/db/storage/index_entry_comparison.cpp index face7000937..a1b42051f15 100644 --- a/src/mongo/db/storage/index_entry_comparison.cpp +++ b/src/mongo/db/storage/index_entry_comparison.cpp @@ -229,10 +229,8 @@ Status buildDupKeyErrorStatus(const BSONObj& key, if (shouldHexEncode) { auto stringToEncode = keyValueElem.valueStringData(); - builderForErrmsg.append( - keyNameElem.fieldName(), - str::stream() << "0x" - << toHexLower(stringToEncode.rawData(), stringToEncode.size())); + builderForErrmsg.append(keyNameElem.fieldName(), + "0x" + hexblob::encodeLower(stringToEncode)); } else { builderForErrmsg.appendAs(keyValueElem, keyNameElem.fieldName()); } diff --git a/src/mongo/db/storage/index_entry_comparison_test.cpp b/src/mongo/db/storage/index_entry_comparison_test.cpp index 5ef646b36e0..cea1a912fce 100644 --- a/src/mongo/db/storage/index_entry_comparison_test.cpp +++ b/src/mongo/db/storage/index_entry_comparison_test.cpp @@ -79,8 +79,7 @@ TEST(IndexEntryComparison, BuildDupKeyErrorMessageIncludesCollationAndHexEncoded ASSERT(dupKeyStatus.reason().find("collation:") != std::string::npos); // Verify that the collation key is hex encoded in the error message. - std::string expectedHexEncoding = - "0x" + toHexLower(mockCollationKey.rawData(), mockCollationKey.size()); + std::string expectedHexEncoding = "0x" + hexblob::encodeLower(mockCollationKey); ASSERT(dupKeyStatus.reason().find(expectedHexEncoding) != std::string::npos); // But no hex encoding should have taken place inside the key attached to the extra error info. diff --git a/src/mongo/db/storage/key_string.cpp b/src/mongo/db/storage/key_string.cpp index 30025e8ea7d..96c94000ba1 100644 --- a/src/mongo/db/storage/key_string.cpp +++ b/src/mongo/db/storage/key_string.cpp @@ -2174,11 +2174,11 @@ Decimal128 adjustDecimalExponent(TypeBits::Reader* typeBits, Decimal128 num) { template <class BufferT> std::string BuilderBase<BufferT>::toString() const { - return toHex(getBuffer(), getSize()); + return hexblob::encode(getBuffer(), getSize()); } std::string Value::toString() const { - return toHex(getBuffer(), getSize()); + return hexblob::encode(getBuffer(), getSize()); } TypeBits& TypeBits::operator=(const TypeBits& tb) { diff --git a/src/mongo/db/storage/key_string_test.cpp b/src/mongo/db/storage/key_string_test.cpp index 831ac4f41fe..87dd9b5a934 100644 --- a/src/mongo/db/storage/key_string_test.cpp +++ b/src/mongo/db/storage/key_string_test.cpp @@ -139,7 +139,7 @@ void checkSizeWhileAppendingTypeBits(int numOfBitsUsedForType, T&& appendBitsFun // patterns, so including it here specifically. TEST(InvalidKeyStringTest, FuzzedCodeWithScopeNesting) { BufBuilder keyData; - fromHexString( + hexblob::decode( "aa00aa4200aafa00aa0200aa0a01aa02aa00aa4200aafa00aa0200aa0a01aa0200aa00aa4200aafa00aa0200aa" "0a01aa0200aa4200aafa00aa0200aa00aaaa00aa00aafa00aa0200aa3900aafa00aa0200aa00aa004200aafa00" "aaaafa00aa0200aa0a01aa0200aa4200aafa00aa0200aa00aaaa00aa00aafa00aa0200aa00aafa00aa0200aa00" @@ -358,7 +358,7 @@ TEST_F(KeyStringBuilderTest, ActualBytesDouble) { "[{toHex_ks_getBuffer_ks_getSize}]", "keyStringVersionToString_version"_attr = keyStringVersionToString(version), "ks_getSize"_attr = ks.getSize(), - "toHex_ks_getBuffer_ks_getSize"_attr = toHex(ks.getBuffer(), ks.getSize())); + "toHex_ks_getBuffer_ks_getSize"_attr = hexblob::encode(ks.getBuffer(), ks.getSize())); ASSERT_EQUALS(10U, ks.getSize()); @@ -371,7 +371,7 @@ TEST_F(KeyStringBuilderTest, ActualBytesDouble) { "80000000000000" // fractional bytes "04"; // kEnd - ASSERT_EQUALS(hex, toHex(ks.getBuffer(), ks.getSize())); + ASSERT_EQUALS(hex, hexblob::encode(ks.getBuffer(), ks.getSize())); ks.resetToKey(a, Ordering::make(BSON("a" << -1))); @@ -381,13 +381,13 @@ TEST_F(KeyStringBuilderTest, ActualBytesDouble) { // last byte (kEnd) doesn't get flipped string hexFlipped; for (size_t i = 0; i < hex.size() - 2; i += 2) { - char c = uassertStatusOK(fromHex(hex.c_str() + i)); + char c = hexblob::decodePair(StringData(hex).substr(i, 2)); c = ~c; - hexFlipped += toHex(&c, 1); + hexFlipped += hexblob::encode(StringData(&c, 1)); } hexFlipped += hex.substr(hex.size() - 2); - ASSERT_EQUALS(hexFlipped, toHex(ks.getBuffer(), ks.getSize())); + ASSERT_EQUALS(hexFlipped, hexblob::encode(ks.getBuffer(), ks.getSize())); } TEST_F(KeyStringBuilderTest, AllTypesSimple) { @@ -1642,8 +1642,8 @@ void checkKeyWithNByteOfTypeBits(KeyString::Version version, size_t n, bool allZ // Also test TypeBits::fromBuffer() BufReader bufReader(ks.getTypeBits().getBuffer(), typeBitsSize); KeyString::TypeBits newTypeBits = KeyString::TypeBits::fromBuffer(version, &bufReader); - ASSERT_EQ(toHex(newTypeBits.getBuffer(), newTypeBits.getSize()), - toHex(ks.getTypeBits().getBuffer(), ks.getTypeBits().getSize())); + ASSERT_EQ(hexblob::encode(newTypeBits.getBuffer(), newTypeBits.getSize()), + hexblob::encode(ks.getTypeBits().getBuffer(), ks.getTypeBits().getSize())); } TEST_F(KeyStringBuilderTest, KeysWithNBytesTypeBits) { diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp index 4d40231e32e..6046ab6e43e 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp @@ -1169,7 +1169,7 @@ protected: "WTIndex::updatePosition -- the new key ({newKey}) is less than the previous " "key ({prevKey}), which is a bug.", "WTIndex::updatePosition -- new key is less than previous key", - "newKey"_attr = redact(toHex(item.data, item.size)), + "newKey"_attr = redact(hexblob::encode(item.data, item.size)), "prevKey"_attr = redact(_key.toString())); // Crash when testing diagnostics are enabled. diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp index b3cc4c6dde7..367b88be4c2 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.cpp @@ -230,7 +230,7 @@ void WiredTigerRecoveryUnit::prepareUnitOfWork() { "preparing transaction at time: {prepareTimestamp}", "prepareTimestamp"_attr = _prepareTimestamp); - const std::string conf = "prepare_timestamp=" + integerToHex(_prepareTimestamp.asULL()); + const std::string conf = "prepare_timestamp=" + unsignedHex(_prepareTimestamp.asULL()); // Prepare the transaction. invariantWTOK(s->prepare_transaction(s, conf.c_str())); } @@ -360,13 +360,13 @@ void WiredTigerRecoveryUnit::_txnClose(bool commit) { invariant(_readAtTimestamp.isNull() || _commitTimestamp >= _readAtTimestamp); if (MONGO_likely(!doUntimestampedWritesForIdempotencyTests.shouldFail())) { - conf << "commit_timestamp=" << integerToHex(_commitTimestamp.asULL()) << ","; + conf << "commit_timestamp=" << unsignedHex(_commitTimestamp.asULL()) << ","; } _isTimestamped = true; } if (!_durableTimestamp.isNull()) { - conf << "durable_timestamp=" << integerToHex(_durableTimestamp.asULL()); + conf << "durable_timestamp=" << unsignedHex(_durableTimestamp.asULL()); } if (_mustBeTimestamped) { @@ -709,7 +709,7 @@ Status WiredTigerRecoveryUnit::setTimestamp(Timestamp timestamp) { return Status::OK(); } - const std::string conf = "commit_timestamp=" + integerToHex(timestamp.asULL()); + const std::string conf = "commit_timestamp=" + unsignedHex(timestamp.asULL()); auto rc = session->timestamp_transaction(session, conf.c_str()); if (rc == 0) { _isTimestamped = true; diff --git a/src/mongo/platform/decimal128_bson_test.cpp b/src/mongo/platform/decimal128_bson_test.cpp index 1f34da163d6..f0d0ffd506a 100644 --- a/src/mongo/platform/decimal128_bson_test.cpp +++ b/src/mongo/platform/decimal128_bson_test.cpp @@ -31,6 +31,7 @@ #include "mongo/platform/basic.h" +#include <algorithm> #include <array> #include <cmath> #include <memory> @@ -54,15 +55,9 @@ const std::string testData = initTestData(); using namespace mongo; BSONObj convertHexStringToBsonObj(StringData hexString) { - const char* p = hexString.rawData(); - size_t bufferSize = hexString.size() / 2; - auto buffer = SharedBuffer::allocate(bufferSize); - - for (unsigned int i = 0; i < bufferSize; i++) { - buffer.get()[i] = uassertStatusOK(fromHex(p)); - p += 2; - } - + std::string data = hexblob::decode(hexString); + auto buffer = SharedBuffer::allocate(data.size()); + std::copy(data.begin(), data.end(), buffer.get()); return BSONObj(std::move(buffer)); } diff --git a/src/mongo/rpc/op_msg.cpp b/src/mongo/rpc/op_msg.cpp index 46713bdbdac..920d238cd4b 100644 --- a/src/mongo/rpc/op_msg.cpp +++ b/src/mongo/rpc/op_msg.cpp @@ -310,7 +310,7 @@ Message OpMsgBuilder::finish() { const auto size = _buf.len(); uassert(ErrorCodes::BSONObjectTooLarge, str::stream() << "BSON size limit hit while building Message. Size: " << size << " (0x" - << integerToHex(size) << "); maxSize: " << BSONObjMaxInternalSize << "(" + << unsignedHex(size) << "); maxSize: " << BSONObjMaxInternalSize << "(" << (BSONObjMaxInternalSize / (1024 * 1024)) << "MB)", size <= BSONObjMaxInternalSize); diff --git a/src/mongo/rpc/op_msg_test.cpp b/src/mongo/rpc/op_msg_test.cpp index 362d5fb5ef9..b22c661a551 100644 --- a/src/mongo/rpc/op_msg_test.cpp +++ b/src/mongo/rpc/op_msg_test.cpp @@ -656,15 +656,15 @@ void testSerializer(const Message& fromSerializer, OpMsgBytes&& expected) { LOGV2(22636, "Mismatch after {commonLength} bytes.", "commonLength"_attr = commonLength); LOGV2(22637, "Common prefix: {hexdump_gotSD_rawData_commonLength}", - "hexdump_gotSD_rawData_commonLength"_attr = hexdump(gotSD.rawData(), commonLength)); + "hexdump_gotSD_rawData_commonLength"_attr = hexdump(gotSD.substr(0, commonLength))); LOGV2(22638, "Got suffix : {hexdump_gotSD_rawData_commonLength_gotSD_size_commonLength}", "hexdump_gotSD_rawData_commonLength_gotSD_size_commonLength"_attr = - hexdump(gotSD.rawData() + commonLength, gotSD.size() - commonLength)); + hexdump(gotSD.substr(commonLength))); LOGV2(22639, "Expected suffix: {hexdump_expectedSD_rawData_commonLength_expectedSD_size_commonLength}", "hexdump_expectedSD_rawData_commonLength_expectedSD_size_commonLength"_attr = - hexdump(expectedSD.rawData() + commonLength, expectedSD.size() - commonLength)); + hexdump(expectedSD.substr(commonLength))); FAIL("Serialization didn't match expected data. See above for details."); } diff --git a/src/mongo/scripting/mozjs/bindata.cpp b/src/mongo/scripting/mozjs/bindata.cpp index 7d38ad14fa7..f66e6064956 100644 --- a/src/mongo/scripting/mozjs/bindata.cpp +++ b/src/mongo/scripting/mozjs/bindata.cpp @@ -79,18 +79,8 @@ void hexToBinData(JSContext* cx, uassert( ErrorCodes::BadValue, "BinData hex string must be an even length", hexstr.size() % 2 == 0); - auto len = hexstr.size() / 2; - - std::unique_ptr<char[]> data(new char[len]); - const char* src = hexstr.c_str(); - for (size_t i = 0; i < len; i++) { - int src_index = i * 2; - if (!std::isxdigit(src[src_index]) || !std::isxdigit(src[src_index + 1])) - uasserted(ErrorCodes::BadValue, "Invalid hex character in string"); - data[i] = uassertStatusOK(fromHex(src + src_index)); - } - std::string encoded = base64::encode(StringData(data.get(), len)); + std::string encoded = base64::encode(hexblob::decode(hexstr)); JS::AutoValueArray<2> args(cx); args[0].setInt32(type); diff --git a/src/mongo/util/base64_test.cpp b/src/mongo/util/base64_test.cpp index 9df4a118742..650877ed7ff 100644 --- a/src/mongo/util/base64_test.cpp +++ b/src/mongo/util/base64_test.cpp @@ -89,7 +89,7 @@ TEST(Base64Test, encodeAllPossibleGroups) { if (kSuperVerbose) { LOGV2(23509, "buf=[{buf}] s=`{s}`", - "buf"_attr = mongo::toHex(buf.data(), sz), + "buf"_attr = mongo::hexblob::encode(buf), "s"_attr = s); } std::string recovered = base64::decode(s); diff --git a/src/mongo/util/hex.cpp b/src/mongo/util/hex.cpp index 41255966894..21a81a91dfc 100644 --- a/src/mongo/util/hex.cpp +++ b/src/mongo/util/hex.cpp @@ -29,82 +29,98 @@ #include "mongo/util/hex.h" -#include <iomanip> -#include <sstream> +#include <algorithm> +#include <cctype> +#include <fmt/format.h> +#include <iterator> #include <string> +#include "mongo/base/error_codes.h" + namespace mongo { -template <typename T> -std::string integerToHexDef(T inInt) { - if (!inInt) - return "0"; - - static const char hexchars[] = "0123456789ABCDEF"; - - static const size_t outbufSize = sizeof(T) * 2 + 1; - char outbuf[outbufSize]; - outbuf[outbufSize - 1] = '\0'; - - char c; - int lastSeenNumber = 0; - for (int j = int(outbufSize) - 2; j >= 0; j--) { - c = hexchars[inInt & 0xF]; - if (c != '0') - lastSeenNumber = j; - outbuf[j] = c; - inInt = inInt >> 4; - } - char* bufPtr = outbuf; - bufPtr += lastSeenNumber; +namespace { + +using namespace fmt::literals; + +constexpr StringData kHexUpper = "0123456789ABCDEF"_sd; +constexpr StringData kHexLower = "0123456789abcdef"_sd; - return std::string(bufPtr); +std::string _hexPack(StringData data, StringData hexchars) { + std::string out; + out.reserve(2 * data.size()); + for (auto c : data) { + out.append({hexchars[(c & 0xF0) >> 4], hexchars[(c & 0x0F)]}); + } + return out; } -template <> -std::string integerToHex<char>(char val) { - return integerToHexDef(val); +template <typename F> +void _decode(StringData s, const F& f) { + uassert(ErrorCodes::FailedToParse, "Hex blob with odd digit count", s.size() % 2 == 0); + for (std::size_t i = 0; i != s.size(); i += 2) + f(hexblob::decodePair(s.substr(i, 2))); } -template <> -std::string integerToHex<int>(int val) { - return integerToHexDef(val); + +} // namespace + +namespace hexblob { + +unsigned char decodeDigit(unsigned char c) { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + uasserted(ErrorCodes::FailedToParse, + "The character \\x{:02x} failed to parse from hex."_format(c)); } -template <> -std::string integerToHex<unsigned int>(unsigned int val) { - return integerToHexDef(val); + +unsigned char decodePair(StringData c) { + uassert(ErrorCodes::FailedToParse, "Need two hex digits", c.size() == 2); + return (decodeDigit(c[0]) << 4) | decodeDigit(c[1]); } -template <> -std::string integerToHex<long>(long val) { - return integerToHexDef(val); + +bool validate(StringData s) { + // There must be an even number of characters, since each pair encodes a single byte. + return s.size() % 2 == 0 && + std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isxdigit(c); }); } -template <> -std::string integerToHex<unsigned long>(unsigned long val) { - return integerToHexDef(val); + +std::string encode(StringData data) { + return _hexPack(data, kHexUpper); } -template <> -std::string integerToHex<long long>(long long val) { - return integerToHexDef(val); + +std::string encodeLower(StringData data) { + return _hexPack(data, kHexLower); } -template <> -std::string integerToHex<unsigned long long>(unsigned long long val) { - return integerToHexDef(val); + +void decode(StringData s, BufBuilder* buf) { + _decode(s, [&](unsigned char c) { buf->appendChar(c); }); } -std::string unsignedIntToFixedLengthHex(uint32_t val) { - char buf[9]; - invariant(snprintf(buf, 9, "%08X", val) == 8); - return std::string(buf, 8); +std::string decode(StringData s) { + std::string r; + r.reserve(s.size() / 2); + _decode(s, [&](unsigned char c) { r.push_back(c); }); + return r; } -std::string hexdump(const char* data, unsigned len) { - verify(len < 1000000); - const unsigned char* p = (const unsigned char*)data; - std::stringstream ss; - ss << std::hex << std::setfill('0'); - for (unsigned i = 0; i < len; i++) { - ss << std::setw(2) << static_cast<unsigned>(p[i]) << ' '; +} // namespace hexblob + +std::string hexdump(StringData data) { + verify(data.size() < 1000000); + std::string out; + out.reserve(3 * data.size()); + char sep = 0; + for (auto c : data) { + if (sep) + out.push_back(sep); + out.append({kHexLower[(c & 0xF0) >> 4], kHexLower[(c & 0x0F)]}); + sep = ' '; } - std::string s = ss.str(); - return s; + return out; } + } // namespace mongo diff --git a/src/mongo/util/hex.h b/src/mongo/util/hex.h index b01cb9b9336..384f725b0fd 100644 --- a/src/mongo/util/hex.h +++ b/src/mongo/util/hex.h @@ -29,104 +29,96 @@ #pragma once -#include <algorithm> -#include <cctype> +#include <fmt/format.h> #include <string> +#include <type_traits> #include "mongo/base/string_data.h" #include "mongo/bson/util/builder.h" -#include "mongo/util/str.h" namespace mongo { -// can't use hex namespace because it conflicts with hex iostream function -inline StatusWith<char> fromHex(char c) { - if ('0' <= c && c <= '9') - return c - '0'; - if ('a' <= c && c <= 'f') - return c - 'a' + 10; - if ('A' <= c && c <= 'F') - return c - 'A' + 10; - return Status(ErrorCodes::FailedToParse, - str::stream() << "The character " << c << " failed to parse from hex."); -} -inline StatusWith<char> fromHex(const char* c) { - if (fromHex(c[0]).isOK() && fromHex(c[1]).isOK()) { - return (char)((fromHex(c[0]).getValue() << 4) | fromHex(c[1]).getValue()); - } - return Status(ErrorCodes::FailedToParse, - str::stream() << "The character " << c[0] << c[1] - << " failed to parse from hex."); -} -inline StatusWith<char> fromHex(StringData c) { - if (fromHex(c[0]).isOK() && fromHex(c[1]).isOK()) { - return (char)((fromHex(c[0]).getValue() << 4) | fromHex(c[1]).getValue()); - } - return Status(ErrorCodes::FailedToParse, - str::stream() << "The character " << c[0] << c[1] - << " failed to parse from hex."); -} /** - * Decodes 'hexString' into raw bytes, appended to the out parameter 'buf'. Callers must first - * ensure that 'hexString' is a valid hex encoding. + * A hex blob is a data interchange format, not meant to be + * convenient to read. The functions in the hexblob namespace are + * specifically to support it, rather than to serve more general + * hexadecimal encoding for diagnostics. + * + * A hex blob is a packed run of hex digit pairs with no punctuation + * or breaks between the encoded bytes. Upper case is produced by + * encoders, but upper or lower case digits are accepted by the + * decoders. */ -inline void fromHexString(StringData hexString, BufBuilder* buf) { - invariant(hexString.size() % 2 == 0); - // Combine every pair of two characters into one byte. - for (std::size_t i = 0; i < hexString.size(); i += 2) { - buf->appendChar(uassertStatusOK(fromHex(StringData(&hexString.rawData()[i], 2)))); - } -} +namespace hexblob { /** - * Returns true if 'hexString' is a valid hexadecimal encoding. + * Decodes hex digit `c` (upper or lower case). + * Throws `FailedToParse` on failure. */ -inline bool isValidHex(StringData hexString) { - // There must be an even number of characters, since each pair encodes a single byte. - return hexString.size() % 2 == 0 && - std::all_of(hexString.begin(), hexString.end(), [](char c) { return std::isxdigit(c); }); -} +unsigned char decodeDigit(unsigned char c); -inline std::string toHex(const void* inRaw, int len) { - static const char hexchars[] = "0123456789ABCDEF"; +/** + * Decodes hex digit pair `c` (upper or lower case). + * Throws `FailedToParse` on failure. + */ +unsigned char decodePair(StringData c); - StringBuilder out; - const char* in = reinterpret_cast<const char*>(inRaw); - for (int i = 0; i < len; ++i) { - char c = in[i]; - char hi = hexchars[(c & 0xF0) >> 4]; - char lo = hexchars[(c & 0x0F)]; +/** + * Returns true if `s` is a valid encoded hex blob. + */ +bool validate(StringData s); - out << hi << lo; - } +/** + * Returns `data` rendered as a concatenation of uppercase hex digit pairs, + * with no separation between bytes. + */ +std::string encode(StringData data); - return out.str(); +/** Raw memory `encode` */ +inline std::string encode(const void* data, size_t len) { + return encode(StringData(reinterpret_cast<const char*>(data), len)); } -template <typename T> -std::string integerToHex(T val); +/** Same as `encode`, but with lowercase hex digits. */ +std::string encodeLower(StringData data); -inline std::string toHexLower(const void* inRaw, int len) { - static const char hexchars[] = "0123456789abcdef"; +/** Raw memory `encodeLower` */ +inline std::string encodeLower(const void* data, size_t len) { + return encodeLower(StringData(reinterpret_cast<const char*>(data), len)); +} - StringBuilder out; - const char* in = reinterpret_cast<const char*>(inRaw); - for (int i = 0; i < len; ++i) { - char c = in[i]; - char hi = hexchars[(c & 0xF0) >> 4]; - char lo = hexchars[(c & 0x0F)]; +/** + * Decodes hex blob `s`, appending its decoded bytes to `buf`. + * Throws `FailedToParse` if `s` is not a valid hex blob encoding. + */ +void decode(StringData s, BufBuilder* buf); - out << hi << lo; - } +/** Overload that returns the decoded hex blob as a `std::string`. */ +std::string decode(StringData s); - return out.str(); -} +} // namespace hexblob /** - * Returns the hex value with a fixed width of 8 chatacters. + * Returns a dump of the buffer as lower case hex digit pairs separated by spaces. + * Requires `len < 1000000`. */ -std::string unsignedIntToFixedLengthHex(uint32_t val); +std::string hexdump(StringData data); + +/** Raw memory `hexdump`. */ +inline std::string hexdump(const void* data, size_t len) { + return hexdump(StringData(reinterpret_cast<const char*>(data), len)); +} + +/** Render `val` in upper case hex, zero-padded to its full width. */ +template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0> +std::string zeroPaddedHex(T val) { + return format(FMT_STRING("{:0{}X}"), std::make_unsigned_t<T>(val), 2 * sizeof(val)); +} + +/** Render the unsigned equivalent of `val` in upper case hex. */ +template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0> +std::string unsignedHex(T val) { + return format(FMT_STRING("{:X}"), std::make_unsigned_t<T>(val)); +} -/* @return a dump of the buffer as hex byte ascii output */ -std::string hexdump(const char* data, unsigned len); } // namespace mongo diff --git a/src/mongo/util/net/sock.cpp b/src/mongo/util/net/sock.cpp index 206e0b65e22..d299512f48a 100644 --- a/src/mongo/util/net/sock.cpp +++ b/src/mongo/util/net/sock.cpp @@ -34,6 +34,7 @@ #include "mongo/util/net/sock.h" #include <algorithm> +#include <fmt/format.h> #if !defined(_WIN32) #include <arpa/inet.h> @@ -750,11 +751,10 @@ bool Socket::isStillConnected() { "idleTimeSecs"_attr = idleTimeSecs, "remoteHost"_attr = remoteString()); if (kDebugBuild) { - std::string hex = hexdump(testBuf, recvd); LOGV2_ERROR(23198, "Hex dump of stale log data: {hex}", "Hex dump of stale log data", - "hex"_attr = hex); + "hex"_attr = hexdump(testBuf, recvd)); } dassert(false); } else { diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 99a1fef567a..f33cb9df0bf 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -221,7 +221,7 @@ std::pair<std::string, RFC4514Parser::ValueTerminator> RFC4514Parser::extractVal str::stream() << "Escaped hex value contains invalid character \'" << hexValStr[1] << "\'", isHex(hexValStr[1])); - const char hexVal = uassertStatusOK(fromHex(StringData(hexValStr.data(), 2))); + const char hexVal = hexblob::decodePair(StringData(hexValStr.data(), 2)); sb << hexVal; if (hexVal != ' ') { trailingSpaces = 0; @@ -353,8 +353,7 @@ void logCert(const CertInformationToLog& cert, StringData certType, const int lo "type"_attr = certType, "subject"_attr = cert.subject.toString(), "issuer"_attr = cert.issuer.toString(), - "thumbprint"_attr = - toHex(static_cast<const void*>(cert.thumbprint.data()), cert.thumbprint.size()), + "thumbprint"_attr = hexblob::encode(cert.thumbprint.data(), cert.thumbprint.size()), "notValidBefore"_attr = cert.validityNotBefore.toString(), "notValidAfter"_attr = cert.validityNotAfter.toString()); } @@ -362,8 +361,7 @@ void logCert(const CertInformationToLog& cert, StringData certType, const int lo void logCRL(const CRLInformationToLog& crl, const int logNum) { LOGV2(logNum, "CRL information", - "thumbprint"_attr = - toHex(static_cast<const void*>(crl.thumbprint.data()), crl.thumbprint.size()), + "thumbprint"_attr = hexblob::encode(crl.thumbprint.data(), crl.thumbprint.size()), "notValidBefore"_attr = crl.validityNotBefore.toString(), "notValidAfter"_attr = crl.validityNotAfter.toString()); } @@ -1212,7 +1210,7 @@ std::string escapeRfc2253(StringData str) { while (pos < str.size()) { if (static_cast<signed char>(str[pos]) < 0) { ret += '\\'; - ret += integerToHex(str[pos]); + ret += unsignedHex(str[pos]); } else { if (std::find(rfc2253EscapeChars.cbegin(), rfc2253EscapeChars.cend(), str[pos]) != rfc2253EscapeChars.cend()) { diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp index 29d9f7cce6d..ad938405d62 100644 --- a/src/mongo/util/net/ssl_manager_windows.cpp +++ b/src/mongo/util/net/ssl_manager_windows.cpp @@ -1183,7 +1183,8 @@ StatusWith<UniqueCertificate> loadCertificateSelectorFromStore( return Status(ErrorCodes::InvalidSSLConfiguration, str::stream() << "CertFindCertificateInStore failed to find cert with thumbprint '" - << toHex(selector.thumbprint.data(), selector.thumbprint.size()) + << hexblob::encode(selector.thumbprint.data(), + selector.thumbprint.size()) << "' in 'My' store in '" << storeName << "': " << errnoWithDescription(gle)); } @@ -1873,7 +1874,7 @@ Status validatePeerCertificate(const std::string& remoteHost, LOGV2_WARNING(23274, "SSL peer certificate validation failed ({errorCode}): {error}", "SSL peer certificate validation failed", - "errorCode"_attr = integerToHex(certChainPolicyStatus.dwError), + "errorCode"_attr = unsignedHex(certChainPolicyStatus.dwError), "error"_attr = errnoWithDescription(certChainPolicyStatus.dwError)); if (certChainPolicyStatus.dwError == CERT_E_CN_NO_MATCH) { @@ -1901,14 +1902,14 @@ Status validatePeerCertificate(const std::string& remoteHost, } else { str::stream msg; msg << "SSL peer certificate validation failed: (" - << integerToHex(certChainPolicyStatus.dwError) << ")" + << unsignedHex(certChainPolicyStatus.dwError) << ")" << errnoWithDescription(certChainPolicyStatus.dwError); LOGV2_ERROR(23279, "SSL peer certificate validation failed: ({errorCode}){error}", "SSL peer certificate validation failed", - "errorCode"_attr = integerToHex(certChainPolicyStatus.dwError), + "errorCode"_attr = unsignedHex(certChainPolicyStatus.dwError), "error"_attr = errnoWithDescription(certChainPolicyStatus.dwError)); return Status(ErrorCodes::SSLHandshakeFailed, msg); } diff --git a/src/mongo/util/net/ssl_options.cpp b/src/mongo/util/net/ssl_options.cpp index 607d4a6a9e8..a142e3a917a 100644 --- a/src/mongo/util/net/ssl_options.cpp +++ b/src/mongo/util/net/ssl_options.cpp @@ -52,22 +52,19 @@ using std::string; SSLParams sslGlobalParams; namespace { -StatusWith<std::vector<uint8_t>> hexToVector(StringData hex) { - if (std::any_of(hex.begin(), hex.end(), [](char c) { return !isxdigit(c); })) { - return {ErrorCodes::BadValue, "Not a valid hex string"}; - } - if (hex.size() % 2) { - return {ErrorCodes::BadValue, "Not an even number of hexits"}; +std::vector<uint8_t> hexToVector(StringData hex) { + try { + std::string data = hexblob::decode(hex); + return std::vector<uint8_t>(data.begin(), data.end()); + } catch (const ExceptionFor<ErrorCodes::FailedToParse>&) { + if (std::any_of(hex.begin(), hex.end(), [](unsigned char c) { return !isxdigit(c); })) { + uasserted(ErrorCodes::BadValue, "Not a valid hex string"); + } + if (hex.size() % 2) { + uasserted(ErrorCodes::BadValue, "Not an even number of hexits"); + } + throw; } - - std::vector<uint8_t> ret; - ret.resize(hex.size() >> 1); - int idx = -2; - std::generate(ret.begin(), ret.end(), [&hex, &idx] { - idx += 2; - return (uassertStatusOK(fromHex(hex[idx])) << 4) | uassertStatusOK(fromHex(hex[idx + 1])); - }); - return ret; } } // namespace @@ -145,15 +142,13 @@ Status parseCertificateSelector(SSLParams::CertificateSelector* selector, << key << "'"}; } - auto swHex = hexToVector(value.substr(delim + 1)); - if (!swHex.isOK()) { + try { + selector->thumbprint = hexToVector(value.substr(delim + 1)); + } catch (const DBException& ex) { return {ErrorCodes::BadValue, str::stream() << "Invalid certificate selector value for '" << name - << "': " << swHex.getStatus().reason()}; + << "': " << ex.reason()}; } - - selector->thumbprint = std::move(swHex.getValue()); - return Status::OK(); } diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp index ba151a6784d..39a38544805 100644 --- a/src/mongo/util/options_parser/options_parser.cpp +++ b/src/mongo/util/options_parser/options_parser.cpp @@ -492,23 +492,10 @@ public: private: static StatusWith<std::vector<std::uint8_t>> hexToVec(StringData hex) { - if (!isValidHex(hex)) { + if (!hexblob::validate(hex)) return {ErrorCodes::BadValue, "Not a valid, even length hex string"}; - } - - std::vector<std::uint8_t> ret; - ret.reserve(hex.size() / 2); - - for (size_t i = 0; i < hex.size(); i += 2) { - auto swFromHex = fromHex(hex.substr(i, 2)); - if (!swFromHex.isOK()) { - // isValidHex() above should guarantee this never occurs. - return {ErrorCodes::BadValue, str::stream() << "Invalid hexits at " << i}; - } - ret.push_back(static_cast<std::uint8_t>(swFromHex.getValue())); - } - - return ret; + std::string blob = hexblob::decode(hex); + return std::vector<std::uint8_t>(blob.begin(), blob.end()); } // The type of expansion represented. diff --git a/src/mongo/util/stacktrace_somap.cpp b/src/mongo/util/stacktrace_somap.cpp index f7ba66a1429..767bd42a521 100644 --- a/src/mongo/util/stacktrace_somap.cpp +++ b/src/mongo/util/stacktrace_somap.cpp @@ -121,7 +121,7 @@ void processNoteSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONO } const char* const noteDescBegin = noteNameBegin + roundUpToElfWordAlignment(noteHeader.n_namesz); - soInfo->append("buildId", toHex(noteDescBegin, noteHeader.n_descsz)); + soInfo->append("buildId", hexblob::encode(noteDescBegin, noteHeader.n_descsz)); } #endif } @@ -197,7 +197,7 @@ void processLoadSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONO return; } - soInfo->append("b", integerToHex(phdr.p_vaddr)); + soInfo->append("b", unsignedHex(phdr.p_vaddr)); } /** @@ -219,7 +219,7 @@ void processLoadSegment(const dl_phdr_info& info, const ElfW(Phdr) & phdr, BSONO int outputSOInfo(dl_phdr_info* info, size_t sz, void* data) { BSONObjBuilder soInfo(reinterpret_cast<BSONArrayBuilder*>(data)->subobjStart()); if (info->dlpi_addr) - soInfo.append("b", integerToHex(ElfW(Addr)(info->dlpi_addr))); + soInfo.append("b", unsignedHex(ElfW(Addr)(info->dlpi_addr))); if (info->dlpi_name && *info->dlpi_name) soInfo.append("path", info->dlpi_name); @@ -262,7 +262,7 @@ void addOSComponentsToSoMap(BSONObjBuilder* soMap) { if (StringData(SEG_TEXT) != segmentCommand->segname) { return false; } - *soInfo << "vmaddr" << integerToHex(segmentCommand->vmaddr); + *soInfo << "vmaddr" << unsignedHex(segmentCommand->vmaddr); return true; }; const uint32_t numImages = _dyld_image_count(); @@ -284,7 +284,7 @@ void addOSComponentsToSoMap(BSONObjBuilder* soMap) { continue; } soInfo << "machType" << static_cast<int32_t>(header->filetype); - soInfo << "b" << integerToHex(reinterpret_cast<intptr_t>(header)); + soInfo << "b" << unsignedHex(reinterpret_cast<uintptr_t>(header)); const char* const loadCommandsBegin = reinterpret_cast<const char*>(header) + headerSize; const char* const loadCommandsEnd = loadCommandsBegin + header->sizeofcmds; @@ -300,7 +300,7 @@ void addOSComponentsToSoMap(BSONObjBuilder* soMap) { switch (lcType(lcCurr)) { case LC_UUID: { const auto uuidCmd = reinterpret_cast<const uuid_command*>(lcCurr); - soInfo << "buildId" << toHex(uuidCmd->uuid, 16); + soInfo << "buildId" << hexblob::encode(uuidCmd->uuid, 16); break; } case LC_SEGMENT_64: diff --git a/src/mongo/util/stacktrace_test.cpp b/src/mongo/util/stacktrace_test.cpp index 714863c357b..9bf6dd99a19 100644 --- a/src/mongo/util/stacktrace_test.cpp +++ b/src/mongo/util/stacktrace_test.cpp @@ -394,7 +394,8 @@ public: "tid"_attr = ostr(stdx::this_thread::get_id()), "sig"_attr = sig); char storage; - LOGV2(23388, "Local var", "var"_attr = integerToHex(reinterpret_cast<uintptr_t>(&storage))); + LOGV2( + 23388, "Local var", "var"_attr = "{:X}"_format(reinterpret_cast<uintptr_t>(&storage))); } static void tryHandler(void (*handler)(int, siginfo_t*, void*)) { @@ -405,8 +406,8 @@ public: std::fill(buf->begin(), buf->end(), kSentinel); LOGV2(24157, "sigaltstack buf", - "size"_attr = integerToHex(buf->size()), - "data"_attr = integerToHex(reinterpret_cast<uintptr_t>(buf->data()))); + "size"_attr = "{:X}"_format(buf->size()), + "data"_attr = "{:X}"_format(reinterpret_cast<uintptr_t>(buf->data()))); stdx::thread thr([&] { LOGV2(23389, "Thread running", "tid"_attr = ostr(stdx::this_thread::get_id())); { @@ -661,7 +662,7 @@ TEST(StackTrace, BacktraceThroughLibc) { LOGV2(23392, "Frame", "i"_attr = i, - "frame"_attr = integerToHex(reinterpret_cast<uintptr_t>(capture.arr[i]))); + "frame"_attr = "{:X}"_format(reinterpret_cast<uintptr_t>(capture.arr[i]))); } } #endif // mongo stacktrace backend diff --git a/src/mongo/util/str.cpp b/src/mongo/util/str.cpp index 440a15f1f15..024a6bca1e3 100644 --- a/src/mongo/util/str.cpp +++ b/src/mongo/util/str.cpp @@ -214,7 +214,7 @@ std::string escape(StringData sd, bool escape_slash) { default: if (c >= 0 && c <= 0x1f) { // For c < 0x7f, ASCII value == Unicode code point. - ret << "\\u00" << toHexLower(&c, 1); + ret << "\\u00" << hexblob::encodeLower(&c, 1); } else { ret << c; } diff --git a/src/mongo/util/str_test.cpp b/src/mongo/util/str_test.cpp index 92259e342a4..bfcc3e56242 100644 --- a/src/mongo/util/str_test.cpp +++ b/src/mongo/util/str_test.cpp @@ -180,25 +180,25 @@ TEST(StringUtilsTest, Substring1) { assertCmp(0, StringData("0001", 3), StringData("0000", 3), false); } -TEST(StringUtilsTest, VariousConversions) { - ASSERT_EQUALS(std::string("0"), integerToHex(0)); - ASSERT_EQUALS(std::string("1"), integerToHex(1)); - ASSERT_EQUALS(std::string("1337"), integerToHex(0x1337)); - ASSERT_EQUALS(std::string("FFFFD499"), integerToHex(-11111)); - ASSERT_EQUALS(std::string("F1FE60C4"), integerToHex(-234987324)); - ASSERT_EQUALS(std::string("80000000"), integerToHex(std::numeric_limits<int>::min())); - ASSERT_EQUALS(std::string("7FFFFFFF"), integerToHex(std::numeric_limits<int>::max())); - ASSERT_EQUALS(std::string("7FFFFFFFFFFFFFFF"), - integerToHex(std::numeric_limits<long long>::max())); - ASSERT_EQUALS(std::string("8000000000000000"), - integerToHex(std::numeric_limits<long long>::min())); +TEST(StringUtilsTest, UnsignedHex) { + ASSERT_EQUALS(unsignedHex(0), "0"); + ASSERT_EQUALS(unsignedHex(1), "1"); + ASSERT_EQUALS(unsignedHex(0x1337), "1337"); + ASSERT_EQUALS(unsignedHex(-11111), "FFFFD499"); + ASSERT_EQUALS(unsignedHex(-234987324), "F1FE60C4"); + ASSERT_EQUALS(unsignedHex(std::numeric_limits<int>::min()), "80000000"); + ASSERT_EQUALS(unsignedHex(std::numeric_limits<int>::max()), "7FFFFFFF"); + ASSERT_EQUALS(unsignedHex(std::numeric_limits<long long>::max()), "7FFFFFFFFFFFFFFF"); + ASSERT_EQUALS(unsignedHex(std::numeric_limits<long long>::min()), "8000000000000000"); } -TEST(StringUtilsTest, unsignedFixedLengthHex) { - ASSERT_EQUALS(unsignedIntToFixedLengthHex(std::numeric_limits<uint32_t>::max()), - std::string("FFFFFFFF")); - ASSERT_EQUALS(unsignedIntToFixedLengthHex(0), std::string("00000000")); - ASSERT_EQUALS(unsignedIntToFixedLengthHex(123), std::string("0000007B")); +TEST(StringUtilsTest, ZeroPaddedHex) { + ASSERT_EQUALS(zeroPaddedHex(std::numeric_limits<uint32_t>::max()), "FFFFFFFF"); + ASSERT_EQUALS(zeroPaddedHex(uint32_t{123}), "0000007B"); + ASSERT_EQUALS(zeroPaddedHex(uint8_t{0}), "00"); + ASSERT_EQUALS(zeroPaddedHex(uint16_t{0}), "0000"); + ASSERT_EQUALS(zeroPaddedHex(uint32_t{0}), "00000000"); + ASSERT_EQUALS(zeroPaddedHex(uint64_t{0}), "0000000000000000"); } TEST(StringUtilsTest, CanParseZero) { diff --git a/src/mongo/util/uuid.cpp b/src/mongo/util/uuid.cpp index cc4fd3b907e..a44208f8832 100644 --- a/src/mongo/util/uuid.cpp +++ b/src/mongo/util/uuid.cpp @@ -31,6 +31,7 @@ #include "mongo/util/uuid.h" +#include <fmt/format.h> #include <pcrecpp.h> #include "mongo/bson/bsonobjbuilder.h" @@ -43,6 +44,8 @@ namespace mongo { namespace { +using namespace fmt::literals; + Mutex uuidGenMutex; SecureRandom uuidGen; @@ -58,7 +61,7 @@ StatusWith<UUID> UUID::parse(BSONElement from) { StatusWith<UUID> UUID::parse(const std::string& s) { if (!isUUIDString(s)) { - return {ErrorCodes::InvalidUUID, "Invalid UUID string: " + s}; + return {ErrorCodes::InvalidUUID, "Invalid UUID string: {}"_format(s)}; } UUIDStorage uuid; @@ -70,10 +73,8 @@ StatusWith<UUID> UUID::parse(const std::string& s) { if (s[j] == '-') j++; - char high = s[j++]; - char low = s[j++]; - - uuid[i] = ((uassertStatusOK(fromHex(high)) << 4) | uassertStatusOK(fromHex(low))); + uuid[i] = hexblob::decodePair(StringData(s).substr(j, 2)); + j += 2; } return UUID{std::move(uuid)}; @@ -132,20 +133,11 @@ BSONObj UUID::toBSON() const { } std::string UUID::toString() const { - StringBuilder ss; - - // 4 Octets - 2 Octets - 2 Octets - 2 Octets - 6 Octets - ss << toHexLower(&_uuid[0], 4); - ss << "-"; - ss << toHexLower(&_uuid[4], 2); - ss << "-"; - ss << toHexLower(&_uuid[6], 2); - ss << "-"; - ss << toHexLower(&_uuid[8], 2); - ss << "-"; - ss << toHexLower(&_uuid[10], 6); - - return ss.str(); + return "{}-{}-{}-{}-{}"_format(hexblob::encodeLower(&_uuid[0], 4), + hexblob::encodeLower(&_uuid[4], 2), + hexblob::encodeLower(&_uuid[6], 2), + hexblob::encodeLower(&_uuid[8], 2), + hexblob::encodeLower(&_uuid[10], 6)); } template <> diff --git a/src/mongo/watchdog/watchdog.cpp b/src/mongo/watchdog/watchdog.cpp index 4142dc3a8ba..ec3253d805e 100644 --- a/src/mongo/watchdog/watchdog.cpp +++ b/src/mongo/watchdog/watchdog.cpp @@ -430,10 +430,9 @@ void checkFile(OperationContext* opCtx, const boost::filesystem::path& file) { "'{toHexLower_readBuffer_get_bytesRead}'", "file_generic_string"_attr = file.generic_string(), "nowStr_size"_attr = nowStr.size(), - "toHexLower_nowStr_c_str_nowStr_size"_attr = - toHexLower(nowStr.c_str(), nowStr.size()), + "toHexLower_nowStr_c_str_nowStr_size"_attr = hexblob::encodeLower(nowStr), "toHexLower_readBuffer_get_bytesRead"_attr = - toHexLower(readBuffer.get(), bytesRead)); + hexblob::encodeLower(readBuffer.get(), bytesRead)); } } @@ -562,9 +561,9 @@ void checkFile(OperationContext* opCtx, const boost::filesystem::path& file) { "'{toHexLower_readBuffer_get_bytesReadTotal}'", "file_generic_string"_attr = file.generic_string(), "nowStr_size"_attr = nowStr.size(), - "toHexLower_nowStr_c_str_nowStr_size"_attr = toHexLower(nowStr.c_str(), nowStr.size()), + "toHexLower_nowStr_c_str_nowStr_size"_attr = hexblob::encodeLower(nowStr), "toHexLower_readBuffer_get_bytesReadTotal"_attr = - toHexLower(readBuffer.get(), bytesReadTotal)); + hexblob::encodeLower(readBuffer.get(), bytesReadTotal)); } if (close(fd)) { |