/* Copyright (C) 2021 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

use super::parser::QuicType;
use super::quic::QuicTransaction;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use digest::Digest;
use digest::Update;
use md5::Md5;

fn quic_tls_extension_name(e: u16) -> Option<String> {
    match e {
        0 => Some("server_name".to_string()),
        1 => Some("max_fragment_length".to_string()),
        2 => Some("client_certificate_url".to_string()),
        3 => Some("trusted_ca_keys".to_string()),
        4 => Some("truncated_hmac".to_string()),
        5 => Some("status_request".to_string()),
        6 => Some("user_mapping".to_string()),
        7 => Some("client_authz".to_string()),
        8 => Some("server_authz".to_string()),
        9 => Some("cert_type".to_string()),
        10 => Some("supported_groups".to_string()),
        11 => Some("ec_point_formats".to_string()),
        12 => Some("srp".to_string()),
        13 => Some("signature_algorithms".to_string()),
        14 => Some("use_srtp".to_string()),
        15 => Some("heartbeat".to_string()),
        16 => Some("alpn".to_string()),
        17 => Some("status_request_v2".to_string()),
        18 => Some("signed_certificate_timestamp".to_string()),
        19 => Some("client_certificate_type".to_string()),
        20 => Some("server_certificate_type".to_string()),
        21 => Some("padding".to_string()),
        22 => Some("encrypt_then_mac".to_string()),
        23 => Some("extended_master_secret".to_string()),
        24 => Some("token_binding".to_string()),
        25 => Some("cached_info".to_string()),
        26 => Some("tls_lts".to_string()),
        27 => Some("compress_certificate".to_string()),
        28 => Some("record_size_limit".to_string()),
        29 => Some("pwd_protect".to_string()),
        30 => Some("pwd_clear".to_string()),
        31 => Some("password_salt".to_string()),
        32 => Some("ticket_pinning".to_string()),
        33 => Some("tls_cert_with_extern_psk".to_string()),
        34 => Some("delegated_credentials".to_string()),
        35 => Some("session_ticket".to_string()),
        36 => Some("tlmsp".to_string()),
        37 => Some("tlmsp_proxying".to_string()),
        38 => Some("tlmsp_delegate".to_string()),
        39 => Some("supported_ekt_ciphers".to_string()),
        41 => Some("pre_shared_key".to_string()),
        42 => Some("early_data".to_string()),
        43 => Some("supported_versions".to_string()),
        44 => Some("cookie".to_string()),
        45 => Some("psk_key_exchange_modes".to_string()),
        47 => Some("certificate_authorities".to_string()),
        48 => Some("oid_filters".to_string()),
        49 => Some("post_handshake_auth".to_string()),
        50 => Some("signature_algorithms_cert".to_string()),
        51 => Some("key_share".to_string()),
        52 => Some("transparency_info".to_string()),
        53 => Some("connection_id_deprecated".to_string()),
        54 => Some("connection_id".to_string()),
        55 => Some("external_id_hash".to_string()),
        56 => Some("external_session_id".to_string()),
        57 => Some("quic_transport_parameters".to_string()),
        58 => Some("ticket_request".to_string()),
        59 => Some("dnssec_chain".to_string()),
        13172 => Some("next_protocol_negotiation".to_string()),
        65281 => Some("renegotiation_info".to_string()),
        _ => None,
    }
}

fn log_template(tx: &QuicTransaction, log_ja4: bool, js: &mut JsonBuilder) -> Result<(), JsonError> {
    js.open_object("quic")?;
    if tx.header.ty != QuicType::Short {
        js.set_string("version", String::from(tx.header.version).as_str())?;

        if let Some(sni) = &tx.sni {
            js.set_string("sni", &String::from_utf8_lossy(sni))?;
        }
        if let Some(ua) = &tx.ua {
            js.set_string("ua", &String::from_utf8_lossy(ua))?;
        }
    }
    if !tx.cyu.is_empty() {
        js.open_array("cyu")?;
        for cyu in &tx.cyu {
            js.start_object()?;
            js.set_string("hash", &cyu.hash)?;
            js.set_string("string", &cyu.string)?;
            js.close()?;
        }
        js.close()?;
    }

    if let Some(ja3) = &tx.ja3 {
        if tx.client {
            js.open_object("ja3")?;
        } else {
            js.open_object("ja3s")?;
        }
        let hash = format!("{:x}", Md5::new().chain(ja3).finalize());
        js.set_string("hash", &hash)?;
        js.set_string("string", ja3)?;
        js.close()?;
    }

    if log_ja4 {
        if let Some(ref ja4) = &tx.ja4 {
            js.set_string("ja4", ja4)?;
        }
    }

    if !tx.extv.is_empty() {
        js.open_array("extensions")?;
        for e in &tx.extv {
            js.start_object()?;
            let etype = u16::from(e.etype);
            if let Some(s) = quic_tls_extension_name(etype) {
                js.set_string("name", &s)?;
            }
            js.set_uint("type", etype.into())?;

            if !e.values.is_empty() {
                js.open_array("values")?;
                for i in 0..e.values.len() {
                    js.append_string(&String::from_utf8_lossy(&e.values[i]))?;
                }
                js.close()?;
            }
            js.close()?;
        }
        js.close()?;
    }

    js.close()?;
    Ok(())
}

#[no_mangle]
pub unsafe extern "C" fn rs_quic_to_json(
    tx: *mut std::os::raw::c_void, log_ja4: bool, js: &mut JsonBuilder,
) -> bool {
    let tx = cast_pointer!(tx, QuicTransaction);
    log_template(tx, log_ja4, js).is_ok()
}
