// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#[cfg(any(test, feature = "generator"))]
use bolero_generator::prelude::*;

//= https://www.rfc-editor.org/rfc/rfc3168#section-5
//# This document specifies that the Internet provide a congestion
//# indication for incipient congestion (as in RED and earlier work
//# [RJ90]) where the notification can sometimes be through marking
//# packets rather than dropping them.  This uses an ECN field in the IP
//# header with two bits, making four ECN codepoints, '00' to '11'.  The
//# ECN-Capable Transport (ECT) codepoints '10' and '01' are set by the
//# data sender to indicate that the end-points of the transport protocol
//# are ECN-capable; we call them ECT(0) and ECT(1) respectively.  The
//# phrase "the ECT codepoint" in this documents refers to either of the
//# two ECT codepoints.  Routers treat the ECT(0) and ECT(1) codepoints
//# as equivalent.  Senders are free to use either the ECT(0) or the
//# ECT(1) codepoint to indicate ECT, on a packet-by-packet basis.
//#
//# The use of both the two codepoints for ECT, ECT(0) and ECT(1), is
//# motivated primarily by the desire to allow mechanisms for the data
//# sender to verify that network elements are not erasing the CE
//# codepoint, and that data receivers are properly reporting to the
//# sender the receipt of packets with the CE codepoint set, as required
//# by the transport protocol.  Guidelines for the senders and receivers
//# to differentiate between the ECT(0) and ECT(1) codepoints will be
//# addressed in separate documents, for each transport protocol.  In
//# particular, this document does not address mechanisms for TCP end-
//# nodes to differentiate between the ECT(0) and ECT(1) codepoints.
//# Protocols and senders that only require a single ECT codepoint SHOULD
//# use ECT(0).
//#
//# The not-ECT codepoint '00' indicates a packet that is not using ECN.
//# The CE codepoint '11' is set by a router to indicate congestion to
//# the end nodes.  Routers that have a packet arriving at a full queue
//# drop the packet, just as they do in the absence of ECN.
//#
//#    +-----+-----+
//#    | ECN FIELD |
//#    +-----+-----+
//#      ECT   CE         [Obsolete] RFC 2481 names for the ECN bits.
//#       0     0         Not-ECT
//#       0     1         ECT(1)
//#       1     0         ECT(0)
//#       1     1         CE
//#
//#    Figure 1: The ECN Field in IP.

/// Explicit Congestion Notification
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(any(test, feature = "generator"), derive(TypeGenerator))]
#[cfg_attr(kani, derive(kani::Arbitrary))]
pub enum ExplicitCongestionNotification {
    /// The not-ECT codepoint '00' indicates a packet that is not using ECN.
    NotEct = 0b00,

    /// ECT(1) is set by the data sender to indicate that the end-points of the transport
    /// protocol are ECN-capable.
    Ect1 = 0b01,

    /// ECT(0) is set by the data sender to indicate that the end-points of the transport
    /// protocol are ECN-capable.
    /// Protocols and senders that only require a single ECT codepoint SHOULD use ECT(0).
    Ect0 = 0b10,

    /// The CE codepoint '11' is set by a router to indicate congestion to the end nodes.
    Ce = 0b11,
}

impl Default for ExplicitCongestionNotification {
    #[inline]
    fn default() -> Self {
        Self::NotEct
    }
}

impl ExplicitCongestionNotification {
    /// Create a ExplicitCongestionNotification from the ECN field in the IP header
    #[inline]
    pub fn new(ecn_field: u8) -> Self {
        match ecn_field & 0b11 {
            0b00 => ExplicitCongestionNotification::NotEct,
            0b01 => ExplicitCongestionNotification::Ect1,
            0b10 => ExplicitCongestionNotification::Ect0,
            0b11 => ExplicitCongestionNotification::Ce,
            _ => unreachable!(),
        }
    }

    /// Returns true if congestion was experienced by the peer
    #[inline]
    pub fn congestion_experienced(self) -> bool {
        self == Self::Ce
    }

    /// Returns true if ECN is in use
    #[inline]
    pub fn using_ecn(self) -> bool {
        self != Self::NotEct
    }
}

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

    #[test]
    fn new() {
        for ecn in &[
            ExplicitCongestionNotification::NotEct,
            ExplicitCongestionNotification::Ect1,
            ExplicitCongestionNotification::Ect0,
            ExplicitCongestionNotification::Ce,
        ] {
            assert_eq!(*ecn, ExplicitCongestionNotification::new(*ecn as u8));
        }
    }

    /// The most-significant 6 bits of the 8-bit traffic class field ECN markings are
    /// read from are used for the differentiated services code point. This test
    /// ensures we still parse ECN bits correctly even when the DSCP markings are present.
    #[test]
    fn dscp_markings() {
        for i in 0..u8::MAX {
            for ecn in &[
                ExplicitCongestionNotification::NotEct,
                ExplicitCongestionNotification::Ect1,
                ExplicitCongestionNotification::Ect0,
                ExplicitCongestionNotification::Ce,
            ] {
                assert_eq!(
                    *ecn,
                    ExplicitCongestionNotification::new((i << 2) | *ecn as u8)
                );
            }
        }
    }
}
