/*
 * Copyright 2009-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <mongoc/mongoc.h>

#include <bson/bson.h>

#include <stdint.h>
#ifdef _POSIX_VERSION
#include <sys/utsname.h>
#endif

#include <mongoc/mongoc-client-private.h>
#include <mongoc/mongoc-config-private.h>
#include <mongoc/mongoc-handshake-private.h>

#include <mongoc/mongoc-handshake.h>

#include <mlib/config.h>

#include <TestSuite.h>
#include <mock_server/future-functions.h>
#include <mock_server/future.h>
#include <mock_server/mock-server.h>
#include <test-conveniences.h>
#include <test-libmongoc.h>

/*
 * Call this before any test which uses mongoc_handshake_data_append, to
 * reset the global state and unfreeze the handshake struct. Call it
 * after a test so later tests don't have a weird handshake document
 *
 * This is not safe to call while we have any clients or client pools running!
 */
static void
_reset_handshake(void)
{
   _mongoc_handshake_cleanup();
   _mongoc_handshake_init();
}

static const char *default_appname = "testapp";
static const char *default_driver_name = "php driver";
static const char *default_driver_version = "version abc";
static const char *default_platform = "./configure -nottoomanyflags";
static const int32_t default_timeout_sec = 60;
static const int32_t default_memory_mb = 1024;

static void
test_mongoc_handshake_appname_in_uri(void)
{
   char long_string[MONGOC_HANDSHAKE_APPNAME_MAX + 2];
   char *uri_str;
   const char *good_uri = "mongodb://host/?" MONGOC_URI_APPNAME "=mongodump";
   mongoc_uri_t *uri;
   const char *appname = "mongodump";
   const char *value;

   memset(long_string, 'a', MONGOC_HANDSHAKE_APPNAME_MAX + 1);
   long_string[MONGOC_HANDSHAKE_APPNAME_MAX + 1] = '\0';

   /* Shouldn't be able to set with appname really long */
   capture_logs(true);
   uri_str = bson_strdup_printf("mongodb://a/?" MONGOC_URI_APPNAME "=%s", long_string);
   ASSERT(!test_framework_client_new(uri_str, NULL));
   ASSERT_CAPTURED_LOG("_mongoc_topology_scanner_set_appname", MONGOC_LOG_LEVEL_WARNING, "Unsupported value");
   capture_logs(false);

   uri = mongoc_uri_new(good_uri);
   ASSERT(uri);
   value = mongoc_uri_get_appname(uri);
   ASSERT(value);
   ASSERT_CMPSTR(appname, value);
   mongoc_uri_destroy(uri);

   uri = mongoc_uri_new(NULL);
   ASSERT(uri);
   ASSERT(!mongoc_uri_set_appname(uri, long_string));
   ASSERT(mongoc_uri_set_appname(uri, appname));
   value = mongoc_uri_get_appname(uri);
   ASSERT(value);
   ASSERT_CMPSTR(appname, value);
   mongoc_uri_destroy(uri);

   bson_free(uri_str);
}

static void
test_mongoc_handshake_appname_frozen_single(void)
{
   mongoc_client_t *client;
   const char *good_uri = "mongodb://host/?" MONGOC_URI_APPNAME "=mongodump";

   client = test_framework_client_new(good_uri, NULL);

   /* Shouldn't be able to set appname again */
   capture_logs(true);
   ASSERT(!mongoc_client_set_appname(client, "a"));
   ASSERT_CAPTURED_LOG(
      "_mongoc_topology_scanner_set_appname", MONGOC_LOG_LEVEL_ERROR, "Cannot set appname more than once");
   capture_logs(false);

   mongoc_client_destroy(client);
}

static void
test_mongoc_handshake_appname_frozen_pooled(void)
{
   mongoc_client_pool_t *pool;
   const char *good_uri = "mongodb://host/?" MONGOC_URI_APPNAME "=mongodump";
   mongoc_uri_t *uri;

   uri = mongoc_uri_new(good_uri);

   pool = test_framework_client_pool_new_from_uri(uri, NULL);
   capture_logs(true);
   ASSERT(!mongoc_client_pool_set_appname(pool, "test"));
   ASSERT_CAPTURED_LOG(
      "_mongoc_topology_scanner_set_appname", MONGOC_LOG_LEVEL_ERROR, "Cannot set appname more than once");
   capture_logs(false);

   mongoc_client_pool_destroy(pool);
   mongoc_uri_destroy(uri);
}

static void
_check_arch_string_valid(const char *arch)
{
#ifdef _POSIX_VERSION
   struct utsname system_info;

   ASSERT(uname(&system_info) >= 0);
   ASSERT_CMPSTR(system_info.machine, arch);
#endif
   ASSERT(strlen(arch) > 0);
}

static void
_check_os_version_valid(const char *os_version)
{
#if defined(__linux__) || defined(_WIN32)
   /* On linux we search the filesystem for os version or use uname.
    * On windows we call GetSystemInfo(). */
   ASSERT(os_version);
   ASSERT(strlen(os_version) > 0);
#elif defined(_POSIX_VERSION)
   /* On a non linux posix systems, we just call uname() */
   struct utsname system_info;

   ASSERT(uname(&system_info) >= 0);
   ASSERT(os_version);
   ASSERT_CMPSTR(system_info.release, os_version);
#endif
}

static void
_handshake_check_application(bson_t *doc)
{
   bson_iter_t md_iter;
   bson_iter_t inner_iter;
   const char *val;

   ASSERT(bson_iter_init_find(&md_iter, doc, "application"));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&md_iter));
   ASSERT(bson_iter_recurse(&md_iter, &inner_iter));
   ASSERT(bson_iter_find(&inner_iter, "name"));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   ASSERT_CMPSTR(val, default_appname);
}

