//! TLS example — in-process TLS 1.3 handshake using paired memory BIOs.
//!
//! Creates a self-signed Ed25519 certificate, configures client and server
//! `SslCtx` objects, and drives a full TLS 1.3 handshake in a single thread
//! using `BIO_new_bio_pair` (via `Bio::new_pair`).
//!
//! Run with: cargo run --example ssl -p native-ossl

use native_ossl::bio::Bio;
use native_ossl::pkey::KeygenCtx;
use native_ossl::ssl::{SslCtx, SslIoError, SslVerifyMode, TlsVersion};
use native_ossl::x509::{X509Builder, X509NameOwned};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ── Key pair and self-signed certificate ───────────────────────────────────

    let mut kgen = KeygenCtx::new(c"ED25519")?;
    let priv_key = kgen.generate()?;
    let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(priv_key.clone());

    let mut name = X509NameOwned::new()?;
    name.add_entry_by_txt(c"CN", b"localhost")?;

    let cert = X509Builder::new()?
        .set_version(2)?
        .set_serial_number(1)?
        .set_not_before_offset(0)?
        .set_not_after_offset(86400)?
        .set_subject_name(&name)?
        .set_issuer_name(&name)?
        .set_public_key(&pub_key)?
        .sign(&priv_key, None)?
        .build();

    // ── Server context ─────────────────────────────────────────────────────────

    let server_ctx = SslCtx::new_server()?;
    server_ctx.set_min_proto_version(TlsVersion::Tls13)?;
    server_ctx.use_certificate(&cert)?;
    server_ctx.use_private_key(&priv_key)?;
    server_ctx.check_private_key()?;

    // ── Client context ─────────────────────────────────────────────────────────

    let client_ctx = SslCtx::new_client()?;
    client_ctx.set_min_proto_version(TlsVersion::Tls13)?;
    // Skip certificate verification for this self-signed example.
    client_ctx.set_verify(SslVerifyMode::NONE);

    // ── SSL objects ────────────────────────────────────────────────────────────

    let mut client = client_ctx.new_ssl()?;
    let mut server = server_ctx.new_ssl()?;

    client.set_connect_state();
    client.set_hostname(c"localhost")?;
    server.set_accept_state();

    // ── In-process BIO pair ────────────────────────────────────────────────────
    // Data written to client_bio is readable from server_bio and vice-versa.

    let (client_bio, server_bio) = Bio::new_pair()?;
    client.set_bio_duplex(client_bio);
    server.set_bio_duplex(server_bio);

    // ── Drive the handshake ────────────────────────────────────────────────────
    // Alternate between client and server until both report success.

    let mut client_done = false;
    let mut server_done = false;

    for step in 0..20 {
        if !client_done {
            match client.connect() {
                Ok(()) => {
                    client_done = true;
                    println!("Client handshake done at step {step}");
                }
                Err(SslIoError::WantRead | SslIoError::WantWrite) => {}
                Err(e) => return Err(format!("client error: {e}").into()),
            }
        }
        if !server_done {
            match server.accept() {
                Ok(()) => {
                    server_done = true;
                    println!("Server handshake done at step {step}");
                }
                Err(SslIoError::WantRead | SslIoError::WantWrite) => {}
                Err(e) => return Err(format!("server error: {e}").into()),
            }
        }
        if client_done && server_done {
            break;
        }
    }
    assert!(client_done && server_done, "handshake did not complete");

    // ── Inspect the peer certificate on the client ─────────────────────────────

    if let Some(peer_cert) = client.peer_certificate() {
        if let Some(subject) = peer_cert.subject_name().to_string() {
            println!("Server cert subject: {subject}");
        }
    }

    // ── Application data exchange ──────────────────────────────────────────────

    let request = b"GET / HTTP/1.0\r\n\r\n";
    let response = b"HTTP/1.0 200 OK\r\n\r\nHello, TLS!";

    let written = client.write(request)?;
    assert_eq!(written, request.len());

    let mut rbuf = [0u8; 64];
    let n = server.read(&mut rbuf)?;
    assert_eq!(&rbuf[..n], request);
    println!("Server received: {:?}", std::str::from_utf8(&rbuf[..n])?);

    let written = server.write(response)?;
    assert_eq!(written, response.len());

    let n = client.read(&mut rbuf)?;
    assert_eq!(&rbuf[..n], response);
    println!("Client received: {:?}", std::str::from_utf8(&rbuf[..n])?);

    println!("In-process TLS 1.3 round-trip: OK");

    Ok(())
}
