// Copyright (C) 2020, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

//! Module-defined Event and functions to work with qlog data structures.
//!
//! The Qlog data structures are very flexible, which makes working with them
//! liable to simple semantic errors. This module provides some helper functions
//! that focus on creating valid types, while also reducing some of the
//! verbosity of using the raw data structures.

use super::*;

/// A representation of qlog events that couple EventCategory, EventType and
/// EventData.
///
/// Functions are provided to help construct valid events. Most events consist
/// of several optional fields, so minimal versions of these functions are
/// provided, which accept only mandatory qlog parameters. Minimal functions are
/// identified by a `_min` suffix.
#[derive(Clone)]
pub struct Event {
    pub category: EventCategory,
    pub ty: EventType,
    pub data: EventData,
}

#[allow(clippy::too_many_arguments)]
impl Event {
    // Connectivity events.

    /// Returns:
    /// * `EventCategory`=`Connectivity`
    /// * `EventType`=`ConnectivityEventType::ServerListening`
    /// * `EventData`=`ServerListening`.
    pub fn server_listening(
        ip_v4: Option<String>, ip_v6: Option<String>, port_v4: u64, port_v6: u64,
        quic_versions: Option<Vec<String>>, alpn_values: Option<Vec<String>>,
        stateless_reset_required: Option<bool>,
    ) -> Self {
        Event {
            category: EventCategory::Connectivity,
            ty: EventType::ConnectivityEventType(
                ConnectivityEventType::ServerListening,
            ),
            data: EventData::ServerListening {
                ip_v4,
                ip_v6,
                port_v4,
                port_v6,
                quic_versions,
                alpn_values,
                stateless_reset_required,
            },
        }
    }