static void
_handshake_check_driver(bson_t *doc)
{
   bson_iter_t md_iter;
   bson_iter_t inner_iter;
   const char *val;
   ASSERT(bson_iter_init_find(&md_iter, doc, "driver"));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&md_iter));
   ASSERT(bson_iter_recurse(&md_iter, &inner_iter));
   ASSERT(bson_iter_find(&inner_iter, "name"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   ASSERT(strstr(val, default_driver_name) != NULL);
   ASSERT(bson_iter_find(&inner_iter, "version"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   ASSERT(strstr(val, default_driver_version));
}

static void
_handshake_check_os(bson_t *doc)
{
   bson_iter_t md_iter;
   bson_iter_t inner_iter;
   const char *val;

   /* Check os type not empty */
   ASSERT(bson_iter_init_find(&md_iter, doc, "os"));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&md_iter));
   ASSERT(bson_iter_recurse(&md_iter, &inner_iter));

   ASSERT(bson_iter_find(&inner_iter, "type"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   ASSERT(strlen(val) > 0);

   /* Check os version valid */
   ASSERT(bson_iter_find(&inner_iter, "version"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   _check_os_version_valid(val);

   /* Check os arch is valid */
   ASSERT(bson_iter_find(&inner_iter, "architecture"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   _check_arch_string_valid(val);

   /* Not checking os_name, as the spec says it can be NULL. */
}

static void
_handshake_check_env(bson_t *doc, int expected_memory_mb, int expected_timeout_sec, const char *expected_region)
{
   bson_iter_t md_iter;
   bson_iter_t inner_iter;
   const char *name;
   ASSERT(bson_iter_init_find(&md_iter, doc, "env"));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&md_iter));
   ASSERT(bson_iter_recurse(&md_iter, &inner_iter));

   ASSERT(bson_iter_find(&inner_iter, "name"));
   name = bson_iter_utf8(&inner_iter, NULL);

   bool is_aws = strstr(name, "aws.lambda") == name;
   bool is_azure = strstr(name, "azure.func") == name;
   bool is_gcp = strstr(name, "gcp.func") == name;
   bool is_vercel = strstr(name, "vercel") == name;
   ASSERT(is_aws || is_azure || is_gcp || is_vercel);

   if (expected_timeout_sec) {
      ASSERT(bson_iter_find(&inner_iter, "timeout_sec"));
      ASSERT(BSON_ITER_HOLDS_INT32(&inner_iter));
      ASSERT_CMPINT32(bson_iter_int32(&inner_iter), ==, expected_timeout_sec);
   }

   if (expected_memory_mb) {
      ASSERT(bson_iter_find(&inner_iter, "memory_mb"));
      ASSERT(BSON_ITER_HOLDS_INT32(&inner_iter));
      ASSERT_CMPINT32(bson_iter_int32(&inner_iter), ==, expected_memory_mb);
   }

   if (expected_region) {
      ASSERT(bson_iter_find(&inner_iter, "region"));
      ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
      ASSERT_CMPSTR(bson_iter_utf8(&inner_iter, NULL), expected_region);
   }
}

static void
_handshake_check_platform(bson_t *doc)
{
   bson_iter_t md_iter;
   const char *val;
   ASSERT(bson_iter_init_find(&md_iter, doc, "platform"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&md_iter));
   val = bson_iter_utf8(&md_iter, NULL);
   ASSERT(val);
   if (strlen(val) < 250) { /* standard val are < 100, may be truncated on some platform */
      ASSERT(strstr(val, default_platform) != NULL);
   }
}

static void
_handshake_check_env_name(bson_t *doc, const char *expected)
{
   bson_iter_t md_iter;
   bson_iter_t inner_iter;
   ASSERT(bson_iter_init_find(&md_iter, doc, "env"));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&md_iter));
   ASSERT(bson_iter_recurse(&md_iter, &inner_iter));
   const char *name;

   ASSERT(bson_iter_find(&inner_iter, "name"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   name = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(name);
   ASSERT_CMPSTR(name, expected);
}

static void
_handshake_check_required_fields(bson_t *doc)
{
   _handshake_check_application(doc);
   _handshake_check_driver(doc);
   _handshake_check_os(doc);
   _handshake_check_platform(doc);
}

// Start a mock server, get the driver's handshake doc, and clean up
static bson_t *
_get_handshake_document(bool default_append)
{
   mock_server_t *server;
   mongoc_uri_t *uri;
   mongoc_client_t *client;
   mongoc_client_pool_t *pool;
   request_t *request;
   const bson_t *request_doc;
   bson_iter_t iter;

   /* Make sure setting the handshake works */
   if (default_append) {
      ASSERT(mongoc_handshake_data_append(default_driver_name, default_driver_version, default_platform));
   }

   server = mock_server_new();
   mock_server_run(server);
   uri = mongoc_uri_copy(mock_server_get_uri(server));
   mongoc_uri_set_option_as_utf8(uri, MONGOC_URI_APPNAME, default_appname);
   pool = test_framework_client_pool_new_from_uri(uri, NULL);

   /* Force topology scanner to start */
   client = mongoc_client_pool_pop(pool);

   request = mock_server_receives_any_hello(server);
   ASSERT(request);
   request_doc = request_get_doc(request, 0);
   ASSERT(request_doc);
   ASSERT(bson_has_field(request_doc, HANDSHAKE_FIELD));

   ASSERT(bson_iter_init_find(&iter, request_doc, HANDSHAKE_FIELD));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&iter));

   uint32_t len;
   const uint8_t *data;
   bson_iter_document(&iter, &len, &data);
   bson_t *handshake_doc = bson_new_from_data(data, len);

   reply_to_request_simple(request, "{'ok': 1, 'isWritablePrimary': true}");
   request_destroy(request);

   /* Cleanup */
   mongoc_client_pool_push(pool, client);
   mongoc_client_pool_destroy(pool);
   mongoc_uri_destroy(uri);
   mock_server_destroy(server);

   return handshake_doc;
}

/* Override host info with plausible but short strings to avoid any
 * truncation */
static void
_override_host_platform_os(void)
{
   _reset_handshake();
   mongoc_handshake_t *md = _mongoc_handshake_get();
   bson_free(md->os_type);
   md->os_type = bson_strdup("Linux");
   bson_free(md->os_name);
   md->os_name = bson_strdup("mongoc");
   bson_free(md->driver_name);
   md->driver_name = bson_strdup("test_e");
   bson_free(md->driver_version);
   md->driver_version = bson_strdup("1.25.0");
   bson_free(md->platform);
   md->platform = bson_strdup("posix=1234");
   bson_free(md->compiler_info);
   md->compiler_info = bson_strdup("CC=GCC");
   bson_free(md->flags);
   md->flags = bson_strdup("CFLAGS=\"-fPIE\"");
}

// erase all FaaS variables used in testing
static void
clear_faas_env(void)
{
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", ""));
   ASSERT(_mongoc_setenv("AWS_LAMBDA_RUNTIME_API", ""));
   ASSERT(_mongoc_setenv("AWS_REGION", ""));
   ASSERT(_mongoc_setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", ""));
   ASSERT(_mongoc_setenv("FUNCTIONS_WORKER_RUNTIME", ""));
   ASSERT(_mongoc_setenv("K_SERVICE", ""));
   ASSERT(_mongoc_setenv("KUBERNETES_SERVICE_HOST", ""));
   ASSERT(_mongoc_setenv("FUNCTION_MEMORY_MB", ""));
   ASSERT(_mongoc_setenv("FUNCTION_TIMEOUT_SEC", ""));
   ASSERT(_mongoc_setenv("FUNCTION_REGION", ""));
   ASSERT(_mongoc_setenv("VERCEL", ""));
   ASSERT(_mongoc_setenv("VERCEL_REGION", ""));
}

static void
test_mongoc_handshake_data_append_success(void)
{
   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   bson_iter_t md_iter;
   bson_iter_init(&md_iter, doc);
   _handshake_check_application(doc);
   _handshake_check_driver(doc);
   _handshake_check_os(doc);
   _handshake_check_platform(doc);

   bson_destroy(doc);
   _reset_handshake();
}

static void
test_valid_aws_lambda(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("AWS_LAMBDA_RUNTIME_API", "foo"));
   ASSERT(_mongoc_setenv("AWS_REGION", "us-east-2"));
   ASSERT(_mongoc_setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);
   _handshake_check_env(doc, default_memory_mb, 0, "us-east-2");
   _handshake_check_env_name(doc, "aws.lambda");

   bson_iter_t iter;
   ASSERT(bson_iter_init_find(&iter, doc, "env"));
   bson_iter_t inner_iter;
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&iter));
   bson_iter_recurse(&iter, &inner_iter);

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_valid_aws_and_vercel(void *test_ctx)
{
   // Test that Vercel takes precedence over AWS. From the specification:
   // > When variables for multiple ``client.env.name`` values are present,
   // > ``vercel`` takes precedence over ``aws.lambda``

   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", "AWS_Lambda_java8"));
   ASSERT(_mongoc_setenv("AWS_REGION", "us-east-2"));
   ASSERT(_mongoc_setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024"));

   ASSERT(_mongoc_setenv("VERCEL", "1"));
   ASSERT(_mongoc_setenv("VERCEL_REGION", "cdg1"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);
   _handshake_check_env(doc, 0, 0, "cdg1");
   _handshake_check_env_name(doc, "vercel");

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_valid_aws(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", "AWS_Lambda_java8"));
   ASSERT(_mongoc_setenv("AWS_REGION", "us-east-2"));
   ASSERT(_mongoc_setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);

   _handshake_check_required_fields(doc);
   _handshake_check_env(doc, default_memory_mb, 0, "us-east-2");
   _handshake_check_env_name(doc, "aws.lambda");

   bson_iter_t iter;
   ASSERT(bson_iter_init_find(&iter, doc, "env"));
   bson_iter_t inner_iter;
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&iter));
   bson_iter_recurse(&iter, &inner_iter);

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_valid_azure(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("FUNCTIONS_WORKER_RUNTIME", "node"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);
   _handshake_check_env(doc, 0, 0, NULL);
   _handshake_check_env_name(doc, "azure.func");

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_valid_gcp(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("K_SERVICE", "servicename"));
   ASSERT(_mongoc_setenv("FUNCTION_MEMORY_MB", "1024"));
   ASSERT(_mongoc_setenv("FUNCTION_TIMEOUT_SEC", "60"));
   ASSERT(_mongoc_setenv("FUNCTION_REGION", "us-central1"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);
   _handshake_check_env(doc, default_memory_mb, default_timeout_sec, "us-central1");
   _handshake_check_env_name(doc, "gcp.func");

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_valid_vercel(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("VERCEL", "1"));
   ASSERT(_mongoc_setenv("VERCEL_REGION", "cdg1"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);
   _handshake_check_env(doc, 0, 0, "cdg1");
   _handshake_check_env_name(doc, "vercel");

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_multiple_faas(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   // Multiple FaaS variables must cause the entire env field to be omitted
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", "AWS_Lambda_java8"));
   ASSERT(_mongoc_setenv("FUNCTIONS_WORKER_RUNTIME", "node"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);

   ASSERT(!bson_has_field(doc, "env"));

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_truncate_region(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", "AWS_Lambda_java8"));
   const size_t region_len = 512;
   char long_region[512];
   memset(&long_region, 'a', region_len - 1);
   long_region[region_len - 1] = '\0';
   ASSERT(_mongoc_setenv("AWS_REGION", long_region));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);
   _handshake_check_env_name(doc, "aws.lambda");

   bson_iter_t iter;
   ASSERT(bson_iter_init_find(&iter, doc, "env"));
   bson_iter_t inner_iter;
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&iter));
   bson_iter_recurse(&iter, &inner_iter);
   ASSERT(!bson_iter_find(&inner_iter, "memory_mb"));
   ASSERT(!bson_iter_find(&inner_iter, "timeout_sec"));
   ASSERT(!bson_iter_find(&inner_iter, "region"));

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}


static void
test_wrong_types(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", "AWS_Lambda_java8"));
   ASSERT(_mongoc_setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "big"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);
   _handshake_check_env_name(doc, "aws.lambda");

   bson_iter_t iter;
   ASSERT(bson_iter_init_find(&iter, doc, "env"));
   bson_iter_t inner_iter;
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&iter));
   bson_iter_recurse(&iter, &inner_iter);
   ASSERT(!bson_iter_find(&iter, "memory_mb"));

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_aws_not_lambda(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   // Entire env field must be omitted with non-lambda AWS
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", "EC2"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);

   ASSERT(!bson_has_field(doc, "env"));

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_aws_and_container(void *test_ctx)
{
   BSON_UNUSED(test_ctx);
   ASSERT(_mongoc_setenv("AWS_EXECUTION_ENV", "AWS_Lambda_java8"));
   ASSERT(_mongoc_setenv("AWS_REGION", "us-east-2"));
   ASSERT(_mongoc_setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024"));
   ASSERT(_mongoc_setenv("KUBERNETES_SERVICE_HOST", "1"));

   _override_host_platform_os();
   bson_t *doc = _get_handshake_document(true);
   _handshake_check_required_fields(doc);

   ASSERT_CMPSTR(bson_lookup_utf8(doc, "container.orchestrator"), "kubernetes");
   ASSERT_CMPSTR(bson_lookup_utf8(doc, "env.name"), "aws.lambda");
   _handshake_check_env(doc, default_memory_mb, 0, "us-east-2");

   bson_destroy(doc);
   clear_faas_env();
   _reset_handshake();
}

static void
test_mongoc_handshake_data_append_null_args(void)
{
   bson_iter_t md_iter;
   bson_iter_t inner_iter;
   const char *val;

   /* Make sure setting the handshake works */
   ASSERT(mongoc_handshake_data_append(NULL, NULL, NULL));

   _reset_handshake();
   bson_t *handshake_doc = _get_handshake_document(false);
   bson_iter_init(&md_iter, handshake_doc);
   _handshake_check_application(handshake_doc);

   /* Make sure driver.name and driver.version and platform are all right */
   ASSERT(bson_iter_find(&md_iter, "driver"));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&md_iter));
   ASSERT(bson_iter_recurse(&md_iter, &inner_iter));
   ASSERT(bson_iter_find(&inner_iter, "name"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   ASSERT(strstr(val, " / ") == NULL); /* No append delimiter */

   ASSERT(bson_iter_find(&inner_iter, "version"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   ASSERT(strstr(val, " / ") == NULL); /* No append delimiter */

   /* Check os type not empty */
   ASSERT(bson_iter_find(&md_iter, "os"));
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&md_iter));
   ASSERT(bson_iter_recurse(&md_iter, &inner_iter));

   ASSERT(bson_iter_find(&inner_iter, "type"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   ASSERT(strlen(val) > 0);

   /* Check os version valid */
   ASSERT(bson_iter_find(&inner_iter, "version"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   _check_os_version_valid(val);

   /* Check os arch is valid */
   ASSERT(bson_iter_find(&inner_iter, "architecture"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&inner_iter));
   val = bson_iter_utf8(&inner_iter, NULL);
   ASSERT(val);
   _check_arch_string_valid(val);

   /* Not checking os_name, as the spec says it can be NULL. */

   /* Check platform field ok */
   ASSERT(bson_iter_find(&md_iter, "platform"));
   ASSERT(BSON_ITER_HOLDS_UTF8(&md_iter));
   val = bson_iter_utf8(&md_iter, NULL);
   ASSERT(val);
   /* standard val are < 100, may be truncated on some platform */
   if (strlen(val) < 250) {
      /* `printf("%s", NULL)` -> "(null)" with libstdc++, libc++, and STL */
      ASSERT(strstr(val, "null") == NULL);
   }

   bson_destroy(handshake_doc);
}


static void
_test_platform(bool platform_oversized)
{
   mongoc_handshake_t *md;
   size_t platform_len;
   const char *platform_suffix = "b";

   _reset_handshake();

   md = _mongoc_handshake_get();

   bson_free(md->os_type);
   md->os_type = bson_strdup("foo");
   bson_free(md->os_name);
   md->os_name = bson_strdup("foo");
   bson_free(md->os_version);
   md->os_version = bson_strdup("foo");
   bson_free(md->os_architecture);
   md->os_architecture = bson_strdup("foo");
   bson_free(md->driver_name);
   md->driver_name = bson_strdup("foo");
   bson_free(md->driver_version);
   md->driver_version = bson_strdup("foo");

   platform_len = HANDSHAKE_MAX_SIZE;
   if (platform_oversized) {
      platform_len += 100;
   }

   bson_free(md->platform);
   md->platform = bson_malloc(platform_len);
   memset(md->platform, 'a', platform_len - 1);
   md->platform[platform_len - 1] = '\0';

   /* returns true, but ignores the suffix; there's no room */
   ASSERT(mongoc_handshake_data_append(NULL, NULL, platform_suffix));
   ASSERT(!strstr(md->platform, "b"));

   bson_t *doc;
   ASSERT(doc = _mongoc_handshake_build_doc_with_application("my app"));
   ASSERT_CMPUINT32(doc->len, ==, (uint32_t)HANDSHAKE_MAX_SIZE);

   bson_destroy(doc);
   _reset_handshake(); /* frees the strings created above */
}


static void
test_mongoc_handshake_big_platform(void)
{
   _test_platform(false);
}


static void
test_mongoc_handshake_oversized_platform(void)
{
   _test_platform(true);
}


static void
test_mongoc_handshake_data_append_after_cmd(void)
{
   mongoc_client_pool_t *pool;
   mongoc_client_t *client;
   mongoc_uri_t *uri;

   _reset_handshake();

   uri = mongoc_uri_new("mongodb://127.0.0.1/?" MONGOC_URI_MAXPOOLSIZE "=1");

   /* Make sure that after we pop a client we can't set global handshake */
   pool = test_framework_client_pool_new_from_uri(uri, NULL);

   client = mongoc_client_pool_pop(pool);

   capture_logs(true);
   ASSERT(!mongoc_handshake_data_append("a", "a", "a"));
   capture_logs(false);

   mongoc_client_pool_push(pool, client);

   mongoc_uri_destroy(uri);
   mongoc_client_pool_destroy(pool);

   _reset_handshake();
}

/*
 * Append to the platform field a huge string
 * Make sure that it gets truncated
 */
static void
test_mongoc_handshake_too_big(void)
{
   mongoc_client_t *client;
   mock_server_t *server;
   mongoc_uri_t *uri;
   future_t *future;
   request_t *request;
   const bson_t *hello_doc;
   bson_iter_t iter;

   enum { BUFFER_SIZE = HANDSHAKE_MAX_SIZE };
   char big_string[BUFFER_SIZE];
   uint32_t len;
   const uint8_t *dummy;

   server = mock_server_new();
   mock_server_run(server);

   _reset_handshake();

   memset(big_string, 'a', BUFFER_SIZE - 1);
   big_string[BUFFER_SIZE - 1] = '\0';
   ASSERT(mongoc_handshake_data_append(NULL, NULL, big_string));

   uri = mongoc_uri_copy(mock_server_get_uri(server));
   /* avoid rare test timeouts */
   mongoc_uri_set_option_as_int32(uri, MONGOC_URI_CONNECTTIMEOUTMS, 20000);

   client = test_framework_client_new_from_uri(uri, NULL);

   ASSERT(mongoc_client_set_appname(client, "my app"));

   /* Send a ping, mock server deals with it */
   future = future_client_command_simple(client, "admin", tmp_bson("{'ping': 1}"), NULL, NULL, NULL);
   request = mock_server_receives_any_hello(server);

   /* Make sure the hello request has a handshake field, and it's not huge */
   ASSERT(request);
   hello_doc = request_get_doc(request, 0);
   ASSERT(hello_doc);
   ASSERT(bson_has_field(hello_doc, HANDSHAKE_FIELD));

   /* hello with handshake isn't too big */
   bson_iter_init_find(&iter, hello_doc, HANDSHAKE_FIELD);
   ASSERT(BSON_ITER_HOLDS_DOCUMENT(&iter));
   bson_iter_document(&iter, &len, &dummy);

   /* Should have truncated the platform field so it fits exactly */
   ASSERT(len == HANDSHAKE_MAX_SIZE);

   reply_to_request_simple(
      request, tmp_str("{'ok': 1, 'minWireVersion': %d, 'maxWireVersion': %d}", WIRE_VERSION_MIN, WIRE_VERSION_MAX));
   request_destroy(request);

   request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'$db': 'admin', 'ping': 1}"));

   reply_to_request_simple(request, "{'ok': 1}");
   ASSERT(future_get_bool(future));

   future_destroy(future);
   request_destroy(request);
   mongoc_client_destroy(client);
   mongoc_uri_destroy(uri);
   mock_server_destroy(server);

   /* So later tests don't have "aaaaa..." as the md platform string */
   _reset_handshake();
}

/*
 * Testing whether platform string data is truncated/dropped appropriately
 * drop specifies what case should be tested for
 */
static void
test_mongoc_platform_truncate(int drop)
{
   mongoc_handshake_t *md;
   bson_iter_t iter;

   char *undropped;
   char *expected;
   char big_string[HANDSHAKE_MAX_SIZE];
   memset(big_string, 'a', HANDSHAKE_MAX_SIZE - 1);
   big_string[HANDSHAKE_MAX_SIZE - 1] = '\0';

   /* Need to know how much space storing fields in our BSON will take
    * so that we can make our platform string the correct length here */
   _reset_handshake();

   /* we manually bypass the defaults of the handshake to ensure an exceedingly
    * long field does not cause our test to incorrectly fail */
   md = _mongoc_handshake_get();
   bson_free(md->os_type);
   md->os_type = bson_strdup("test_a");
   bson_free(md->os_name);
   md->os_name = bson_strdup("test_b");
   bson_free(md->os_architecture);
   md->os_architecture = bson_strdup("test_d");
   bson_free(md->driver_name);
   md->driver_name = bson_strdup("test_e");
   bson_free(md->driver_version);
   md->driver_version = bson_strdup("test_f");

   // Set all fields used to generate the platform string to empty
   bson_free(md->platform);
   md->platform = bson_strdup("");
   bson_free(md->compiler_info);
   md->compiler_info = bson_strdup("");
   bson_free(md->flags);
   md->flags = bson_strdup("");

   // Set os_version to a long string to force drop all os fields except name
   bson_free(md->os_version);
   md->os_version = big_string;

   bson_t *handshake_no_platform = _mongoc_handshake_build_doc_with_application(default_appname);
   size_t handshake_remaining_space = HANDSHAKE_MAX_SIZE - handshake_no_platform->len;
   bson_destroy(handshake_no_platform);

   md->os_version = bson_strdup("test_c");
   bson_free(md->compiler_info);
   md->compiler_info = bson_strdup("test_g");
   bson_free(md->flags);
   md->flags = bson_strdup("test_h");

   /* adjust remaining space depending on which combination of
    * flags/compiler_info we want to test dropping */
   if (drop == 2) {
      undropped = bson_strdup_printf("%s", "");
   } else if (drop == 1) {
      handshake_remaining_space -= strlen(md->compiler_info);
      undropped = bson_strdup_printf("%s", md->compiler_info);
   } else {
      handshake_remaining_space -= strlen(md->flags) + strlen(md->compiler_info);
      undropped = bson_strdup_printf("%s%s", md->compiler_info, md->flags);
   }

   big_string[handshake_remaining_space] = '\0';
   ASSERT(mongoc_handshake_data_append(NULL, NULL, big_string));

   bson_t *doc;
   ASSERT(doc = _mongoc_handshake_build_doc_with_application(default_appname));

   /* doc.len being strictly less than HANDSHAKE_MAX_SIZE proves that we have
    * dropped the flags correctly, instead of truncating anything
    */

   ASSERT_CMPUINT32(doc->len, <=, (uint32_t)HANDSHAKE_MAX_SIZE);
   ASSERT(bson_iter_init_find(&iter, doc, "platform"));
   expected = bson_strdup_printf("%s%s", big_string, undropped);
   ASSERT_CMPSTR(bson_iter_utf8(&iter, NULL), expected);

   bson_free(expected);
   bson_free(undropped);
   bson_destroy(doc);
   /* So later tests don't have "aaaaa..." as the md platform string */
   _reset_handshake();
}

/*
 * Test dropping neither compiler_info/flags, dropping just flags, and dropping
 * both
 */
static void
test_mongoc_oversized_flags(void)
{
   test_mongoc_platform_truncate(0);
   test_mongoc_platform_truncate(1);
   test_mongoc_platform_truncate(2);
}

/* Test the case where we can't prevent the handshake doc being too big
 * and so we just don't send it */
static void
test_mongoc_handshake_cannot_send(void)
{
   mock_server_t *server;
   mongoc_uri_t *uri;
   mongoc_client_t *client;
   mongoc_client_pool_t *pool;
   request_t *request;
   const char *const server_reply = "{'ok': 1, 'isWritablePrimary': true}";
   const bson_t *request_doc;
   char big_string[HANDSHAKE_MAX_SIZE];

   _reset_handshake();
   capture_logs(true);

   memset(big_string, 'a', HANDSHAKE_MAX_SIZE - 1);
   big_string[HANDSHAKE_MAX_SIZE - 1] = '\0';

   /* The handshake cannot be built if a field that cannot be dropped
    * (os.type) is set to a very long string */
   mongoc_handshake_t *md = _mongoc_handshake_get();
   bson_free(md->os_type);
   md->os_type = bson_strdup(big_string);

   server = mock_server_new();
   mock_server_run(server);
   uri = mongoc_uri_copy(mock_server_get_uri(server));
   mongoc_uri_set_option_as_int32(uri, MONGOC_URI_HEARTBEATFREQUENCYMS, 500);
   pool = test_framework_client_pool_new_from_uri(uri, NULL);

   /* Pop a client to trigger the topology scanner */
   client = mongoc_client_pool_pop(pool);
   request = mock_server_receives_any_hello(server);

   /* Make sure the hello request DOESN'T have a handshake field: */
   ASSERT(request);
   request_doc = request_get_doc(request, 0);
   ASSERT(request_doc);
   ASSERT(!bson_has_field(request_doc, HANDSHAKE_FIELD));

   reply_to_request_simple(request, server_reply);
   request_destroy(request);

   /* Cause failure on client side */
   request = mock_server_receives_any_hello(server);
   ASSERT(request);
   reply_to_request_with_hang_up(request);
   request_destroy(request);

   /* Make sure the hello request still DOESN'T have a handshake field
    * on subsequent heartbeats. */
   request = mock_server_receives_any_hello(server);
   ASSERT(request);
   request_doc = request_get_doc(request, 0);
   ASSERT(request_doc);
   ASSERT(!bson_has_field(request_doc, HANDSHAKE_FIELD));

   reply_to_request_simple(request, server_reply);
   request_destroy(request);

   /* cleanup */
   mongoc_client_pool_push(pool, client);

   mongoc_client_pool_destroy(pool);
   mongoc_uri_destroy(uri);
   mock_server_destroy(server);

   /* Reset again so the next tests don't have a handshake doc which
    * is too big */
   _reset_handshake();
}

/* Test the case where the driver does not raise an error if saslSupportedMechs attribute
of the initial handshake reply contains an unknown mechanism. */
static void
test_mongoc_handshake_no_validation_for_sasl_supported_mech(void)
{
   mongoc_client_t *client;
   mock_server_t *server;
   mongoc_uri_t *uri;
   future_t *future;
   request_t *request;
   const bson_t *doc;

   server = mock_server_new();
   mock_server_run(server);

   uri = mongoc_uri_copy(mock_server_get_uri(server));
   /* avoid rare test timeouts */
   mongoc_uri_set_option_as_int32(uri, MONGOC_URI_CONNECTTIMEOUTMS, 20000);

   client = test_framework_client_new_from_uri(uri, NULL);
   ASSERT(mongoc_client_set_appname(client, "my app"));

   /* Send a ping where 'saslSupportedMechs' contains an arbitrary string  */
   future = future_client_command_simple(
      client, "admin", tmp_bson("{'ping': 1, 'saslSupportedMechs': 'unknownMechanism'}"), NULL, NULL, NULL);
   ASSERT(future);
   request = mock_server_receives_any_hello(server);
   ASSERT(request);

   reply_to_request_simple(
      request, tmp_str("{'ok': 1, 'minWireVersion': %d, 'maxWireVersion': %d}", WIRE_VERSION_MIN, WIRE_VERSION_MAX));
   request_destroy(request);
   request = mock_server_receives_msg(server, MONGOC_MSG_NONE, tmp_bson("{'$db': 'admin', 'ping': 1}"));

   ASSERT(request);
   doc = request_get_doc(request, 0);
   ASSERT(doc);
   ASSERT(bson_has_field(doc, "saslSupportedMechs"));

   reply_to_request_simple(request, "{'ok': 1}");
   ASSERT(future_get_bool(future));

   future_destroy(future);
   request_destroy(request);
   mongoc_client_destroy(client);
   mongoc_uri_destroy(uri);
   mock_server_destroy(server);

   ASSERT_NO_CAPTURED_LOGS("mongoc_handshake_no_validation_for_sasl_supported_mechs");
}

extern char *
_mongoc_handshake_get_config_hex_string(void);

static bool
_get_bit(char *config_str, uint32_t bit)
{
   /* get the location of the two byte chars for this bit. */
   uint32_t byte = bit / 8;
   uint32_t bit_of_byte = bit % 8;
   uint32_t char_loc;
   uint32_t as_num;
   char byte_str[3];

   /* byte 0 is represented at the last two characters. */
   char_loc = (uint32_t)strlen(config_str) - 2 - (byte * 2);
   /* index should be past the prefixed "0x" */
   ASSERT_CMPINT32(char_loc, >, 1);
   /* get the number representation of the byte. */
   byte_str[0] = config_str[char_loc];
   byte_str[1] = config_str[char_loc + 1];
   byte_str[2] = '\0';
   as_num = (uint8_t)strtol(byte_str, NULL, 16);
   return (as_num & (1u << bit_of_byte)) > 0u;
}

void
test_handshake_platform_config(void)
{
   /* Parse the config string, and check that it matches the defined flags. */
   char *config_str = _mongoc_handshake_get_config_hex_string();
   uint32_t total_bytes = (LAST_MONGOC_MD_FLAG + 7) / 8;
   uint32_t total_bits = 8 * total_bytes;
   uint32_t i;
   /* config_str should have the form 0x?????. */
   ASSERT_CMPINT((int)strlen(config_str), ==, 2 + (2 * total_bytes));
   BSON_ASSERT(strncmp(config_str, "0x", 2) == 0);

/* go through all flags. */
#ifdef MONGOC_ENABLE_SSL_SECURE_CHANNEL
   BSON_ASSERT(_get_bit(config_str, MONGOC_ENABLE_SSL_SECURE_CHANNEL));
#endif

#ifdef MONGOC_ENABLE_CRYPTO_CNG
   BSON_ASSERT(_get_bit(config_str, MONGOC_ENABLE_CRYPTO_CNG));
#endif

#ifdef MONGOC_HAVE_BCRYPT_PBKDF2
   BSON_ASSERT(_get_bit(config_str, MONGOC_HAVE_BCRYPT_PBKDF2));
#endif

#ifdef MONGOC_ENABLE_SSL_SECURE_TRANSPORT
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SSL_SECURE_TRANSPORT));
#endif

#ifdef MONGOC_ENABLE_CRYPTO_COMMON_CRYPTO
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_CRYPTO_COMMON_CRYPTO));
#endif

#ifdef MONGOC_ENABLE_SSL_OPENSSL
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SSL_OPENSSL));
#endif

#ifdef MONGOC_ENABLE_CRYPTO_LIBCRYPTO
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_CRYPTO_LIBCRYPTO));
#endif

#ifdef MONGOC_ENABLE_SSL
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SSL));
#endif

#ifdef MONGOC_ENABLE_CRYPTO
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_CRYPTO));
#endif

