/*
 * 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 <mongoc/mongoc-client-private.h>
#include <mongoc/mongoc-server-description-private.h>
#include "TestSuite.h"
#include "test-conveniences.h"

void
reset_basic_sd (mongoc_server_description_t *sd)
{
   bson_error_t error;
   bson_t *hello;

   hello = BCON_NEW ("minWireVersion", BCON_INT32 (WIRE_VERSION_MIN), "maxWireVersion", BCON_INT32 (WIRE_VERSION_MAX));

   mongoc_server_description_reset (sd);
   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_handle_hello (sd, hello, 0 /* rtt */, &error);
   bson_destroy (hello);
}

/* These checks will start failing and need to be updated once CDRIVER-3527 is
 * addressed. */
static void
_test_hostlist (mongoc_server_description_t *sd1,
                bson_t *sd1_hostlist,
                mongoc_server_description_t *sd2,
                bson_t *sd2_hostlist)
{
   bson_reinit (sd1_hostlist);
   bson_reinit (sd2_hostlist);
   BSON_ASSERT (_mongoc_server_description_equal (sd1, sd2));

   /* [ "h1" ] vs [] */
   BSON_APPEND_UTF8 (sd1_hostlist, "0", "h1");
   BSON_ASSERT (!_mongoc_server_description_equal (sd1, sd2));

   /* [ "h1" ] vs [ "h1" ] */
   BSON_APPEND_UTF8 (sd2_hostlist, "0", "h1");
   BSON_ASSERT (_mongoc_server_description_equal (sd1, sd2));

   /* [ "h1", "h2", "h3" ] vs [ "h1" ] */
   BSON_APPEND_UTF8 (sd1_hostlist, "1", "h2");
   BSON_APPEND_UTF8 (sd1_hostlist, "2", "h3");
   BSON_ASSERT (!_mongoc_server_description_equal (sd1, sd2));

   /* [ "h1", "h2", "h3" ] vs [ "h1", "h3", "h2" ]. Considered unequal since we
    * do not do a set comparison. */
   BSON_APPEND_UTF8 (sd2_hostlist, "1", "h3");
   BSON_APPEND_UTF8 (sd2_hostlist, "2", "h2");
   BSON_ASSERT (!_mongoc_server_description_equal (sd1, sd2));

   /* [ "h1", "h1" ] vs [ "h1" ]. Considered unequal since we do not do a set
    * comparison. */
   bson_reinit (sd1_hostlist);
   bson_reinit (sd2_hostlist);
   BSON_APPEND_UTF8 (sd1_hostlist, "0", "h1");
   BSON_APPEND_UTF8 (sd1_hostlist, "1", "h1");
   BSON_APPEND_UTF8 (sd2_hostlist, "0", "h1");
   BSON_ASSERT (!_mongoc_server_description_equal (sd1, sd2));

   /* Arbitrary non-array like equal docs. Considered equal since we don't do
    * any parsing. */
   bson_reinit (sd1_hostlist);
   bson_reinit (sd2_hostlist);
   BSON_APPEND_UTF8 (sd1_hostlist, "test", "h1");
   BSON_APPEND_UTF8 (sd2_hostlist, "test", "h1");
   BSON_ASSERT (_mongoc_server_description_equal (sd1, sd2));
}

/* Unit test of server description. */
/* Check that variations on all (=) fields result in inequal, and variations on
 * all non (=) fields do not */