    pub fn server_listening_min(port_v4: u64, port_v6: u64) -> Self {
        Event::server_listening(None, None, port_v4, port_v6, None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Connectivity`
    /// * `EventType`=`ConnectivityEventType::ConnectionStarted`
    /// * `EventData`=`ConnectionStarted`.
    pub fn connection_started(
        ip_version: String, src_ip: String, dst_ip: String,
        protocol: Option<String>, src_port: u64, dst_port: u64,
        quic_version: Option<String>, src_cid: Option<String>,
        dst_cid: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Connectivity,
            ty: EventType::ConnectivityEventType(
                ConnectivityEventType::ConnectionStarted,
            ),
            data: EventData::ConnectionStarted {
                ip_version,
                src_ip,
                dst_ip,
                protocol,
                src_port,
                dst_port,
                quic_version,
                src_cid,
                dst_cid,
            },
        }
    }

    pub fn connection_started_min(
        ip_version: String, src_ip: String, dst_ip: String, src_port: u64,
        dst_port: u64,
    ) -> Self {
        Event::connection_started(
            ip_version, src_ip, dst_ip, None, src_port, dst_port, None, None,
            None,
        )
    }

    /// Returns:
    /// * `EventCategory`=`Connectivity`
    /// * `EventType`=`ConnectivityEventType::ConnectionIdUpdated`
    /// * `EventData`=`ConnectionIdUpdated`.
    pub fn connection_id_updated(
        src_old: Option<String>, src_new: Option<String>,
        dst_old: Option<String>, dst_new: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Connectivity,
            ty: EventType::ConnectivityEventType(
                ConnectivityEventType::ConnectionIdUpdated,
            ),
            data: EventData::ConnectionIdUpdated {
                src_old,
                src_new,
                dst_old,
                dst_new,
            },
        }
    }

    pub fn connection_id_updated_min() -> Self {
        Event::connection_id_updated(None, None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Connectivity`
    /// * `EventType`=`ConnectivityEventType::SpinBitUpdated`
    /// * `EventData`=`SpinBitUpdated`.
    pub fn spinbit_updated(state: bool) -> Self {
        Event {
            category: EventCategory::Connectivity,
            ty: EventType::ConnectivityEventType(
                ConnectivityEventType::SpinBitUpdated,
            ),
            data: EventData::SpinBitUpdated { state },
        }
    }

    /// Returns:
    /// * `EventCategory`=`Connectivity`
    /// * `EventType`=`ConnectivityEventType::ConnectionState`
    /// * `EventData`=`ConnectionState`.
    pub fn connection_state_updated(
        old: Option<ConnectionState>, new: ConnectionState,
    ) -> Self {
        Event {
            category: EventCategory::Connectivity,
            ty: EventType::ConnectivityEventType(
                ConnectivityEventType::ConnectionStateUpdated,
            ),
            data: EventData::ConnectionStateUpdated { old, new },
        }
    }

    pub fn connection_state_updated_min(new: ConnectionState) -> Self {
        Event::connection_state_updated(None, new)
    }

    // Transport events.

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::ParametersSet`
    /// * `EventData`=`ParametersSet`.
    pub fn transport_parameters_set(
        owner: Option<TransportOwner>, resumption_allowed: Option<bool>,
        early_data_enabled: Option<bool>, alpn: Option<String>,
        version: Option<String>, tls_cipher: Option<String>,
        original_connection_id: Option<String>,
        stateless_reset_token: Option<String>,
        disable_active_migration: Option<bool>, idle_timeout: Option<u64>,
        max_packet_size: Option<u64>, ack_delay_exponent: Option<u64>,
        max_ack_delay: Option<u64>, active_connection_id_limit: Option<u64>,
        initial_max_data: Option<String>,
        initial_max_stream_data_bidi_local: Option<String>,
        initial_max_stream_data_bidi_remote: Option<String>,
        initial_max_stream_data_uni: Option<String>,
        initial_max_streams_bidi: Option<String>,
        initial_max_streams_uni: Option<String>,
        preferred_address: Option<PreferredAddress>,
    ) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(TransportEventType::ParametersSet),
            data: EventData::TransportParametersSet {
                owner,

                resumption_allowed,
                early_data_enabled,
                alpn,
                version,
                tls_cipher,

                original_connection_id,
                stateless_reset_token,
                disable_active_migration,

                idle_timeout,
                max_packet_size,
                ack_delay_exponent,
                max_ack_delay,
                active_connection_id_limit,

                initial_max_data,
                initial_max_stream_data_bidi_local,
                initial_max_stream_data_bidi_remote,
                initial_max_stream_data_uni,
                initial_max_streams_bidi,
                initial_max_streams_uni,

                preferred_address,
            },
        }
    }

    pub fn transport_parameters_set_min() -> Self {
        Event::transport_parameters_set(
            None, None, None, None, None, None, None, None, None, None, None,
            None, None, None, None, None, None, None, None, None, None,
        )
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::DatagramsReceived`
    /// * `EventData`=`DatagramsReceived`.
    pub fn datagrams_received(
        count: Option<u64>, byte_length: Option<u64>,
    ) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(
                TransportEventType::DatagramsReceived,
            ),
            data: EventData::DatagramsReceived { count, byte_length },
        }
    }

    pub fn datagrams_received_min() -> Self {
        Event::datagrams_received(None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::DatagramsSent`
    /// * `EventData`=`DatagramsSent`.
    pub fn datagrams_sent(count: Option<u64>, byte_length: Option<u64>) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(TransportEventType::DatagramsSent),
            data: EventData::DatagramsSent { count, byte_length },
        }
    }

    pub fn datagrams_sent_min() -> Self {
        Event::datagrams_sent(None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::DatagramDropped`
    /// * `EventData`=`DatagramDropped`.
    pub fn datagram_dropped(byte_length: Option<u64>) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(
                TransportEventType::DatagramDropped,
            ),
            data: EventData::DatagramDropped { byte_length },
        }
    }