#ifdef MONGOC_ENABLE_CRYPTO_SYSTEM_PROFILE
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_CRYPTO_SYSTEM_PROFILE));
#endif

#ifdef MONGOC_ENABLE_SASL
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SASL));
#endif

#ifdef MONGOC_HAVE_SASL_CLIENT_DONE
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_HAVE_SASL_CLIENT_DONE));
#endif

   BSON_ASSERT(!_get_bit(config_str, MONGOC_MD_FLAG_NO_AUTOMATIC_GLOBALS_UNUSED)); // Flag was removed.

#ifdef MONGOC_EXPERIMENTAL_FEATURES
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_EXPERIMENTAL_FEATURES));
#endif

#ifdef MONGOC_ENABLE_SASL_CYRUS
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SASL_CYRUS));
#endif

#ifdef MONGOC_ENABLE_SASL_SSPI
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SASL_SSPI));
#endif

#ifdef MONGOC_HAVE_SOCKLEN
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_HAVE_SOCKLEN));
#endif

#ifdef MONGOC_ENABLE_COMPRESSION
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_COMPRESSION));
#endif

#ifdef MONGOC_ENABLE_COMPRESSION_SNAPPY
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_COMPRESSION_SNAPPY));
#endif

#ifdef MONGOC_ENABLE_COMPRESSION_ZLIB
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_COMPRESSION_ZLIB));
#endif

