use core::fmt;
use core::ops::BitOr;
use crate::access_mode::AccessMode;
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) struct MessageFlags(u8);
impl MessageFlags {
pub(crate) const fn new(byte: u8) -> Self {
Self(byte)
}
pub(crate) const fn get(self) -> u8 {
self.0
}
pub(crate) const fn is_constant(self) -> bool {
self.contains(Self::CONSTANT)
}
pub(crate) const fn is_shared(self) -> bool {
self.contains(Self::SHARED)
}
pub(crate) const fn forbids_sharing(self) -> bool {
self.contains(Self::FORBID_SHARING)
}
pub(crate) const fn fails_if_unknown_and_open_for_write(self) -> bool {
self.contains(Self::FAIL_IF_UNKNOWN_AND_OPEN_FOR_WRITE)
}
pub(crate) const fn marks_if_unknown(self) -> bool {
self.contains(Self::MARK_IF_UNKNOWN)
}
pub(crate) const fn was_unknown(self) -> bool {
self.contains(Self::WAS_UNKNOWN)
}
pub(crate) const fn is_shareable(self) -> bool {
self.contains(Self::SHAREABLE)
}
pub(crate) const fn fails_if_unknown_always(self) -> bool {
self.contains(Self::FAIL_IF_UNKNOWN_ALWAYS)
}
pub(crate) const fn must_be_understood(self, access_mode: AccessMode) -> bool {
self.fails_if_unknown_always()
|| (self.fails_if_unknown_and_open_for_write() && access_mode.is_read_write())
}
pub(crate) const fn is_empty(self) -> bool {
self.0 == 0
}
pub(crate) const fn difference(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub(crate) const NONE: Self = Self(0x00);
pub(crate) const CONSTANT: Self = Self(0x01);
pub(crate) const SHARED: Self = Self(0x02);
pub(crate) const FORBID_SHARING: Self = Self(0x04);
pub(crate) const FAIL_IF_UNKNOWN_AND_OPEN_FOR_WRITE: Self = Self(0x08);
pub(crate) const MARK_IF_UNKNOWN: Self = Self(0x10);
pub(crate) const WAS_UNKNOWN: Self = Self(0x20);
pub(crate) const SHAREABLE: Self = Self(0x40);
pub(crate) const FAIL_IF_UNKNOWN_ALWAYS: Self = Self(0x80);
}
impl BitOr for MessageFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl fmt::Debug for MessageFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let named = [
(self.is_constant(), "CONSTANT"),
(self.is_shared(), "SHARED"),
(self.forbids_sharing(), "FORBID_SHARING"),
(
self.fails_if_unknown_and_open_for_write(),
"FAIL_IF_UNKNOWN_AND_OPEN_FOR_WRITE",
),
(self.marks_if_unknown(), "MARK_IF_UNKNOWN"),
(self.was_unknown(), "WAS_UNKNOWN"),
(self.is_shareable(), "SHAREABLE"),
(self.fails_if_unknown_always(), "FAIL_IF_UNKNOWN_ALWAYS"),
];
f.write_str("MessageFlags(")?;
let mut separator = "";
for name in named.iter().filter_map(|&(set, name)| set.then_some(name)) {
f.write_str(separator)?;
f.write_str(name)?;
separator = " | ";
}
f.write_str(")")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_bit_of_every_flag_is_the_one_the_specification_gives() {
assert_eq!(MessageFlags::NONE.get(), 0x00);
assert_eq!(MessageFlags::CONSTANT.get(), 0x01);
assert_eq!(MessageFlags::SHARED.get(), 0x02);
assert_eq!(MessageFlags::FORBID_SHARING.get(), 0x04);
assert_eq!(MessageFlags::FAIL_IF_UNKNOWN_AND_OPEN_FOR_WRITE.get(), 0x08);
assert_eq!(MessageFlags::MARK_IF_UNKNOWN.get(), 0x10);
assert_eq!(MessageFlags::WAS_UNKNOWN.get(), 0x20);
assert_eq!(MessageFlags::SHAREABLE.get(), 0x40);
assert_eq!(MessageFlags::FAIL_IF_UNKNOWN_ALWAYS.get(), 0x80);
}
#[test]
fn a_flags_byte_survives_the_round_trip() {
assert_eq!(MessageFlags::new(0x93).get(), 0x93);
}
#[test]
fn an_empty_flags_byte_sets_no_flag() {
assert!(MessageFlags::NONE.is_empty());
assert_eq!(flags_set_in(MessageFlags::NONE), Vec::new());
}
#[test]
fn each_bit_sets_the_one_flag_it_stands_for() {
for bit in BITS {
assert_eq!(flags_set_in(bit), vec![bit]);
}
}
#[test]
fn a_byte_with_two_bits_sets_both_of_their_flags() {
let flags = MessageFlags::CONSTANT | MessageFlags::FORBID_SHARING;
assert!(!flags.is_empty());
assert!(flags.is_constant());
assert!(flags.forbids_sharing());
assert!(!flags.is_shared());
}
#[test]
fn every_bit_of_a_byte_stands_for_a_flag() {
let all = BITS
.into_iter()
.fold(MessageFlags::NONE, |all, bit| all | bit);
assert_eq!(all.get(), u8::MAX);
assert_eq!(flags_set_in(all), BITS.to_vec());
}
#[test]
fn a_difference_clears_the_flags_it_names_and_keeps_the_rest() {
let flags = MessageFlags::new(u8::MAX).difference(MessageFlags::SHARED);
assert_eq!(flags.get(), 0xFD);
assert!(
MessageFlags::SHARED
.difference(MessageFlags::SHARED)
.is_empty()
);
}
#[test]
fn debug_names_every_flag_a_byte_sets() {
assert_eq!(
format!("{:?}", MessageFlags::new(0x06)),
"MessageFlags(SHARED | FORBID_SHARING)"
);
}
#[test]
fn debug_names_no_flag_for_an_empty_flags_byte() {
assert_eq!(format!("{:?}", MessageFlags::NONE), "MessageFlags()");
}
#[test]
fn only_write_access_must_understand_a_message_flagged_fail_if_unknown_and_open_for_write() {
let flags = MessageFlags::FAIL_IF_UNKNOWN_AND_OPEN_FOR_WRITE;
assert!(!flags.must_be_understood(AccessMode::ReadOnly));
assert!(flags.must_be_understood(AccessMode::ReadWrite));
}
#[test]
fn either_access_must_understand_a_message_flagged_fail_if_unknown_always() {
let flags = MessageFlags::FAIL_IF_UNKNOWN_ALWAYS;
assert!(flags.must_be_understood(AccessMode::ReadOnly));
assert!(flags.must_be_understood(AccessMode::ReadWrite));
}
#[test]
fn either_access_reads_past_a_message_flagged_with_no_fail_if_unknown_bit() {
let flags = MessageFlags::CONSTANT | MessageFlags::SHAREABLE;
assert!(!flags.must_be_understood(AccessMode::ReadOnly));
assert!(!flags.must_be_understood(AccessMode::ReadWrite));
}
fn flags_set_in(flags: MessageFlags) -> Vec<MessageFlags> {
[
(MessageFlags::CONSTANT, flags.is_constant()),
(MessageFlags::SHARED, flags.is_shared()),
(MessageFlags::FORBID_SHARING, flags.forbids_sharing()),
(
MessageFlags::FAIL_IF_UNKNOWN_AND_OPEN_FOR_WRITE,
flags.fails_if_unknown_and_open_for_write(),
),
(MessageFlags::MARK_IF_UNKNOWN, flags.marks_if_unknown()),
(MessageFlags::WAS_UNKNOWN, flags.was_unknown()),
(MessageFlags::SHAREABLE, flags.is_shareable()),
(
MessageFlags::FAIL_IF_UNKNOWN_ALWAYS,
flags.fails_if_unknown_always(),
),
]
.into_iter()
.filter_map(|(bit, set)| set.then_some(bit))
.collect()
}
const BITS: [MessageFlags; 8] = [
MessageFlags::CONSTANT,
MessageFlags::SHARED,
MessageFlags::FORBID_SHARING,
MessageFlags::FAIL_IF_UNKNOWN_AND_OPEN_FOR_WRITE,
MessageFlags::MARK_IF_UNKNOWN,
MessageFlags::WAS_UNKNOWN,
MessageFlags::SHAREABLE,
MessageFlags::FAIL_IF_UNKNOWN_ALWAYS,
];
}