/**
 * Tests ingress connection metrics.
 *
 * @tags: [requires_fcv_63]
 */
import {detectDefaultTLSProtocol, determineSSLProvider} from "jstests/ssl/libs/ssl_helpers.js";

// Short circuits for system configurations that do not support this setParameter, (i.e. OpenSSL
// versions that don't support TLS 1.3)
if (determineSSLProvider() === "openssl" && detectDefaultTLSProtocol() !== "TLS1_3") {
    jsTestLog("Platform does not support TLS 1.3; skipping test.");
    quit();
}

// We use 'opensslCipherSuiteConfig' to deterministically set the cipher suite negotiated when
// openSSL is being used. This can be different on Windows/OSX implementations.
let cipherSuite = "TLS_AES_256_GCM_SHA384";

const mongodOptions = (connectionHealthLoggingOn) => {
    let options = {
        tlsMode: "requireTLS",
        tlsCertificateKeyFile: "jstests/libs/server.pem",
        tlsCAFile: "jstests/libs/ca.pem",
        setParameter: {
            opensslCipherSuiteConfig: cipherSuite,
            enableDetailedConnectionHealthMetricLogLines: connectionHealthLoggingOn
        },
    };

    return options;
};

function testConn(mongod) {
    const mongo = runMongoProgram('mongo',
                                  '--host',
                                  'localhost',
                                  '--port',
                                  mongod.port,
                                  '--tls',
                                  '--tlsCAFile',
                                  'jstests/libs/ca.pem',
                                  '--tlsCertificateKeyFile',
                                  'jstests/libs/client.pem',
                                  '--eval',
                                  ';');
    return mongo === 0;
}

let runTest = (connectionHealthLoggingOn) => {
    jsTestLog("Establishing connection to mongod");
    let mongod = MongoRunner.runMongod(Object.merge(mongodOptions(connectionHealthLoggingOn)));
    let ssNetworkMetrics = mongod.adminCommand({serverStatus: 1}).metrics.network;
    let initialHandshakeTimeMillis = ssNetworkMetrics.totalIngressTLSHandshakeTimeMillis;
    jsTestLog(`totalTLSHandshakeTimeMillis: ${initialHandshakeTimeMillis}`);

    if (connectionHealthLoggingOn) {
        checkLog.containsJson(
            mongod, 6723804, {durationMillis: Number(initialHandshakeTimeMillis)});
    } else {
        assert.eq(checkLog.checkContainsOnceJson(mongod, 6723804, {}), false);
    }

    assert.commandWorked(mongod.adminCommand({clearLog: 'global'}));
    assert.eq(1, ssNetworkMetrics.totalIngressTLSConnections, ssNetworkMetrics);

    // Get the logId that corresponds to the implementation of TLS being used.
    let logId;
    switch (determineSSLProvider()) {
        case "openssl":
            logId = 6723801;
            break;
        case "windows":
            logId = 6723802;
            // This cipher is chosen to represent the cipher negotiated by Windows Server 2019
            // by default.
            cipherSuite = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
            break;
        case "apple":
            logId = 6723803;
            // We log only the cipher represented as its enum value in this code path. This
            // corresponds to the hex value 0xC030 which maps to the cipher suite
            // "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384". This cipher is chosen by OSX 12.1 by
            // default.
            cipherSuite = 49200;
            break;
        default:
            assert(false, "Failed to determine that we are using a supported SSL provider");
    }

    // Start a new connection to check that 'durationMicros' is cumulatively measured in server
    // status.
    assert.soon(() => testConn(mongod), "Couldn't connect to mongod");
    ssNetworkMetrics = mongod.adminCommand({serverStatus: 1}).metrics.network;
    let totalTLSHandshakeTimeMillis = ssNetworkMetrics.totalIngressTLSHandshakeTimeMillis;
    jsTestLog(`totalTLSHandshakeTimeMillis: ${totalTLSHandshakeTimeMillis}`);
    let secondHandshakeDuration = totalTLSHandshakeTimeMillis - initialHandshakeTimeMillis;

    if (connectionHealthLoggingOn) {
        checkLog.containsJson(mongod, 6723804, {durationMillis: Number(secondHandshakeDuration)});
        assert.soon(() => checkLog.checkContainsOnceJson(mongod, logId, {"cipher": cipherSuite}),
                    "failed waiting for log line with negotiated cipher info");
    } else {
        assert.eq(checkLog.checkContainsOnceJson(mongod, 6723804, {}), false);
        assert.eq(checkLog.checkContainsOnceJson(mongod, logId, {}), false);
    }

    assert.gt(totalTLSHandshakeTimeMillis, initialHandshakeTimeMillis);
    assert.eq(2, ssNetworkMetrics.totalIngressTLSConnections, ssNetworkMetrics);

    MongoRunner.stopMongod(mongod);
};

// Parameterized on turning connection health logging on/off.
runTest(true);
runTest(false);