#ifdef MONGOC_MD_FLAG_ENABLE_SASL_GSSAPI
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SASL_GSSAPI));
#endif

#ifdef MONGOC_HAVE_RES_NSEARCH
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_RES_NSEARCH));
#endif

#ifdef MONGOC_HAVE_RES_NDESTROY
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_RES_NDESTROY));
#endif

#ifdef MONGOC_HAVE_RES_NCLOSE
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_RES_NCLOSE));
#endif

#ifdef MONGOC_HAVE_RES_SEARCH
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_RES_SEARCH));
#endif

#ifdef MONGOC_HAVE_DNSAPI
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_DNSAPI));
#endif

#ifdef MONGOC_HAVE_RDTSCP
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_RDTSCP));
#endif

#ifdef MONGOC_HAVE_SCHED_GETCPU
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_HAVE_SCHED_GETCPU));
#endif

   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SRV) == MONGOC_SRV_ENABLED);

#ifdef MONGOC_ENABLE_SHM_COUNTERS
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_SHM_COUNTERS));
#endif

   mlib_diagnostic_push();
   mlib_disable_constant_conditional_expression_warnings();
   if (MONGOC_TRACE_ENABLED) {
      BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_TRACE));
   }
   mlib_diagnostic_pop();

   // Check that `MONGOC_MD_FLAG_ENABLE_ICU` is always unset. libicu dependency
   // was removed in CDRIVER-4680.
   BSON_ASSERT(!_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_ICU_UNUSED));

