//! FIFA 17's TLS profile — what the retail client was *observed* to offer.
//!
//! # Evidence, not assumption
//!
//! A retail FIFA 17 client was captured through a passive proxy while reaching
//! the FUT hub. It offers **exactly eight suites, every one static-RSA**:
//!
//! ```text
//!   0x009D TLS_RSA_WITH_AES_256_GCM_SHA384    0x0035 TLS_RSA_WITH_AES_256_CBC_SHA
//!   0x009C TLS_RSA_WITH_AES_128_GCM_SHA256    0x002F TLS_RSA_WITH_AES_128_CBC_SHA
//!   0x003D TLS_RSA_WITH_AES_256_CBC_SHA256    0x0005 TLS_RSA_WITH_RC4_128_SHA
//!   0x003C TLS_RSA_WITH_AES_128_CBC_SHA256    0x0004 TLS_RSA_WITH_RC4_128_MD5
//!
//!   client_version TLS 1.2   extensions: server_name, signature_algorithms only
//! ```
//!
//! Zero forward-secret suites — which is why `rustls` cannot serve this client,
//! and why the transport hosts link OpenSSL directly.
//!
//! # Deliberately not enabled
//!
//! * **RC4 and MD5.** The client offers them; it does not need them. It already
//!   negotiates `AES256-GCM-SHA384` against the Python oracle, so resurrecting
//!   RC4 for completeness would weaken the service for nothing.
//! * **SSLv3.** Never.
//! * **A lowered security level.** Not applied pre-emptively. The default
//!   policy is tried first; if a retail handshake fails because OpenSSL rejects
//!   something genuinely required, the narrowest possible change is made and
//!   documented — not a blanket `SECLEVEL=0`.
//!
//! # Why this is data and not code
//!
//! These are facts about FIFA 17, so they live in the FIFA 17 adapter rather
//! than in `openfut-tls`, which must stay game-independent. They are plain
//! strings so this crate keeps its lean dependency list: an adapter should not
//! drag OpenSSL into the build of everything that reads a card table.
//!
//! Confirmed live on 2026-08-11: the retail client reached the FUT hub through
//! a Rust host configured from exactly these values, negotiating
//! `TLSv1.2 / AES256-GCM-SHA384` with `sni=winter15.gosredirector.ea.com`.

/// The suites enabled for FIFA 17: the six RSA+AES options the client offers,
/// strongest first, in OpenSSL's pre-TLS-1.3 naming.
///
/// Order expresses preference; the client's own order put AES-256-GCM first
/// anyway, which is what the live handshake selected.
pub const CIPHER_LIST: &str =
    "AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA";

/// Suites the observed client offers that are deliberately refused.
pub const REFUSED_SUITES: [&str; 2] = ["RC4-SHA", "RC4-MD5"];

/// All eight suites the captured ClientHello offered, for handshake rehearsals.
pub const OBSERVED_CLIENT_SUITES: &str =
    "AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:\
     AES256-SHA:AES128-SHA:RC4-SHA:RC4-MD5";

/// The SNI the retail client sends to the redirector.
///
/// Nothing routes on it — recorded so a rehearsal handshake matches the real
/// one, and so a log line can show whether a connection came from the game or
/// from a probe.
pub const CLIENT_SNI: &str = "winter15.gosredirector.ea.com";

/// Protocol floor, as OpenSSL spells it.
///
/// The floor is NOT dropped to TLS 1.0 pre-emptively. The Python oracle permits
/// it, but no evidence shows this client needs it, and "the oracle permits it"
/// is not "the client requires it".
pub const MIN_VERSION: &str = "TLSv1.2";

/// Protocol ceiling. The client offers no TLS 1.3, so the window is exact.
pub const MAX_VERSION: &str = "TLSv1.2";

/// The suite the live retail handshake selected, and which a rehearsal is
/// expected to reproduce.
pub const EXPECTED_SUITE: &str = "AES256-GCM-SHA384";

#[cfg(test)]
mod tests {
    use super::*;

    /// The enabled list must be exactly the client's offer minus the refusals.
    /// Stated as a derivation so adding a suite to one constant without the
    /// other is a test failure rather than a silent divergence.
    #[test]
    fn the_enabled_list_is_the_offer_minus_the_refusals() {
        let offered: Vec<&str> = OBSERVED_CLIENT_SUITES.split(':').collect();
        let enabled: Vec<&str> = CIPHER_LIST.split(':').collect();
        let expected: Vec<&str> = offered
            .iter()
            .copied()
            .filter(|s| !REFUSED_SUITES.contains(s))
            .collect();
        assert_eq!(enabled, expected);
        assert_eq!(offered.len(), 8, "the capture showed eight suites");
        assert_eq!(enabled.len(), 6);
    }

    /// Every enabled suite must be static RSA. An ECDHE suite slipping in would
    /// be silently useless to this client, which offers none.
    #[test]
    fn nothing_forward_secret_is_enabled() {
        for s in CIPHER_LIST.split(':') {
            assert!(
                !s.contains("ECDHE") && !s.contains("DHE"),
                "{s} is forward-secret; FIFA 17 offers no such suite"
            );
        }
    }

    #[test]
    fn rc4_and_md5_are_refused_not_merely_absent() {
        for s in REFUSED_SUITES {
            assert!(
                OBSERVED_CLIENT_SUITES.contains(s),
                "{s} must be one the client actually offers"
            );
            assert!(!CIPHER_LIST.contains(s), "{s} must not be enabled");
        }
    }

    #[test]
    fn the_expected_suite_is_one_we_enable_and_the_client_offers() {
        assert!(CIPHER_LIST.split(':').any(|s| s == EXPECTED_SUITE));
        assert!(OBSERVED_CLIENT_SUITES
            .split(':')
            .any(|s| s == EXPECTED_SUITE));
        assert_eq!(
            CIPHER_LIST.split(':').next(),
            Some(EXPECTED_SUITE),
            "preference order should put the observed selection first"
        );
    }

    #[test]
    fn the_protocol_window_is_exactly_tls12() {
        assert_eq!(MIN_VERSION, "TLSv1.2");
        assert_eq!(MAX_VERSION, "TLSv1.2");
    }
}