void
test_server_description_equal (void)
{
   mongoc_server_description_t sd1;
   mongoc_server_description_t sd2;

   mongoc_server_description_init (&sd1, "host:1234", 1);
   mongoc_server_description_init (&sd2, "host:1234", 2);

   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   BSON_ASSERT (_mongoc_server_description_equal (&sd1, &sd2));

   /* "address" differs, still considered equal. */
   sd2.connection_address = "host2:5678";
   BSON_ASSERT (_mongoc_server_description_equal (&sd1, &sd2));

   /* "roundTripTime" differs, still considered equal. */
   sd1.round_trip_time_msec = 1234;
   BSON_ASSERT (_mongoc_server_description_equal (&sd1, &sd2));

   /* "lastWriteDate"/"opTime" are stored in last_hello_response and not parsed
    * out. Check that overwriting the stored reply does not factor into the
    * equality check. */
   bson_reinit (&sd1.last_hello_response);
   BSON_ASSERT (_mongoc_server_description_equal (&sd1, &sd2));

   /* "error" differs, considered unequal. */
   bson_set_error (&sd1.error, MONGOC_ERROR_SERVER, 123, "some error");
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "type" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.type = MONGOC_SERVER_RS_GHOST;
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "minWireVersion" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.min_wire_version = 2;
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "maxWireVersion" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.max_wire_version = 7;
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "me" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.me = "test:1234";
   sd2.me = "test:1235";
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* But if "me" only differs only in case, considered equal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.me = "tesT:1234";
   sd2.me = "test:1234";
   BSON_ASSERT (_mongoc_server_description_equal (&sd1, &sd2));

   /* Test variations of "hosts", "passives", and "arbiters". */
   _test_hostlist (&sd1, &sd1.hosts, &sd2, &sd2.hosts);
   _test_hostlist (&sd1, &sd1.passives, &sd2, &sd2.passives);
   _test_hostlist (&sd1, &sd1.arbiters, &sd2, &sd2.arbiters);

   /* "tags" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   bson_reinit (&sd1.tags);
   BSON_APPEND_UTF8 (&sd1.tags, "tag", "nyc");
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "setName" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.set_name = "set";
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "setName" differs by case only, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.set_name = "set";
   sd2.set_name = "SET";
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "setVersion" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.set_version = 1;
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "electionId" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   bson_oid_init_from_string (&sd1.election_id, "000000000000000000001234");
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "primary" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.current_primary = "host";
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "primary" differs in case only, considered equal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.current_primary = "host";
   sd2.current_primary = "HOST";
   BSON_ASSERT (_mongoc_server_description_equal (&sd1, &sd2));

   /* "logicalSessionTimeoutMinutes" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   sd1.session_timeout_minutes = 1;
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   /* "compressors" differs, still considered equal since that is only
    * applicable for handshake. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   bson_reinit (&sd1.compressors);
   BSON_APPEND_UTF8 (&sd1.compressors, "0", "zstd");
   BSON_ASSERT (_mongoc_server_description_equal (&sd1, &sd2));

   /* "topologyVersion" differs, considered unequal. */
   reset_basic_sd (&sd1);
   reset_basic_sd (&sd2);
   BCON_APPEND (&sd1.topology_version, "x", BCON_INT32 (1));
   BSON_ASSERT (!_mongoc_server_description_equal (&sd1, &sd2));

   mongoc_server_description_cleanup (&sd1);
   mongoc_server_description_cleanup (&sd2);
}

/* Test that msg set to anything else besides "isdbgrid" is not considered
 * server type mongos */
void
test_server_description_msg_without_isdbgrid (void)
{
   mongoc_server_description_t sd;
   bson_t *hello;
   bson_error_t error;

   mongoc_server_description_init (&sd, "host:1234", 1);
   hello = BCON_NEW ("minWireVersion",
                     BCON_INT32 (WIRE_VERSION_MIN),
                     "maxWireVersion",
                     BCON_INT32 (WIRE_VERSION_MAX),
                     "msg",
                     "isdbgrid");
   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_handle_hello (&sd, hello, 0 /* rtt */, &error);
   BSON_ASSERT (sd.type == MONGOC_SERVER_MONGOS);

   mongoc_server_description_reset (&sd);
   bson_destroy (hello);
   hello = BCON_NEW ("minWireVersion",
                     BCON_INT32 (WIRE_VERSION_MIN),
                     "maxWireVersion",
                     BCON_INT32 (WIRE_VERSION_MAX),
                     "msg",
                     "something_else");
   mongoc_server_description_handle_hello (&sd, hello, 0 /* rtt */, &error);
   BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);

   bson_destroy (hello);
   mongoc_server_description_cleanup (&sd);
}