#ifdef MONGOC_ENABLE_CLIENT_SIDE_ENCRYPTION
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_CLIENT_SIDE_ENCRYPTION));
#endif

#ifdef MONGOC_ENABLE_MONGODB_AWS_AUTH
   BSON_ASSERT(_get_bit(config_str, MONGOC_MD_FLAG_ENABLE_MONGODB_AWS_AUTH));
#endif

   /* any excess bits should all be zero. */
   for (i = LAST_MONGOC_MD_FLAG; i < total_bits; i++) {
      BSON_ASSERT(!_get_bit(config_str, i));
   }
   bson_free(config_str);
}

/* Called by a single thread in test_mongoc_handshake_race_condition */
static BSON_THREAD_FUN(handshake_append_worker, data)
{
   BSON_UNUSED(data);

   mongoc_handshake_data_append(default_driver_name, default_driver_version, default_platform);

   BSON_THREAD_RETURN;
}

/* Run 1000 iterations of mongoc_handshake_data_append() using 4 threads */
static void
test_mongoc_handshake_race_condition(void)
{
   unsigned i, j;
   bson_thread_t threads[4];

   for (i = 0; i < 1000; ++i) {
      _reset_handshake();

      for (j = 0; j < 4; ++j) {
         BSON_ASSERT(!mcommon_thread_create(&threads[j], &handshake_append_worker, NULL /* args */));
      }
      for (j = 0; j < 4; ++j) {
         mcommon_thread_join(threads[j]);
      }
   }

   _reset_handshake();
}