    pub fn datagram_dropped_min() -> Self {
        Event::datagram_dropped(None)
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::PacketReceived`
    /// * `EventData`=`PacketReceived`.
    pub fn packet_received(
        packet_type: PacketType, header: PacketHeader,
        frames: Option<Vec<QuicFrame>>, is_coalesced: Option<bool>,
        raw_encrypted: Option<String>, raw_decrypted: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(TransportEventType::PacketReceived),
            data: EventData::PacketReceived {
                packet_type,
                header,
                frames,

                is_coalesced,

                raw_encrypted,
                raw_decrypted,
            },
        }
    }

    pub fn packet_received_min(
        packet_type: PacketType, header: PacketHeader,
    ) -> Self {
        Event::packet_received(packet_type, header, None, None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::PacketSent`
    /// * `EventData`=`PacketSent`.
    pub fn packet_sent(
        packet_type: PacketType, header: PacketHeader,
        frames: Option<Vec<QuicFrame>>, is_coalesced: Option<bool>,
        raw_encrypted: Option<String>, raw_decrypted: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(TransportEventType::PacketSent),
            data: EventData::PacketSent {
                packet_type,
                header,
                frames,

                is_coalesced,

                raw_encrypted,
                raw_decrypted,
            },
        }
    }

    pub fn packet_sent_min(
        packet_type: PacketType, header: PacketHeader,
        frames: Option<Vec<QuicFrame>>,
    ) -> Self {
        Event::packet_sent(packet_type, header, frames, None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::PacketDropped`
    /// * `EventData`=`PacketDropped`.
    pub fn packet_dropped(
        packet_type: Option<PacketType>, packet_size: Option<u64>,
        raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(TransportEventType::PacketDropped),
            data: EventData::PacketDropped {
                packet_type,
                packet_size,
                raw,
            },
        }
    }

    pub fn packet_dropped_min() -> Self {
        Event::packet_dropped(None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::PacketBuffered`
    /// * `EventData`=`PacketBuffered`.
    pub fn packet_buffered(
        packet_type: PacketType, packet_number: String,
    ) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(TransportEventType::PacketBuffered),
            data: EventData::PacketBuffered {
                packet_type,
                packet_number,
            },
        }
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::StreamStateUpdated`
    /// * `EventData`=`StreamStateUpdated`.
    pub fn stream_state_updated(
        stream_id: String, stream_type: Option<StreamType>,
        old: Option<StreamState>, new: StreamState,
        stream_side: Option<StreamSide>,
    ) -> Self {
        Event {
            category: EventCategory::Connectivity,
            ty: EventType::TransportEventType(
                TransportEventType::StreamStateUpdated,
            ),
            data: EventData::StreamStateUpdated {
                stream_id,
                stream_type,
                old,
                new,
                stream_side,
            },
        }
    }

    pub fn stream_state_updated_min(stream_id: String, new: StreamState) -> Self {
        Event::stream_state_updated(stream_id, None, None, new, None)
    }

    /// Returns:
    /// * `EventCategory`=`Transport`
    /// * `EventType`=`TransportEventType::FramesProcessed`
    /// * `EventData`=`FramesProcessed`.
    pub fn frames_processed(frames: Vec<QuicFrame>) -> Self {
        Event {
            category: EventCategory::Transport,
            ty: EventType::TransportEventType(
                TransportEventType::FramesProcessed,
            ),
            data: EventData::FramesProcessed { frames },
        }
    }

    // Recovery events.

    /// Returns:
    /// * `EventCategory`=`Recovery`
    /// * `EventType`=`RecoveryEventType::ParametersSet`
    /// * `EventData`=`RecoveryParametersSet`.
    pub fn recovery_parameters_set(
        reordering_threshold: Option<u64>, time_threshold: Option<u64>,
        timer_granularity: Option<u64>, initial_rtt: Option<u64>,
        max_datagram_size: Option<u64>, initial_congestion_window: Option<u64>,
        minimum_congestion_window: Option<u64>,
        loss_reduction_factor: Option<u64>,
        persistent_congestion_threshold: Option<u64>,
    ) -> Self {
        Event {
            category: EventCategory::Recovery,
            ty: EventType::RecoveryEventType(RecoveryEventType::ParametersSet),
            data: EventData::RecoveryParametersSet {
                reordering_threshold,
                time_threshold,
                timer_granularity,
                initial_rtt,
                max_datagram_size,
                initial_congestion_window,
                minimum_congestion_window,
                loss_reduction_factor,
                persistent_congestion_threshold,
            },
        }
    }

    pub fn recovery_parameters_set_min() -> Self {
        Event::recovery_parameters_set(
            None, None, None, None, None, None, None, None, None,
        )
    }

    /// Returns:
    /// * `EventCategory`=`Recovery`
    /// * `EventType`=`RecoveryEventType::MetricsUpdated`
    /// * `EventData`=`MetricsUpdated`.
    pub fn metrics_updated(
        min_rtt: Option<u64>, smoothed_rtt: Option<u64>, latest_rtt: Option<u64>,
        rtt_variance: Option<u64>, max_ack_delay: Option<u64>,
        pto_count: Option<u64>, congestion_window: Option<u64>,
        bytes_in_flight: Option<u64>, ssthresh: Option<u64>,
        packets_in_flight: Option<u64>, in_recovery: Option<bool>,
        pacing_rate: Option<u64>,
    ) -> Self {
        Event {
            category: EventCategory::Recovery,
            ty: EventType::RecoveryEventType(RecoveryEventType::MetricsUpdated),
            data: EventData::MetricsUpdated {
                min_rtt,
                smoothed_rtt,
                latest_rtt,
                rtt_variance,
                max_ack_delay,
                pto_count,
                congestion_window,
                bytes_in_flight,
                ssthresh,
                packets_in_flight,
                in_recovery,
                pacing_rate,
            },
        }
    }

    pub fn metrics_updated_min() -> Self {
        Event::metrics_updated(
            None, None, None, None, None, None, None, None, None, None, None,
            None,
        )
    }

    /// Returns:
    /// * `EventCategory`=`Recovery`
    /// * `EventType`=`RecoveryEventType::CongestionStateUpdated`
    /// * `EventData`=`CongestionStateUpdated`.
    pub fn congestion_state_updated(old: Option<String>, new: String) -> Self {
        Event {
            category: EventCategory::Recovery,
            ty: EventType::RecoveryEventType(
                RecoveryEventType::CongestionStateUpdated,
            ),
            data: EventData::CongestionStateUpdated { old, new },
        }
    }

    pub fn congestion_state_updated_min(new: String) -> Self {
        Event::congestion_state_updated(None, new)
    }

    /// Returns:
    /// * `EventCategory`=`Recovery`
    /// * `EventType`=`RecoveryEventType::LossTimerSet`
    /// * `EventData`=`LossTimerSet`.
    pub fn loss_timer_set(
        timer_type: Option<TimerType>, timeout: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Recovery,
            ty: EventType::RecoveryEventType(RecoveryEventType::LossTimerSet),
            data: EventData::LossTimerSet {
                timer_type,
                timeout,
            },
        }
    }

    pub fn loss_timer_set_min() -> Self {
        Event::loss_timer_set(None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Recovery`
    /// * `EventType`=`RecoveryEventType::PacketLost`
    /// * `EventData`=`PacketLost`.
    pub fn packet_lost(
        packet_type: PacketType, packet_number: String,
        header: Option<PacketHeader>, frames: Vec<QuicFrame>,
    ) -> Self {
        Event {
            category: EventCategory::Recovery,
            ty: EventType::RecoveryEventType(RecoveryEventType::PacketLost),
            data: EventData::PacketLost {
                packet_type,
                packet_number,
                header,
                frames,
            },
        }
    }

    pub fn packet_lost_min(
        packet_type: PacketType, packet_number: String, frames: Vec<QuicFrame>,
    ) -> Self {
        Event::packet_lost(packet_type, packet_number, None, frames)
    }

    /// Returns:
    /// * `EventCategory`=`Recovery`
    /// * `EventType`=`RecoveryEventType::MarkedForRetransmit`
    /// * `EventData`=`MarkedForRetransmit`.
    pub fn marked_for_retransmit(frames: Vec<QuicFrame>) -> Self {
        Event {
            category: EventCategory::Recovery,
            ty: EventType::RecoveryEventType(
                RecoveryEventType::MarkedForRetransmit,
            ),
            data: EventData::MarkedForRetransmit { frames },
        }
    }

    // HTTP/3 events.

    /// Returns:
    /// * `EventCategory`=`Http`
    /// * `EventType`=`Http3EventType::ParametersSet`
    /// * `EventData`=`H3ParametersSet`.
    pub fn h3_parameters_set(
        owner: Option<H3Owner>, max_header_list_size: Option<u64>,
        max_table_capacity: Option<u64>, blocked_streams_count: Option<u64>,
        push_allowed: Option<bool>, waits_for_settings: Option<bool>,
    ) -> Self {
        Event {
            category: EventCategory::Http,
            ty: EventType::Http3EventType(Http3EventType::ParametersSet),
            data: EventData::H3ParametersSet {
                owner,
                max_header_list_size,
                max_table_capacity,
                blocked_streams_count,
                push_allowed,
                waits_for_settings,
            },
        }
    }

    pub fn h3_parameters_set_min() -> Self {
        Event::h3_parameters_set(None, None, None, None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Http`
    /// * `EventType`=`Http3EventType::StreamTypeSet`
    /// * `EventData`=`H3StreamTypeSet`.
    pub fn h3_stream_type_set(
        stream_id: String, owner: Option<H3Owner>, old: Option<H3StreamType>,
        new: H3StreamType,
    ) -> Self {
        Event {
            category: EventCategory::Http,
            ty: EventType::Http3EventType(Http3EventType::StreamTypeSet),
            data: EventData::H3StreamTypeSet {
                stream_id,
                owner,
                old,
                new,
            },
        }
    }

    pub fn h3_stream_type_set_min(stream_id: String, new: H3StreamType) -> Self {
        Event::h3_stream_type_set(stream_id, None, None, new)
    }

    /// Returns:
    /// * `EventCategory`=`Http`
    /// * `EventType`=`Http3EventType::FrameCreated`
    /// * `EventData`=`H3FrameCreated`.
    pub fn h3_frame_created(
        stream_id: String, frame: Http3Frame, byte_length: Option<String>,
        raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Http,
            ty: EventType::Http3EventType(Http3EventType::FrameCreated),
            data: EventData::H3FrameCreated {
                stream_id,
                frame,
                byte_length,
                raw,
            },
        }
    }

    pub fn h3_frame_created_min(stream_id: String, frame: Http3Frame) -> Self {
        Event::h3_frame_created(stream_id, frame, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Http`
    /// * `EventType`=`Http3EventType::FrameParsed`
    /// * `EventData`=`H3FrameParsed`.
    pub fn h3_frame_parsed(
        stream_id: String, frame: Http3Frame, byte_length: Option<String>,
        raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Http,
            ty: EventType::Http3EventType(Http3EventType::FrameParsed),
            data: EventData::H3FrameParsed {
                stream_id,
                frame,
                byte_length,
                raw,
            },
        }
    }

    pub fn h3_frame_parsed_min(stream_id: String, frame: Http3Frame) -> Self {
        Event::h3_frame_parsed(stream_id, frame, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Http`
    /// * `EventType`=`Http3EventType::DataMoved`
    /// * `EventData`=`H3DataMoved`.
    pub fn h3_data_moved(
        stream_id: String, offset: Option<String>, length: Option<u64>,
        from: Option<H3DataRecipient>, to: Option<H3DataRecipient>,
        raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Http,
            ty: EventType::Http3EventType(Http3EventType::DataMoved),
            data: EventData::H3DataMoved {
                stream_id,
                offset,
                length,
                from,
                to,
                raw,
            },
        }
    }

    pub fn h3_data_moved_min(stream_id: String) -> Self {
        Event::h3_data_moved(stream_id, None, None, None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Http`
    /// * `EventType`=`Http3EventType::PushResolved`
    /// * `EventData`=`H3PushResolved`.
    pub fn h3_push_resolved(
        push_id: Option<String>, stream_id: Option<String>,
        decision: Option<H3PushDecision>,
    ) -> Self {
        Event {
            category: EventCategory::Http,
            ty: EventType::Http3EventType(Http3EventType::PushResolved),
            data: EventData::H3PushResolved {
                push_id,
                stream_id,
                decision,
            },
        }
    }

    pub fn h3_push_resolved_min() -> Self {
        Event::h3_push_resolved(None, None, None)
    }

    // QPACK events.

    /// Returns:
    /// * `EventCategory`=`Qpack`
    /// * `EventType`=`QpackEventType::StateUpdated`
    /// * `EventData`=`QpackStateUpdated`.
    pub fn qpack_state_updated(
        owner: Option<QpackOwner>, dynamic_table_capacity: Option<u64>,
        dynamic_table_size: Option<u64>, known_received_count: Option<u64>,
        current_insert_count: Option<u64>,
    ) -> Self {
        Event {
            category: EventCategory::Qpack,
            ty: EventType::QpackEventType(QpackEventType::StateUpdated),
            data: EventData::QpackStateUpdated {
                owner,
                dynamic_table_capacity,
                dynamic_table_size,
                known_received_count,
                current_insert_count,
            },
        }
    }

    pub fn qpack_state_updated_min() -> Self {
        Event::qpack_state_updated(None, None, None, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Qpack`
    /// * `EventType`=`QpackEventType::StreamStateUpdated`
    /// * `EventData`=`QpackStreamStateUpdated`.
    pub fn qpack_stream_state_updated(
        stream_id: String, state: QpackStreamState,
    ) -> Self {
        Event {
            category: EventCategory::Qpack,
            ty: EventType::QpackEventType(QpackEventType::StreamStateUpdated),
            data: EventData::QpackStreamStateUpdated { stream_id, state },
        }
    }

    /// Returns:
    /// * `EventCategory`=`Qpack`
    /// * `EventType`=`QpackEventType::DynamicTableUpdated`
    /// * `EventData`=`QpackDynamicTableUpdated`.
    pub fn qpack_dynamic_table_updated(
        update_type: QpackUpdateType, entries: Vec<QpackDynamicTableEntry>,
    ) -> Self {
        Event {
            category: EventCategory::Qpack,
            ty: EventType::QpackEventType(QpackEventType::DynamicTableUpdated),
            data: EventData::QpackDynamicTableUpdated {
                update_type,
                entries,
            },
        }
    }

    /// Returns:
    /// * `EventCategory`=`Qpack`
    /// * `EventType`=`QpackEventType::HeadersEncoded`
    /// * `EventData`=`QpackHeadersEncoded`.
    pub fn qpack_headers_encoded(
        stream_id: Option<String>, headers: Option<HttpHeader>,
        block_prefix: QpackHeaderBlockPrefix,
        header_block: Vec<QpackHeaderBlockRepresentation>, raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Qpack,
            ty: EventType::QpackEventType(QpackEventType::HeadersEncoded),
            data: EventData::QpackHeadersEncoded {
                stream_id,
                headers,
                block_prefix,
                header_block,
                raw,
            },
        }
    }

    pub fn qpack_headers_encoded_min(
        block_prefix: QpackHeaderBlockPrefix,
        header_block: Vec<QpackHeaderBlockRepresentation>,
    ) -> Self {
        Event::qpack_headers_encoded(None, None, block_prefix, header_block, None)
    }

    /// Returns:
    /// * `EventCategory`=`Qpack`
    /// * `EventType`=`QpackEventType::HeadersDecoded`
    /// * `EventData`=`QpackHeadersDecoded`.
    pub fn qpack_headers_decoded(
        stream_id: Option<String>, headers: Option<HttpHeader>,
        block_prefix: QpackHeaderBlockPrefix,
        header_block: Vec<QpackHeaderBlockRepresentation>, raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Qpack,
            ty: EventType::QpackEventType(QpackEventType::HeadersDecoded),
            data: EventData::QpackHeadersDecoded {
                stream_id,
                headers,
                block_prefix,
                header_block,
                raw,
            },
        }
    }

    pub fn qpack_headers_decoded_min(
        block_prefix: QpackHeaderBlockPrefix,
        header_block: Vec<QpackHeaderBlockRepresentation>,
    ) -> Self {
        Event::qpack_headers_decoded(None, None, block_prefix, header_block, None)
    }

    /// Returns:
    /// * `EventCategory`=`Qpack`
    /// * `EventType`=`QpackEventType::InstructionSent`
    /// * `EventData`=`QpackInstructionSent`.
    pub fn qpack_instruction_sent(
        instruction: QPackInstruction, byte_length: Option<String>,
        raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Qpack,
            ty: EventType::QpackEventType(QpackEventType::InstructionSent),
            data: EventData::QpackInstructionSent {
                instruction,
                byte_length,
                raw,
            },
        }
    }

    pub fn qpack_instruction_sent_min(instruction: QPackInstruction) -> Self {
        Event::qpack_instruction_sent(instruction, None, None)
    }

    /// Returns:
    /// * `EventCategory`=`Qpack`
    /// * `EventType`=`QpackEventType::InstructionReceived`
    /// * `EventData`=`QpackInstructionReceived`.
    pub fn qpack_instruction_received(
        instruction: QPackInstruction, byte_length: Option<String>,
        raw: Option<String>,
    ) -> Self {
        Event {
            category: EventCategory::Qpack,
            ty: EventType::QpackEventType(QpackEventType::InstructionReceived),
            data: EventData::QpackInstructionReceived {
                instruction,
                byte_length,
                raw,
            },
        }
    }

    pub fn qpack_instruction_received_min(instruction: QPackInstruction) -> Self {
        Event::qpack_instruction_received(instruction, None, None)
    }

    /// Checks if the the combination of `EventCategory`, `EventType` and
    /// `EventData` is valid.
    pub fn is_valid(&self) -> bool {
        match (&self.category, &self.ty) {
            (
                EventCategory::Connectivity,
                EventType::ConnectivityEventType(_),
            ) => matches!(&self.data,
                EventData::ServerListening { .. } |
                EventData::ConnectionStarted { .. } |
                EventData::ConnectionIdUpdated { .. } |
                EventData::SpinBitUpdated { .. } |
                EventData::ConnectionStateUpdated { .. }),

            (EventCategory::Transport, EventType::TransportEventType(_)) =>
                matches!(&self.data,
                    EventData::TransportParametersSet { .. } |
                    EventData::DatagramsReceived { .. } |
                    EventData::DatagramsSent { .. } |
                    EventData::DatagramDropped { .. } |
                    EventData::PacketReceived { .. } |
                    EventData::PacketSent { .. } |
                    EventData::PacketDropped { .. } |
                    EventData::PacketBuffered { .. } |
                    EventData::StreamStateUpdated { .. } |
                    EventData::FramesProcessed { .. }),

            (EventCategory::Security, EventType::SecurityEventType(_)) =>
                matches!(&self.data,
                    EventData::KeyUpdated { .. } |
                    EventData::KeyRetired { .. }),

            (EventCategory::Recovery, EventType::RecoveryEventType(_)) =>
                matches!(&self.data,
                    EventData::RecoveryParametersSet { .. } |
                    EventData::MetricsUpdated { .. } |
                    EventData::CongestionStateUpdated { .. } |
                    EventData::LossTimerSet { .. } |
                    EventData::PacketLost { .. } |
                    EventData::MarkedForRetransmit { .. }),

            (EventCategory::Http, EventType::Http3EventType(_)) =>
                matches!(&self.data,
                    EventData::H3ParametersSet { .. } |
                    EventData::H3StreamTypeSet { .. } |
                    EventData::H3FrameCreated { .. } |
                    EventData::H3FrameParsed { .. } |
                    EventData::H3DataMoved { .. } |
                    EventData::H3PushResolved { .. }),

            (EventCategory::Qpack, EventType::QpackEventType(_)) =>
                matches!(&self.data,
                    EventData::QpackStateUpdated { .. } |
                    EventData::QpackStreamStateUpdated { .. } |
                    EventData::QpackDynamicTableUpdated { .. } |
                    EventData::QpackHeadersEncoded { .. } |
                    EventData::QpackHeadersDecoded { .. } |
                    EventData::QpackInstructionSent { .. } |
                    EventData::QpackInstructionReceived { .. }),

            // TODO: in qlog-01 there is no sane default category defined for
            // GenericEventType
            (_, EventType::GenericEventType(_)) => matches!(&self.data,
                EventData::ConnectionError { .. } |
                EventData::ApplicationError { .. } |
                EventData::InternalError { .. } |
                EventData::InternalWarning { .. } |
                EventData::Message { .. } |
                EventData::Marker { .. }),

            _ => false,
        }
    }
}