static void
test_server_description_ignores_rtt (void)
{
   mongoc_server_description_t sd;
   bson_error_t error;
   bson_t hello;

   bson_init (&hello);
   BCON_APPEND (&hello, "isWritablePrimary", BCON_BOOL (true));

   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_init (&sd, "host:1234", 1);
   /* Initially, the RTT is MONGOC_RTT_UNSET. */
   ASSERT_CMPINT64 (sd.round_trip_time_msec, ==, MONGOC_RTT_UNSET);
   BSON_ASSERT (sd.type == MONGOC_SERVER_UNKNOWN);
   /* If MONGOC_RTT_UNSET is passed as the RTT, it remains MONGOC_RTT_UNSET. */
   mongoc_server_description_handle_hello (&sd, &hello, MONGOC_RTT_UNSET, &error);
   ASSERT_CMPINT64 (sd.round_trip_time_msec, ==, MONGOC_RTT_UNSET);
   BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);
   /* The first real RTT overwrites the stored RTT. */
   mongoc_server_description_handle_hello (&sd, &hello, 10, &error);
   ASSERT_CMPINT64 (sd.round_trip_time_msec, ==, 10);
   BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);
   /* But subsequent MONGOC_RTT_UNSET values do not effect it. */
   mongoc_server_description_handle_hello (&sd, &hello, MONGOC_RTT_UNSET, &error);
   ASSERT_CMPINT64 (sd.round_trip_time_msec, ==, 10);
   BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);

   mongoc_server_description_cleanup (&sd);
   bson_destroy (&hello);
}

static void
test_server_description_hello (void)
{
   mongoc_server_description_t sd;
   bson_error_t error;
   bson_t hello_response;

   bson_init (&hello_response);
   BCON_APPEND (&hello_response, "isWritablePrimary", BCON_BOOL (true));

   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_init (&sd, "host:1234", 1);
   BSON_ASSERT (sd.type == MONGOC_SERVER_UNKNOWN);
   mongoc_server_description_handle_hello (&sd, &hello_response, 0, &error);
   BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);

   mongoc_server_description_cleanup (&sd);
   bson_destroy (&hello_response);
}

static void
test_server_description_hello_cmd_not_found (void)
{
   mongoc_server_description_t sd;
   bson_error_t error;
   const char *response = "{"
                          "  'ok' : 0,"
                          "  'errmsg' : 'no such command: \\'hello\\'',"
                          "  'code' : 59,"
                          "  'codeName' : 'CommandNotFound'"
                          "}";

   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_init (&sd, "host:1234", 1);
   BSON_ASSERT (sd.type == MONGOC_SERVER_UNKNOWN);
   mongoc_server_description_handle_hello (&sd, tmp_bson (response), 0, &error);
   BSON_ASSERT (sd.type == MONGOC_SERVER_UNKNOWN);

   mongoc_server_description_cleanup (&sd);
}

static void
test_server_description_legacy_hello (void)
{
   mongoc_server_description_t sd;
   bson_error_t error;
   bson_t hello_response;

   bson_init (&hello_response);
   BCON_APPEND (&hello_response, HANDSHAKE_RESPONSE_LEGACY_HELLO, BCON_BOOL (true));

   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_init (&sd, "host:1234", 1);
   BSON_ASSERT (sd.type == MONGOC_SERVER_UNKNOWN);
   mongoc_server_description_handle_hello (&sd, &hello_response, 0, &error);
   BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);
   BSON_ASSERT (!sd.hello_ok);

   mongoc_server_description_cleanup (&sd);
   bson_destroy (&hello_response);
}

static void
test_server_description_legacy_hello_ok (void)
{
   mongoc_server_description_t sd;
   bson_error_t error;
   bson_t hello_response;

   bson_init (&hello_response);
   BCON_APPEND (&hello_response, HANDSHAKE_RESPONSE_LEGACY_HELLO, BCON_BOOL (true));
   BCON_APPEND (&hello_response, "helloOk", BCON_BOOL (true));

   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_init (&sd, "host:1234", 1);
   BSON_ASSERT (sd.type == MONGOC_SERVER_UNKNOWN);
   mongoc_server_description_handle_hello (&sd, &hello_response, 0, &error);
   BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);
   BSON_ASSERT (sd.hello_ok);

   mongoc_server_description_cleanup (&sd);
   bson_destroy (&hello_response);
}

