summaryrefslogtreecommitdiff
path: root/src/mongo/rpc/metadata/client_metadata.cpp
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/metadata/client_metadata.cpp
parent931a227eedca19bc05fc6318996ffd3c6a2c6f4b (diff)
downloadmongo-ad27c92e01758c96e7ace4cba13574e0d97a761d.tar.gz
SERVER-24611 Implement ClientMetadata class
Diffstat (limited to 'src/mongo/rpc/metadata/client_metadata.cpp')
-rw-r--r--src/mongo/rpc/metadata/client_metadata.cpp400
1 files changed, 400 insertions, 0 deletions
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