summaryrefslogtreecommitdiff
path: root/src/mongo/rpc
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2016-08-04 17:29:34 -0400
committerMark Benvenuto <mark.benvenuto@mongodb.com>2016-08-04 17:29:34 -0400
commitad27c92e01758c96e7ace4cba13574e0d97a761d (patch)
tree9ef9c726765d76b531c7090063900484b65ea4c8 /src/mongo/rpc
parent931a227eedca19bc05fc6318996ffd3c6a2c6f4b (diff)
downloadmongo-ad27c92e01758c96e7ace4cba13574e0d97a761d.tar.gz
SERVER-24611 Implement ClientMetadata class
Diffstat (limited to 'src/mongo/rpc')
-rw-r--r--src/mongo/rpc/SConscript26
-rw-r--r--src/mongo/rpc/metadata/client_metadata.cpp400
-rw-r--r--src/mongo/rpc/metadata/client_metadata.h230
-rw-r--r--src/mongo/rpc/metadata/client_metadata_ismaster.cpp77
-rw-r--r--src/mongo/rpc/metadata/client_metadata_ismaster.h89
-rw-r--r--src/mongo/rpc/metadata/client_metadata_test.cpp296
6 files changed, 1118 insertions, 0 deletions
diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript
index 7822e216189..c3b2b73ddaf 100644
--- a/src/mongo/rpc/SConscript
+++ b/src/mongo/rpc/SConscript
@@ -139,6 +139,7 @@ env.Library(
'metadata/repl_set_metadata.cpp',
],
LIBDEPS=[
+ 'client_metadata',
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/bson/util/bson_extract',
'$BUILD_DIR/mongo/client/read_preference',
@@ -194,3 +195,28 @@ env.CppUnitTest(
],
LIBDEPS=['metadata']
)
+
+env.Library(
+ target='client_metadata',
+ source=[
+ 'metadata/client_metadata.cpp',
+ 'metadata/client_metadata_ismaster.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ "$BUILD_DIR/mongo/util/concurrency/spin_lock",
+ '$BUILD_DIR/mongo/util/decorable',
+ '$BUILD_DIR/mongo/util/net/hostandport',
+ "$BUILD_DIR/mongo/util/processinfo",
+ ],
+)
+
+env.CppUnitTest(
+ target='client_metadata_test',
+ source=[
+ 'metadata/client_metadata_test.cpp',
+ ],
+ LIBDEPS=[
+ 'client_metadata',
+ ]
+)
diff --git a/src/mongo/rpc/metadata/client_metadata.cpp b/src/mongo/rpc/metadata/client_metadata.cpp
new file mode 100644
index 00000000000..ea03d5e0f84
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata.cpp
@@ -0,0 +1,400 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/rpc/metadata/client_metadata.h"
+
+#include <string>
+
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/util/log.h"
+#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/processinfo.h"
+
+namespace mongo {
+
+namespace {
+
+constexpr auto kApplication = "application"_sd;
+constexpr auto kDriver = "driver"_sd;
+constexpr auto kOperatingSystem = "os"_sd;
+
+constexpr auto kArchitecture = "architecture"_sd;
+constexpr auto kName = "name"_sd;
+constexpr auto kType = "type"_sd;
+constexpr auto kVersion = "version"_sd;
+
+constexpr uint32_t kMaxMetadataDocumentByteLength = 512U;
+constexpr uint32_t kMaxApplicationNameByteLength = 128U;
+
+} // namespace
+
+StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElement& element) {
+ if (element.eoo()) {
+ return {boost::none};
+ }
+
+ if (!element.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch, "The client metadata document must be a document");
+ }
+
+ ClientMetadata clientMetadata;
+ Status s = clientMetadata.parseClientMetadataDocument(element.Obj());
+ if (!s.isOK()) {
+ return s;
+ }
+
+ return {std::move(clientMetadata)};
+}
+
+Status ClientMetadata::parseClientMetadataDocument(const BSONObj& doc) {
+ if (static_cast<uint32_t>(doc.objsize()) > kMaxMetadataDocumentByteLength) {
+ return Status(ErrorCodes::ClientMetadataDocumentTooLarge,
+ str::stream() << "The client metadata document must be less then or equal to "
+ << kMaxMetadataDocumentByteLength
+ << "bytes");
+ }
+
+ // Get a copy so that we can take a stable reference to the app name inside
+ BSONObj docOwned = doc.getOwned();
+
+ StringData appName;
+ bool foundDriver = false;
+ bool foundOperatingSystem = false;
+
+ BSONObjIterator i(docOwned);
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ if (name == kApplication) {
+ // Application is an optional sub-document, but we require it to be a document if
+ // specified.
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kApplication
+ << "' field is required to be a BSON document in the "
+ "client metadata document");
+ }
+
+ auto swAppName = parseApplicationDocument(e.Obj());
+ if (!swAppName.getStatus().isOK()) {
+ return swAppName.getStatus();
+ }
+
+ appName = swAppName.getValue();
+
+ } else if (name == kDriver) {
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kDriver << "' field is required to be a "
+ "BSON document in the client "
+ "metadata document");
+ }
+
+ Status s = validateDriverDocument(e.Obj());
+ if (!s.isOK()) {
+ return s;
+ }
+
+ foundDriver = true;
+ } else if (name == kOperatingSystem) {
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kOperatingSystem
+ << "' field is required to be a BSON document in the "
+ "client metadata document");
+ }
+
+ Status s = validateOperatingSystemDocument(e.Obj());
+ if (!s.isOK()) {
+ return s;
+ }
+
+ foundOperatingSystem = true;
+ }
+
+ // Ignore other fields as extra fields are allowed.
+ }
+
+ // Driver is a required sub document.
+ if (!foundDriver) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required sub-document '" << kDriver
+ << "' in the client metadata document");
+ }
+
+ // OS is a required sub document.
+ if (!foundOperatingSystem) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required sub-document '" << kOperatingSystem
+ << "' in the client metadata document");
+ }
+
+ _document = std::move(docOwned);
+ _appName = std::move(appName);
+
+ return Status::OK();
+}
+
+StatusWith<StringData> ClientMetadata::parseApplicationDocument(const BSONObj& doc) {
+ BSONObjIterator i(doc);
+
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ // Name is the only required field, and any other fields are simply ignored.
+ if (name == kName) {
+
+ if (e.type() != String) {
+ return {
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kApplication << "." << kName
+ << "' field must be a string in the client metadata document"};
+ }
+
+ StringData value = e.checkAndGetStringData();
+
+ if (value.size() > kMaxApplicationNameByteLength) {
+ return {ErrorCodes::ClientMetadataAppNameTooLarge,
+ str::stream() << "The '" << kApplication << "." << kName
+ << "' field must be less then or equal to "
+ << kMaxApplicationNameByteLength
+ << " bytes in the client metadata document"};
+ }
+
+ return {std::move(value)};
+ }
+ }
+
+ return {StringData()};
+}
+
+Status ClientMetadata::validateDriverDocument(const BSONObj& doc) {
+ bool foundName = false;
+ bool foundVersion = false;
+
+ BSONObjIterator i(doc);
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ if (name == kName) {
+ if (e.type() != String) {
+ return Status(
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kDriver << "." << kName
+ << "' field must be a string in the client metadata document");
+ }
+
+ foundName = true;
+ } else if (name == kVersion) {
+ if (e.type() != String) {
+ return Status(
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kDriver << "." << kVersion
+ << "' field must be a string in the client metadata document");
+ }
+
+ foundVersion = true;
+ }
+ }
+
+ if (foundName == false) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required field '" << kDriver << "." << kName
+ << "' in the client metadata document");
+ }
+
+ if (foundVersion == false) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required field '" << kDriver << "." << kVersion
+ << "' in the client metadata document");
+ }
+
+ return Status::OK();
+}
+
+Status ClientMetadata::validateOperatingSystemDocument(const BSONObj& doc) {
+ bool foundType = false;
+
+ BSONObjIterator i(doc);
+ while (i.more()) {
+ BSONElement e = i.next();
+ StringData name = e.fieldNameStringData();
+
+ if (name == kType) {
+ if (e.type() != String) {
+ return Status(
+ ErrorCodes::TypeMismatch,
+ str::stream() << "The '" << kOperatingSystem << "." << kType
+ << "' field must be a string in the client metadata document");
+ }
+
+ foundType = true;
+ }
+ }
+
+ if (foundType == false) {
+ return Status(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required field '" << kOperatingSystem << "."
+ << kType
+ << "' in the client metadata document");
+ }
+
+ return Status::OK();
+}
+
+void ClientMetadata::serialize(StringData driverName,
+ StringData driverVersion,
+ BSONObjBuilder* builder) {
+
+ ProcessInfo processInfo;
+
+ serializePrivate(driverName,
+ driverVersion,
+ processInfo.getOsType(),
+ processInfo.getOsName(),
+ processInfo.getArch(),
+ processInfo.getOsVersion(),
+ builder);
+}
+
+void ClientMetadata::serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ BSONObjBuilder* builder) {
+ invariant(!driverName.empty() && !driverVersion.empty() && !osType.empty() && !osName.empty() &&
+ !osArchitecture.empty() && !osVersion.empty());
+
+ BSONObjBuilder metaObjBuilder(builder->subobjStart(kMetadataDocumentName));
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kDriver));
+ subObjBuilder.append(kName, driverName);
+ subObjBuilder.append(kVersion, driverVersion);
+ }
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kOperatingSystem));
+ subObjBuilder.append(kType, osType);
+ subObjBuilder.append(kName, osName);
+ subObjBuilder.append(kArchitecture, osArchitecture);
+ subObjBuilder.append(kVersion, osVersion);
+ }
+}
+
+Status ClientMetadata::serialize(StringData driverName,
+ StringData driverVersion,
+ StringData appName,
+ BSONObjBuilder* builder) {
+
+ ProcessInfo processInfo;
+
+ return serializePrivate(driverName,
+ driverVersion,
+ processInfo.getOsType(),
+ processInfo.getOsName(),
+ processInfo.getArch(),
+ processInfo.getOsVersion(),
+ appName,
+ builder);
+}
+
+Status ClientMetadata::serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ StringData appName,
+ BSONObjBuilder* builder) {
+ invariant(!driverName.empty() && !driverVersion.empty() && !osType.empty() && !osName.empty() &&
+ !osArchitecture.empty() && !osVersion.empty());
+
+ if (appName.size() > kMaxApplicationNameByteLength) {
+ return Status(ErrorCodes::ClientMetadataAppNameTooLarge,
+ str::stream() << "The '" << kApplication << "." << kName
+ << "' field must be less then or equal to "
+ << kMaxApplicationNameByteLength
+ << " bytes in the client metadata document");
+ }
+
+ {
+ BSONObjBuilder metaObjBuilder(builder->subobjStart(kMetadataDocumentName));
+
+ if (!appName.empty()) {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kApplication));
+ subObjBuilder.append(kName, appName);
+ }
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kDriver));
+ subObjBuilder.append(kName, driverName);
+ subObjBuilder.append(kVersion, driverVersion);
+ }
+
+ {
+ BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kOperatingSystem));
+ subObjBuilder.append(kType, osType);
+ subObjBuilder.append(kName, osName);
+ subObjBuilder.append(kArchitecture, osArchitecture);
+ subObjBuilder.append(kVersion, osVersion);
+ }
+ }
+
+ return Status::OK();
+}
+
+StringData ClientMetadata::getApplicationName() const {
+ return _appName;
+}
+
+const BSONObj& ClientMetadata::getDocument() const {
+ return _document;
+}
+
+void ClientMetadata::logClientMetadata(Client* client) const {
+ invariant(!getDocument().isEmpty());
+ log() << "received client metadata from " << client->getRemote().toString() << " "
+ << client->desc() << ": " << getDocument();
+}
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata.h b/src/mongo/rpc/metadata/client_metadata.h
new file mode 100644
index 00000000000..92646e3a88d
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata.h
@@ -0,0 +1,230 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/base/status.h"
+#include "mongo/base/status_with.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+
+namespace mongo {
+
+class Client;
+class ClientBasic;
+class OperationContext;
+
+constexpr auto kMetadataDocumentName = "client"_sd;
+
+/**
+ * The ClientMetadata class is responsible for parsing the client metadata document that is received
+ * in isMaster from clients. This class also provides static methods for client libraries to create
+ * a valid client metadata document.
+ *
+ * Example document of isMaster request with client metadata document:
+ * {
+ * "isMaster" : 1,
+ * "client" : {
+ * "application" : { // Optional
+ * "name" : "string" // Optional with caveats
+ * },
+ * "driver" : { // Required, Informational Only
+ * "name" : "string", // Required, Informational Only
+ * "version" : "string" // Required, Informational Only
+ * },
+ * "os" : { // Required, Informational Only
+ * "type" : "string", // Required, Informational Only, See note
+ * "name" : "string", // Optional, Informational Only
+ * "architecture" : "string", // Optional, Informational Only
+ * "version" : "string" // Optional, Informational Only
+ * }
+ * }
+ * }
+ *
+ * For this classes' purposes, the client metadata document is the sub-document in "client". It is
+ * allowed to contain additional fields that are not listed in the example above. These additional
+ * fields are ignore by this class. The "os" document "type" field is required (defaults to
+ * "unknown" in Mongo Drivers). The "driver", and "os" documents while required, are for
+ * informational purposes only. The content is logged to disk but otherwise ignored.
+ *
+ * See Driver Specification: "MongoDB Handshake" for more information.
+ */
+class ClientMetadata {
+ MONGO_DISALLOW_COPYING(ClientMetadata);
+
+public:
+ ClientMetadata(ClientMetadata&&) = default;
+ ClientMetadata& operator=(ClientMetadata&&) = default;
+
+ /**
+ * Parse and validate a client metadata document contained in an isMaster request.
+ *
+ * Empty or non-existent sub-documents are permitted. Non-empty documents are required to have
+ * the fields driver.name, driver.version, and os.type which must be strings.
+ *
+ * Returns an empty optional if element is empty.
+ */
+ static StatusWith<boost::optional<ClientMetadata>> parse(const BSONElement& element);
+
+ /**
+ * Create a new client metadata document with os information from the ProcessInfo class.
+ *
+ * This method outputs the "client" field, and client metadata sub-document in the
+ * BSONObjBuilder:
+ *
+ * "client" : {
+ * "driver" : {
+ * "name" : "string",
+ * "version" : "string"
+ * },
+ * "os" : {
+ * "type" : "string",
+ * "name" : "string",
+ * "architecture" : "string",
+ * "version" : "string"
+ * }
+ * }
+ */
+ static void serialize(StringData driverName, StringData driverVersion, BSONObjBuilder* builder);
+
+ /**
+ * Create a new client metadata document with os information from the ProcessInfo class.
+ *
+ * driverName - name of the driver, must not be empty
+ * driverVersion - a string for the driver version, must not be empty
+ *
+ * Notes: appName must be <= 128 bytes otherwise an error is returned. It may be empty in which
+ * case it is omitted from the output document.
+ *
+ * This method outputs the "client" field, and client metadata sub-document in the
+ * BSONObjBuilder:
+ *
+ * "client" : {
+ * "application" : {
+ * "name" : "string"
+ * },
+ * "driver" : {
+ * "name" : "string",
+ * "version" : "string"
+ * },
+ * "os" : {
+ * "type" : "string",
+ * "name" : "string",
+ * "architecture" : "string",
+ * "version" : "string"
+ * }
+ * }
+ */
+ static Status serialize(StringData driverName,
+ StringData driverVersion,
+ StringData appName,
+ BSONObjBuilder* builder);
+
+ /**
+ * Get the Application Name for the client metadata document.
+ *
+ * Used to log Application Name in slow operation reports, and into system.profile.
+ * Return: May be empty.
+ */
+ StringData getApplicationName() const;
+
+ /**
+ * Get the BSON Document of the client metadata document. In the example above in the class
+ * comment, this is the document in the "client" field.
+ *
+ * Return: May be empty.
+ */
+ const BSONObj& getDocument() const;
+
+ /**
+ * Log client and client metadata information to disk.
+ */
+ void logClientMetadata(Client* client) const;
+
+public:
+ /**
+ * Create a new client metadata document.
+ *
+ * Exposed for Unit Test purposes
+ */
+ static void serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ BSONObjBuilder* builder);
+
+ /**
+ * Create a new client metadata document.
+ *
+ * driverName - name of the driver
+ * driverVersion - a string for the driver version
+ * osType - name of host operating system of client, i.e. uname -s
+ * osName - name of operating system distro, i.e. "Ubuntu..." or "Microsoft Windows 8"
+ * osArchitecture - architecture of host operating system, i.e. uname -p
+ * osVersion - operating system version, i.e. uname -v
+ *
+ * Notes: appName must be <= 128 bytes otherwise an error is returned. It may be empty in which
+ * case it is omitted from the output document. All other fields must not be empty.
+ *
+ * Exposed for Unit Test purposes
+ */
+ static Status serializePrivate(StringData driverName,
+ StringData driverVersion,
+ StringData osType,
+ StringData osName,
+ StringData osArchitecture,
+ StringData osVersion,
+ StringData appName,
+ BSONObjBuilder* builder);
+
+private:
+ ClientMetadata() = default;
+
+ Status parseClientMetadataDocument(const BSONObj& doc);
+ static Status validateDriverDocument(const BSONObj& doc);
+ static Status validateOperatingSystemDocument(const BSONObj& doc);
+ static StatusWith<StringData> parseApplicationDocument(const BSONObj& doc);
+
+private:
+ // Parsed Client Metadata document
+ // May be empty
+ // Owned
+ BSONObj _document;
+
+ // Application Name extracted from the client metadata document.
+ // May be empty
+ StringData _appName;
+};
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.cpp b/src/mongo/rpc/metadata/client_metadata_ismaster.cpp
new file mode 100644
index 00000000000..53164ed33d2
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata_ismaster.cpp
@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/rpc/metadata/client_metadata_ismaster.h"
+
+#include <string>
+
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/db/client.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+namespace {
+
+const auto getClientMetadataIsMasterState =
+ ClientBasic::declareDecoration<ClientMetadataIsMasterState>();
+
+} // namespace
+
+ClientMetadataIsMasterState& ClientMetadataIsMasterState::get(ClientBasic* client) {
+ return getClientMetadataIsMasterState(*client);
+}
+
+bool ClientMetadataIsMasterState::hasSeenIsMaster() const {
+ return _hasSeenIsMaster;
+}
+
+void ClientMetadataIsMasterState::setSeenIsMaster() {
+ invariant(!_hasSeenIsMaster);
+ _hasSeenIsMaster = true;
+}
+
+const boost::optional<ClientMetadata>& ClientMetadataIsMasterState::getClientMetadata() const {
+ return _clientMetadata;
+}
+
+void ClientMetadataIsMasterState::setClientMetadata(
+ Client* client, boost::optional<ClientMetadata> clientMetadata) {
+ auto& state = get(client);
+
+ stdx::lock_guard<Client> lk(*client);
+ state._clientMetadata = std::move(clientMetadata);
+}
+
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.h b/src/mongo/rpc/metadata/client_metadata_ismaster.h
new file mode 100644
index 00000000000..9bd274ae4f6
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata_ismaster.h
@@ -0,0 +1,89 @@
+/**
+* Copyright (C) 2016 MongoDB Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* As a special exception, the copyright holders give permission to link the
+* code of portions of this program with the OpenSSL library under certain
+* conditions as described in each individual source file and distribute
+* linked combinations including the program with the OpenSSL library. You
+* must comply with the GNU Affero General Public License in all respects
+* for all of the code used other than as permitted herein. If you modify
+* file(s) with this exception, you may extend this exception to your
+* version of the file(s), but you are not obligated to do so. If you do not
+* wish to do so, delete this exception statement from your version. If you
+* delete this exception statement from all source files in the program,
+* then also delete it in the license file.
+*/
+
+#pragma once
+
+#include <boost/optional.hpp>
+#include <memory>
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/rpc/metadata/client_metadata.h"
+
+namespace mongo {
+
+class Client;
+class ClientBasic;
+
+/**
+ * ClientMetadataIsMasterState is responsible for tracking whether the client metadata document has
+ * been received by the specified Client object.
+ */
+class ClientMetadataIsMasterState {
+ MONGO_DISALLOW_COPYING(ClientMetadataIsMasterState);
+
+public:
+ ClientMetadataIsMasterState() = default;
+
+ static ClientMetadataIsMasterState& get(ClientBasic* client);
+
+ /**
+ * Get the optional client metadata object.
+ */
+ const boost::optional<ClientMetadata>& getClientMetadata() const;
+
+ /**
+ * Set the optional client metadata object.
+ */
+ static void setClientMetadata(Client* client, boost::optional<ClientMetadata> clientMetadata);
+
+ /**
+ * Check a flag to indicate that isMaster has been seen for this Client.
+ */
+ bool hasSeenIsMaster() const;
+
+ /**
+ * Set a flag to indicate that isMaster has been seen for this Client.
+ */
+ void setSeenIsMaster();
+
+private:
+ // Optional client metadata document.
+ // Set if client sees isMaster cmd or as part of OP_Command processing.
+ // Thread-Safety:
+ // Can be read and written from the thread owning "Client".
+ // Can be read from other threads if they hold the "Client" lock.
+ boost::optional<ClientMetadata> _clientMetadata{boost::none};
+
+ // Indicates whether we have seen an is master for this client.
+ // Thread-Safety:
+ // None - must be only be read and written from the thread owning "Client".
+ bool _hasSeenIsMaster{false};
+};
+
+} // namespace mongo
diff --git a/src/mongo/rpc/metadata/client_metadata_test.cpp b/src/mongo/rpc/metadata/client_metadata_test.cpp
new file mode 100644
index 00000000000..8d64e5e7697
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata_test.cpp
@@ -0,0 +1,296 @@
+/**
+ * Copyright (C) 2016 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/rpc/metadata/client_metadata.h"
+
+#include <boost/filesystem.hpp>
+#include <map>
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/log.h"
+
+namespace mongo {
+
+constexpr auto kMetadataDoc = "client"_sd;
+constexpr auto kApplication = "application"_sd;
+constexpr auto kDriver = "driver"_sd;
+constexpr auto kName = "name"_sd;
+constexpr auto kType = "type"_sd;
+constexpr auto kVersion = "version"_sd;
+constexpr auto kOperatingSystem = "os"_sd;
+constexpr auto kArchitecture = "architecture"_sd;
+
+constexpr auto kUnknown = "unkown"_sd;
+
+#define ASSERT_DOC_OK(...) \
+ do { \
+ auto _swParseStatus = \
+ ClientMetadata::parse(BSON(kMetadataDoc << BSON(__VA_ARGS__))[kMetadataDoc]); \
+ ASSERT_OK(_swParseStatus.getStatus()); \
+ } while (0)
+
+#define ASSERT_DOC_NOT_OK(...) \
+ do { \
+ auto _swParseStatus = \
+ ClientMetadata::parse(BSON(kMetadataDoc << BSON(__VA_ARGS__))[kMetadataDoc]); \
+ ASSERT_NOT_OK(_swParseStatus.getStatus()); \
+ } while (0)
+
+
+TEST(ClientMetadatTest, TestLoopbackTest) {
+ // Serialize without application name
+ {
+ BSONObjBuilder builder;
+ ASSERT_OK(ClientMetadata::serializePrivate("a", "b", "c", "d", "e", "f", "g", &builder));
+
+ auto obj = builder.obj();
+ auto swParseStatus = ClientMetadata::parse(obj[kMetadataDoc]);
+ ASSERT_OK(swParseStatus.getStatus());
+ ASSERT_EQUALS("g", swParseStatus.getValue().get().getApplicationName());
+
+ BSONObj outDoc =
+ BSON(kMetadataDoc << BSON(
+ kApplication << BSON(kName << "g") << kDriver
+ << BSON(kName << "a" << kVersion << "b")
+ << kOperatingSystem
+ << BSON(kType << "c" << kName << "d" << kArchitecture << "e"
+ << kVersion
+ << "f")));
+ ASSERT_EQUALS(obj, outDoc);
+ }
+
+ // Serialize without application name
+ {
+ BSONObjBuilder builder;
+ ClientMetadata::serializePrivate("a", "b", "c", "d", "e", "f", &builder);
+
+ auto obj = builder.obj();
+ auto swParseStatus = ClientMetadata::parse(obj[kMetadataDoc]);
+ ASSERT_OK(swParseStatus.getStatus());
+
+ BSONObj outDoc = BSON(
+ kMetadataDoc << BSON(
+ kDriver << BSON(kName << "a" << kVersion << "b") << kOperatingSystem
+ << BSON(kType << "c" << kName << "d" << kArchitecture << "e" << kVersion
+ << "f")));
+ ASSERT_EQUALS(obj, outDoc);
+ }
+
+ // Serialize with the os information automatically computed
+ {
+ BSONObjBuilder builder;
+ ASSERT_OK(ClientMetadata::serialize("a", "b", "f", &builder));
+
+ auto obj = builder.obj();
+
+ auto swParse = ClientMetadata::parse(obj[kMetadataDoc]);
+ ASSERT_OK(swParse.getStatus());
+ ASSERT_EQUALS("f", swParse.getValue().get().getApplicationName());
+ }
+}
+
+// Mixed: no client metadata document
+TEST(ClientMetadatTest, TestEmptyDoc) {
+ {
+ auto parseStatus = ClientMetadata::parse(BSONElement());
+
+ ASSERT_OK(parseStatus.getStatus());
+ }
+
+ {
+ auto obj = BSON("client" << BSONObj());
+ auto parseStatus = ClientMetadata::parse(obj[kMetadataDoc]);
+
+ ASSERT_NOT_OK(parseStatus.getStatus());
+ }
+}
+
+// Positive: test with only required fields
+TEST(ClientMetadatTest, TestRequiredOnlyFields) {
+ // Without app name
+ ASSERT_DOC_OK(kDriver << BSON(kName << "n1" << kVersion << "v1") << kOperatingSystem
+ << BSON(kType << kUnknown));
+
+ // With AppName
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+
+// Positive: test with app_name spelled wrong fields
+TEST(ClientMetadatTest, TestWithAppNameSpelledWrong) {
+ ASSERT_DOC_OK(kApplication << BSON("extra"
+ << "1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+// Positive: test with empty application document
+TEST(ClientMetadatTest, TestWithEmptyApplication) {
+ ASSERT_DOC_OK(kApplication << BSONObj() << kDriver << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+// Negative: test with appplication wrong type
+TEST(ClientMetadatTest, TestNegativeWithAppNameWrongType) {
+ ASSERT_DOC_NOT_OK(kApplication << "1" << kDriver << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+}
+
+// Positive: test with extra fields
+TEST(ClientMetadatTest, TestExtraFields) {
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1"
+ << "extra"
+ << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown << "extra"
+ << "v1"));
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1")
+ << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown)
+ << "extra"
+ << "v1");
+}
+
+// Negative: only application specified
+TEST(ClientMetadatTest, TestNegativeOnlyApplication) {
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1"
+ << "extra"
+ << "v1"));
+}
+
+// Negative: all combinations of only missing 1 required field
+TEST(ClientMetadatTest, TestNegativeMissingRequiredOneField) {
+ ASSERT_DOC_NOT_OK(kDriver << BSON(kVersion << "v1") << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kDriver << BSON(kName << "n1") << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kDriver << BSON(kName << "n1" << kVersion << "v1"));
+}
+
+// Negative: document with wrong types for required fields
+TEST(ClientMetadatTest, TestNegativeWrongTypes) {
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << 1) << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << 1 << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << 1)
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "v1")
+ << kOperatingSystem
+ << BSON(kType << 1));
+}
+
+// Negative: document larger than 512 bytes
+TEST(ClientMetadatTest, TestNegativeLargeDocument) {
+ {
+ std::string str(350, 'x');
+ ASSERT_DOC_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown)
+ << "extra"
+ << str);
+ }
+ {
+ std::string str(512, 'x');
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown)
+ << "extra"
+ << str);
+ }
+}
+
+// Negative: document with app_name larger than 128 bytes
+TEST(ClientMetadatTest, TestNegativeLargeAppName) {
+ {
+ std::string str(128, 'x');
+ ASSERT_DOC_OK(kApplication << BSON(kName << str) << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+
+ BSONObjBuilder builder;
+ ASSERT_OK(ClientMetadata::serialize("n1", "1", str, &builder));
+ }
+ {
+ std::string str(129, 'x');
+ ASSERT_DOC_NOT_OK(kApplication << BSON(kName << str) << kDriver
+ << BSON(kName << "n1" << kVersion << "1")
+ << kOperatingSystem
+ << BSON(kType << kUnknown));
+
+ BSONObjBuilder builder;
+ ASSERT_NOT_OK(ClientMetadata::serialize("n1", "1", str, &builder));
+ }
+}
+
+} // namespace mongo