static void
test_server_description_connection_id (void)
{
   mongoc_server_description_t sd;
   bson_t *hello;
   bson_error_t error;

   // Test an int32.
   {
      mongoc_server_description_init (&sd, "host:1234", 1);
      hello = BCON_NEW ("minWireVersion",
                        BCON_INT32 (WIRE_VERSION_MIN),
                        "maxWireVersion",
                        BCON_INT32 (WIRE_VERSION_MAX),
                        "connectionId",
                        BCON_INT32 (1));
      memset (&error, 0, sizeof (bson_error_t));
      mongoc_server_description_handle_hello (&sd, hello, 0 /* rtt */, &error);
      BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);
      ASSERT_CMPINT64 (sd.server_connection_id, ==, 1);
      mongoc_server_description_cleanup (&sd);
      bson_destroy (hello);
   }
   // Test an int64.
   {
      mongoc_server_description_init (&sd, "host:1234", 1);
      hello = BCON_NEW ("minWireVersion",
                        BCON_INT32 (WIRE_VERSION_MIN),
                        "maxWireVersion",
                        BCON_INT32 (WIRE_VERSION_MAX),
                        "connectionId",
                        BCON_INT64 (1));
      memset (&error, 0, sizeof (bson_error_t));
      mongoc_server_description_handle_hello (&sd, hello, 0 /* rtt */, &error);
      BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);
      ASSERT_CMPINT64 (sd.server_connection_id, ==, 1);
      bson_destroy (hello);
      mongoc_server_description_cleanup (&sd);
   }
   // Test a double.
   {
      mongoc_server_description_init (&sd, "host:1234", 1);
      hello = BCON_NEW ("minWireVersion",
                        BCON_INT32 (WIRE_VERSION_MIN),
                        "maxWireVersion",
                        BCON_INT32 (WIRE_VERSION_MAX),
                        "connectionId",
                        BCON_DOUBLE (1));
      memset (&error, 0, sizeof (bson_error_t));
      mongoc_server_description_handle_hello (&sd, hello, 0 /* rtt */, &error);
      BSON_ASSERT (sd.type == MONGOC_SERVER_STANDALONE);
      ASSERT_CMPINT64 (sd.server_connection_id, ==, 1);
      bson_destroy (hello);
      mongoc_server_description_cleanup (&sd);
   }
}

static void
test_server_description_hello_type_error (void)
{
   mongoc_server_description_t sd;
   bson_error_t error;
   const char *hello = "{"
                       "  'ok' : { '$numberInt' : '1' },"
                       " 'ismaster' : true,"
                       " 'maxBsonObjectSize' : { '$numberInt' : '16777216' },"
                       " 'maxMessageSizeBytes' : { '$numberInt' : '48000000'},"
                       " 'maxWriteBatchSize' : { '$numberLong' : '565160423'},"
                       " 'logicalSessionTimeoutMinutes' : { '$numberInt' : '30'},"
                       " 'connectionId' : { '$numberLong' : '565160423'},"
                       " 'minWireVersion' : { '$numberInt' : '0'},"
                       " 'maxWireVersion' : { '$numberInt' : '15'},"
                       " 'readOnly' : true"
                       "}";
   mongoc_server_description_init (&sd, "host:1234", 1);
   memset (&error, 0, sizeof (bson_error_t));
   mongoc_server_description_handle_hello (&sd, tmp_bson (hello), 0, &error);
   BSON_ASSERT (sd.type == MONGOC_SERVER_UNKNOWN);
   BSON_ASSERT (sd.error.code == MONGOC_ERROR_STREAM_INVALID_TYPE);
   ASSERT_ERROR_CONTAINS (sd.error, MONGOC_ERROR_STREAM, MONGOC_ERROR_STREAM_INVALID_TYPE, "unexpected type");

   mongoc_server_description_cleanup (&sd);
}

