use super::negotiation::{ext_type, Extension};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u8)]
pub enum Priority {
Critical = 0,
High = 1,
#[default]
Normal = 2,
Low = 3,
Background = 4,
}
impl Priority {
pub fn from_byte(b: u8) -> Option<Self> {
match b {
0 => Some(Priority::Critical),
1 => Some(Priority::High),
2 => Some(Priority::Normal),
3 => Some(Priority::Low),
4 => Some(Priority::Background),
_ => None,
}
}
pub fn to_byte(self) -> u8 {
self as u8
}
pub fn to_bitmask(self) -> u8 {
1 << (self as u8)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PrioritySupportBitmap(pub u8);
impl PrioritySupportBitmap {
pub const ALL: Self = Self(0b00011111);
pub const NORMAL_ONLY: Self = Self(0b00000100);
pub fn supports(&self, priority: Priority) -> bool {
(self.0 & priority.to_bitmask()) != 0
}
pub fn add(&mut self, priority: Priority) {
self.0 |= priority.to_bitmask();
}
pub fn remove(&mut self, priority: Priority) {
self.0 &= !priority.to_bitmask();
}
pub fn iter_supported(&self) -> impl Iterator<Item = Priority> + '_ {
[
Priority::Critical,
Priority::High,
Priority::Normal,
Priority::Low,
Priority::Background,
]
.into_iter()
.filter(|p| self.supports(*p))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PriorityConfig {
pub supported: PrioritySupportBitmap,
pub default: Priority,
}
impl Default for PriorityConfig {
fn default() -> Self {
Self {
supported: PrioritySupportBitmap::ALL,
default: Priority::Normal,
}
}
}
impl PriorityConfig {
pub fn all() -> Self {
Self::default()
}
pub fn minimal() -> Self {
Self {
supported: PrioritySupportBitmap::NORMAL_ONLY,
default: Priority::Normal,
}
}
pub const fn wire_size() -> usize {
2 }
pub fn to_extension(&self) -> Extension {
Extension::new(ext_type::PRIORITY, vec![self.supported.0, self.default.to_byte()])
}
pub fn from_extension(ext: &Extension) -> Option<Self> {
if ext.ext_type != ext_type::PRIORITY || ext.data.len() < 2 {
return None;
}
let default = Priority::from_byte(ext.data[1])?;
Some(Self {
supported: PrioritySupportBitmap(ext.data[0]),
default,
})
}
pub fn negotiate(client: &Self, server: &Self) -> Self {
let supported = PrioritySupportBitmap(client.supported.0 & server.supported.0);
let default = client.default.max(server.default); Self { supported, default }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_priority_ordering() {
assert!(Priority::Critical < Priority::High);
assert!(Priority::High < Priority::Normal);
assert!(Priority::Normal < Priority::Low);
assert!(Priority::Low < Priority::Background);
}
#[test]
fn test_priority_byte_roundtrip() {
for p in [
Priority::Critical,
Priority::High,
Priority::Normal,
Priority::Low,
Priority::Background,
] {
assert_eq!(Priority::from_byte(p.to_byte()), Some(p));
}
assert_eq!(Priority::from_byte(5), None);
assert_eq!(Priority::from_byte(255), None);
}
#[test]
fn test_bitmap_operations() {
let mut bitmap = PrioritySupportBitmap(0);
assert!(!bitmap.supports(Priority::Normal));
bitmap.add(Priority::Normal);
bitmap.add(Priority::High);
assert!(bitmap.supports(Priority::Normal));
assert!(bitmap.supports(Priority::High));
assert!(!bitmap.supports(Priority::Critical));
bitmap.remove(Priority::Normal);
assert!(!bitmap.supports(Priority::Normal));
assert!(bitmap.supports(Priority::High));
}
#[test]
fn test_bitmap_all() {
let bitmap = PrioritySupportBitmap::ALL;
assert!(bitmap.supports(Priority::Critical));
assert!(bitmap.supports(Priority::High));
assert!(bitmap.supports(Priority::Normal));
assert!(bitmap.supports(Priority::Low));
assert!(bitmap.supports(Priority::Background));
}
#[test]
fn test_config_extension_roundtrip() {
let config = PriorityConfig {
supported: PrioritySupportBitmap::ALL,
default: Priority::Low,
};
let ext = config.to_extension();
assert_eq!(ext.ext_type, ext_type::PRIORITY);
let decoded = PriorityConfig::from_extension(&ext).unwrap();
assert_eq!(decoded, config);
}
#[test]
fn test_negotiate() {
let client = PriorityConfig {
supported: PrioritySupportBitmap(0b00011111), default: Priority::Normal,
};
let server = PriorityConfig {
supported: PrioritySupportBitmap(0b00000111), default: Priority::High,
};
let result = PriorityConfig::negotiate(&client, &server);
assert!(result.supported.supports(Priority::Critical));
assert!(result.supported.supports(Priority::High));
assert!(result.supported.supports(Priority::Normal));
assert!(!result.supported.supports(Priority::Low));
assert!(!result.supported.supports(Priority::Background));
assert_eq!(result.default, Priority::Normal);
}
}