static void
test_mongoc_handshake_cpp(void)
{
   bson_t *handshake = _mongoc_handshake_build_doc_with_application("foo");
   const char *platform = bson_lookup_utf8(handshake, "platform");
   if (0 != strlen(MONGOC_CXX_COMPILER_VERSION)) {
      ASSERT_CONTAINS(platform, "CXX=" MONGOC_CXX_COMPILER_ID " " MONGOC_CXX_COMPILER_VERSION);
   } else {
      ASSERT_CONTAINS(platform, "CXX=" MONGOC_CXX_COMPILER_ID);
   }
   bson_destroy(handshake);
}

void
test_handshake_install(TestSuite *suite)
{
   TestSuite_Add(suite, "/MongoDB/handshake/appname_in_uri", test_mongoc_handshake_appname_in_uri);
   TestSuite_Add(suite, "/MongoDB/handshake/appname_frozen_single", test_mongoc_handshake_appname_frozen_single);
   TestSuite_Add(suite, "/MongoDB/handshake/appname_frozen_pooled", test_mongoc_handshake_appname_frozen_pooled);

   TestSuite_AddMockServerTest(suite, "/MongoDB/handshake/success", test_mongoc_handshake_data_append_success);
   TestSuite_AddMockServerTest(suite, "/MongoDB/handshake/null_args", test_mongoc_handshake_data_append_null_args);
   TestSuite_Add(suite, "/MongoDB/handshake/big_platform", test_mongoc_handshake_big_platform);
   TestSuite_Add(suite, "/MongoDB/handshake/oversized_platform", test_mongoc_handshake_oversized_platform);
   TestSuite_Add(suite, "/MongoDB/handshake/failure", test_mongoc_handshake_data_append_after_cmd);
   TestSuite_AddMockServerTest(suite, "/MongoDB/handshake/too_big", test_mongoc_handshake_too_big);
   TestSuite_Add(suite, "/MongoDB/handshake/oversized_flags", test_mongoc_oversized_flags);
   TestSuite_AddMockServerTest(suite, "/MongoDB/handshake/cannot_send", test_mongoc_handshake_cannot_send);
   TestSuite_AddMockServerTest(suite,
                               "/MongoDB/handshake/no_validation_for_sasl_supported_mech",
                               test_mongoc_handshake_no_validation_for_sasl_supported_mech);
   TestSuite_Add(suite, "/MongoDB/handshake/platform_config", test_handshake_platform_config);
   TestSuite_Add(suite, "/MongoDB/handshake/race_condition", test_mongoc_handshake_race_condition);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/valid_aws",
                     test_valid_aws,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/valid_azure",
                     test_valid_azure,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/valid_gcp",
                     test_valid_gcp,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/valid_aws_lambda",
                     test_valid_aws_lambda,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/valid_aws_and_vercel",
                     test_valid_aws_and_vercel,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/valid_vercel",
                     test_valid_vercel,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/multiple",
                     test_multiple_faas,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/truncate_region",
                     test_truncate_region,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/wrong_types",
                     test_wrong_types,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/aws_not_lambda",
                     test_aws_not_lambda,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_AddFull(suite,
                     "/MongoDB/handshake/faas/aws_and_container",
                     test_aws_and_container,
                     NULL /* dtor */,
                     NULL /* ctx */,
                     test_framework_skip_if_no_setenv);
   TestSuite_Add(suite, "/MongoDB/handshake/includes_c++", test_mongoc_handshake_cpp);
}