static void
test_copy (const char *hello_json)
{
   mongoc_server_description_t sd, *sd_copy;
   mongoc_server_description_init (&sd, "host:1234", 1);
   bson_error_t empty_error = {0};
   mongoc_server_description_handle_hello (&sd, tmp_bson (hello_json), 0, &empty_error);
   sd_copy = mongoc_server_description_new_copy (&sd);

   // Check server descriptions compare equal by "Server Description Equality" rules. Not all fields are considered.
   ASSERT (_mongoc_server_description_equal (&sd, sd_copy));

   // Check all fields:
   ASSERT_CMPUINT32 (sd.id, ==, sd_copy->id);
   ASSERT_CMPSTR (sd.host.host_and_port, sd_copy->host.host_and_port);
   ASSERT_CMPINT64 (sd.round_trip_time_msec, ==, sd_copy->round_trip_time_msec);
   ASSERT_CMPINT64 (sd.last_update_time_usec, ==, sd_copy->last_update_time_usec);
   ASSERT_EQUAL_BSON (&sd.last_hello_response, &sd_copy->last_hello_response);
   ASSERT_CMPINT ((int) sd.has_hello_response, ==, (int) sd_copy->has_hello_response);
   ASSERT_CMPINT ((int) sd.hello_ok, ==, (int) sd_copy->hello_ok);
   ASSERT_CMPSTR (sd.connection_address, sd_copy->connection_address);
   ASSERT_CMPSTR (sd.me, sd_copy->me);
   ASSERT_CMPINT ((int) sd.opened, ==, (int) sd_copy->opened);
   ASSERT_CMPSTR (sd.set_name, sd_copy->set_name);
   ASSERT_MEMCMP (&sd.error, &sd_copy->error, (int) sizeof (bson_error_t));
   ASSERT_CMPINT ((int) sd.type, ==, (int) sd_copy->type);
   ASSERT_CMPINT32 (sd.min_wire_version, ==, sd_copy->min_wire_version);
   ASSERT_CMPINT32 (sd.max_wire_version, ==, sd_copy->max_wire_version);
   ASSERT_CMPINT32 (sd.max_msg_size, ==, sd_copy->max_msg_size);
   ASSERT_CMPINT32 (sd.max_bson_obj_size, ==, sd_copy->max_bson_obj_size);
   ASSERT_CMPINT32 (sd.max_write_batch_size, ==, sd_copy->max_write_batch_size);
   ASSERT_CMPINT64 (sd.session_timeout_minutes, ==, sd_copy->session_timeout_minutes);
   ASSERT_EQUAL_BSON (&sd.hosts, &sd_copy->hosts);
   ASSERT_EQUAL_BSON (&sd.passives, &sd_copy->passives);
   ASSERT_EQUAL_BSON (&sd.arbiters, &sd_copy->arbiters);
   ASSERT_EQUAL_BSON (&sd.tags, &sd_copy->tags);
   ASSERT_CMPSTR (sd.current_primary, sd_copy->current_primary);
   ASSERT_CMPINT64 (sd.set_version, ==, sd_copy->set_version);
   ASSERT_MEMCMP (&sd.election_id, &sd_copy->election_id, (int) sizeof (bson_oid_t));
   ASSERT_CMPINT64 (sd.last_write_date_ms, ==, sd_copy->last_write_date_ms);
   ASSERT_EQUAL_BSON (&sd.compressors, &sd_copy->compressors);
   ASSERT_EQUAL_BSON (&sd.topology_version, &sd_copy->topology_version);
   ASSERT_CMPUINT32 (sd.generation, ==, sd_copy->generation);
   ASSERT (sd_copy->_generation_map_ != NULL); // Do not compare entries. Just ensure non-NULL.
   ASSERT_MEMCMP (&sd.service_id, &sd_copy->service_id, (int) sizeof (bson_oid_t));
   ASSERT_CMPINT64 (sd.server_connection_id, ==, sd_copy->server_connection_id);

   mongoc_server_description_cleanup (&sd);
   mongoc_server_description_destroy (sd_copy);
}

