// Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::fmt; use neqo_transport::StreamId; use sfv::{BareItem, Dictionary, Integer, Item, ListEntry, Parser}; use crate::{frames::HFrame, Error, Res}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Priority { urgency: u8, incremental: bool, } impl Default for Priority { fn default() -> Self { Self { urgency: 3, incremental: false, } } } impl Priority { /// # Panics /// /// If an invalid urgency (>7 is given) #[must_use] pub fn new(urgency: u8, incremental: bool) -> Self { assert!(urgency < 8); let priority = Self { urgency, incremental, }; #[cfg(feature = "build-fuzzing-corpus")] neqo_common::write_item_to_fuzzing_corpus("priority", priority.to_string().as_bytes()); priority } /// Constructs a priority from raw bytes (either a field value of frame content). /// /// # Errors /// /// When the contained syntax is invalid. pub fn from_bytes(bytes: &[u8]) -> Res { #[cfg(feature = "build-fuzzing-corpus")] neqo_common::write_item_to_fuzzing_corpus("priority", bytes); let dict: Dictionary = Parser::new(bytes).parse().map_err(|_| Error::HttpFrame)?; let urgency = match dict.get("u") { Some(ListEntry::Item(Item { bare_item: BareItem::Integer(u), .. })) if (Integer::constant(0)..=Integer::constant(7)).contains(u) => { u8::try_from(*u).map_err(|_| Error::Internal)? } _ => 3, }; let incremental = match dict.get("i") { Some(ListEntry::Item(Item { bare_item: BareItem::Boolean(i), .. })) => *i, _ => false, }; Ok(Self { urgency, incremental, }) } } impl fmt::Display for Priority { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self { urgency: 3, incremental: false, } => Ok(()), Self { urgency: 3, incremental: true, } => write!(f, "i"), Self { urgency, incremental: false, } => write!(f, "u={urgency}"), Self { urgency, incremental: true, } => write!(f, "u={urgency},i"), } } } #[derive(Debug)] pub struct PriorityHandler { push_stream: bool, priority: Priority, last_send_priority: Priority, } impl PriorityHandler { pub const fn new(push_stream: bool, priority: Priority) -> Self { Self { push_stream, priority, last_send_priority: priority, } } /*pub fn priority(&self) -> Priority { self.priority }*/ /// Returns if an priority update will be issued pub fn maybe_update_priority(&mut self, priority: Priority) -> bool { if priority == self.priority { false } else { self.priority = priority; true } } pub const fn priority_update_sent(&mut self) { self.last_send_priority = self.priority; } /// Returns `HFrame` if an priority update is outstanding pub fn maybe_encode_frame(&self, stream_id: StreamId) -> Option { if self.priority == self.last_send_priority { None } else if self.push_stream { Some(HFrame::PriorityUpdatePush { element_id: stream_id.as_u64(), priority: self.priority, }) } else { Some(HFrame::PriorityUpdateRequest { element_id: stream_id.as_u64(), priority: self.priority, }) } } } #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod test { use neqo_transport::StreamId; use crate::{priority::PriorityHandler, HFrame, Priority}; #[test] fn priority_updates_ignore_same() { let mut p = PriorityHandler::new(false, Priority::new(5, false)); assert!(!p.maybe_update_priority(Priority::new(5, false))); // updating with the same priority -> there should not be any priority frame sent assert!(p.maybe_encode_frame(StreamId::new(4)).is_none()); } #[test] fn priority_updates_send_update() { let mut p = PriorityHandler::new(false, Priority::new(5, false)); assert!(p.maybe_update_priority(Priority::new(6, false))); // updating with the a different priority -> there should be a priority frame sent assert!(p.maybe_encode_frame(StreamId::new(4)).is_some()); } #[test] fn multiple_priority_updates_ignore_same() { let mut p = PriorityHandler::new(false, Priority::new(5, false)); assert!(p.maybe_update_priority(Priority::new(6, false))); assert!(p.maybe_update_priority(Priority::new(5, false))); // initial and last priority same -> there should not be any priority frame sent assert!(p.maybe_encode_frame(StreamId::new(4)).is_none()); } #[test] fn multiple_priority_updates_send_update() { let mut p = PriorityHandler::new(false, Priority::new(5, false)); assert!(p.maybe_update_priority(Priority::new(6, false))); assert!(p.maybe_update_priority(Priority::new(7, false))); // updating two times with a different priority -> the last priority update should be in the // next frame let expected = HFrame::PriorityUpdateRequest { element_id: 4, priority: Priority::new(7, false), }; assert_eq!(p.maybe_encode_frame(StreamId::new(4)), Some(expected)); } #[test] fn priority_updates_incremental() { let mut p = PriorityHandler::new(false, Priority::new(5, false)); assert!(p.maybe_update_priority(Priority::new(5, true))); // updating the incremental parameter -> there should be a priority frame sent let expected = HFrame::PriorityUpdateRequest { element_id: 4, priority: Priority::new(5, true), }; assert_eq!(p.maybe_encode_frame(StreamId::new(4)), Some(expected)); } #[test] fn priority_update_sent_clears_pending() { let mut p = PriorityHandler::new(false, Priority::new(5, false)); assert!(p.maybe_update_priority(Priority::new(6, false))); assert!(p.maybe_encode_frame(StreamId::new(4)).is_some()); p.priority_update_sent(); // After sending, no more pending update. assert!(p.maybe_encode_frame(StreamId::new(4)).is_none()); } #[test] fn from_bytes_invalid_urgency_defaults() { // Urgency outside 0-7 should default to 3. let p = Priority::from_bytes(b"u=8").unwrap(); assert_eq!(p, Priority::default()); let p = Priority::from_bytes(b"u=-1").unwrap(); assert_eq!(p, Priority::default()); } #[test] fn priority_display_urgency_and_incremental() { assert_eq!(Priority::new(5, true).to_string(), "u=5,i"); } #[test] fn priority_update_push_stream() { let mut p = PriorityHandler::new(true, Priority::new(5, false)); assert!(p.maybe_update_priority(Priority::new(6, false))); let frame = p.maybe_encode_frame(StreamId::new(4)); assert!(matches!(frame, Some(HFrame::PriorityUpdatePush { .. }))); } }