static void
test_server_description_copy (void)
{
   const char *hello_mongod = BSON_STR ({
      "topologyVersion" : {"processId" : {"$oid" : "6792ef87965dee8797402adb"}, "counter" : 6},
      "hosts" : ["localhost:27017"],
      "setName" : "rs0",
      "setVersion" : 1,
      "isWritablePrimary" : true,
      "secondary" : false,
      "primary" : "localhost:27017",
      "me" : "localhost:27017",
      "electionId" : {"$oid" : "7fffffff0000000000000016"},
      "lastWrite" : {
         "opTime" : {"ts" : {"$timestamp" : {"t" : 1737682844, "i" : 1}}, "t" : 22},
         "lastWriteDate" : {"$date" : "2025-01-24T01:40:44Z"},
         "majorityOpTime" : {"ts" : {"$timestamp" : {"t" : 1737682844, "i" : 1}}, "t" : 22},
         "majorityWriteDate" : {"$date" : "2025-01-24T01:40:44Z"}
      },
      "maxBsonObjectSize" : 16777216,
      "maxMessageSizeBytes" : 48000000,
      "maxWriteBatchSize" : 100000,
      "localTime" : {"$date" : "2025-01-24T01:40:51.968Z"},
      "logicalSessionTimeoutMinutes" : 30,
      "connectionId" : 13,
      "minWireVersion" : 0,
      "maxWireVersion" : 25,
      "readOnly" : false,
      "ok" : 1.0,
      "$clusterTime" : {
         "clusterTime" : {"$timestamp" : {"t" : 1737682844, "i" : 1}},
         "signature" :
            {"hash" : {"$binary" : {"base64" : "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType" : "00"}}, "keyId" : 0}
      },
      "operationTime" : {"$timestamp" : {"t" : 1737682844, "i" : 1}}
   });

   const char *hello_mongos = BSON_STR ({
      "isWritablePrimary" : true,
      "msg" : "isdbgrid",
      "topologyVersion" : {"processId" : {"$oid" : "6791af1181771f367602ec40"}, "counter" : 0},
      "maxBsonObjectSize" : 16777216,
      "maxMessageSizeBytes" : 48000000,
      "maxWriteBatchSize" : 100000,
      "localTime" : {"$date" : "2025-01-24T01:24:57.217Z"},
      "logicalSessionTimeoutMinutes" : 30,
      "connectionId" : 3310,
      "maxWireVersion" : 25,
      "minWireVersion" : 0,
      "ok" : 1.0,
      "$clusterTime" : {
         "clusterTime" : {"$timestamp" : {"t" : 1737681896, "i" : 1}},
         "signature" :
            {"hash" : {"$binary" : {"base64" : "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType" : "00"}}, "keyId" : 0}
      },
      "operationTime" : {"$timestamp" : {"t" : 1737681896, "i" : 1}}
   });

   test_copy (hello_mongod);
   test_copy (hello_mongos);
}

void
test_server_description_install (TestSuite *suite)
{
   TestSuite_Add (suite, "/server_description/equal", test_server_description_equal);
   TestSuite_Add (suite, "/server_description/msg_without_isdbgrid", test_server_description_msg_without_isdbgrid);
   TestSuite_Add (suite, "/server_description/ignores_unset_rtt", test_server_description_ignores_rtt);
   TestSuite_Add (suite, "/server_description/hello", test_server_description_hello);
   TestSuite_Add (suite, "/server_description/hello_cmd_not_found", test_server_description_hello_cmd_not_found);
   TestSuite_Add (suite, "/server_description/legacy_hello", test_server_description_legacy_hello);
   TestSuite_Add (suite, "/server_description/legacy_hello_ok", test_server_description_legacy_hello_ok);
   TestSuite_Add (suite, "/server_description/connection_id", test_server_description_connection_id);
   TestSuite_Add (suite, "/server_description/hello_type_error", test_server_description_hello_type_error);
   TestSuite_Add (suite, "/server_description/copy", test_server_description_copy);
}
