use std::fmt;
use bytes::{Bytes, BytesMut};
use subtle::ConstantTimeEq;
#[derive(Clone, Default)]
pub struct Community(Bytes);
impl Community {
#[must_use]
pub fn new(value: impl Into<Self>) -> Self {
value.into()
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[must_use]
pub fn into_bytes(self) -> Bytes {
self.0
}
#[must_use]
pub fn matches(&self, candidate: impl AsRef<[u8]>) -> bool {
let candidate = candidate.as_ref();
self.0.len() == candidate.len() && bool::from(self.0.as_ref().ct_eq(candidate))
}
}
impl fmt::Debug for Community {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Community([REDACTED])")
}
}
impl From<Bytes> for Community {
fn from(value: Bytes) -> Self {
Self(value)
}
}
impl From<&Bytes> for Community {
fn from(value: &Bytes) -> Self {
Self(value.clone())
}
}
impl From<&Community> for Community {
fn from(value: &Community) -> Self {
value.clone()
}
}
impl From<BytesMut> for Community {
fn from(value: BytesMut) -> Self {
Self(value.freeze())
}
}
impl From<Vec<u8>> for Community {
fn from(value: Vec<u8>) -> Self {
Self(Bytes::from(value))
}
}
impl From<&Vec<u8>> for Community {
fn from(value: &Vec<u8>) -> Self {
Self(Bytes::copy_from_slice(value))
}
}
impl From<String> for Community {
fn from(value: String) -> Self {
Self(Bytes::from(value))
}
}
impl From<&String> for Community {
fn from(value: &String) -> Self {
Self(Bytes::copy_from_slice(value.as_bytes()))
}
}
impl From<&str> for Community {
fn from(value: &str) -> Self {
Self(Bytes::copy_from_slice(value.as_bytes()))
}
}
impl From<&[u8]> for Community {
fn from(value: &[u8]) -> Self {
Self(Bytes::copy_from_slice(value))
}
}
impl<const N: usize> From<&[u8; N]> for Community {
fn from(value: &[u8; N]) -> Self {
Self(Bytes::copy_from_slice(value))
}
}
impl<const N: usize> From<[u8; N]> for Community {
fn from(value: [u8; N]) -> Self {
Self(Bytes::copy_from_slice(&value))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_is_redacted_and_bytes_remain_explicitly_available() {
let community = Community::from(Bytes::from_static(b"sentinel-community"));
assert!(community.matches(b"sentinel-community"));
assert!(!format!("{community:?}").contains("sentinel-community"));
assert!(!format!("{community:#?}").contains("sentinel-community"));
assert_eq!(
community.into_bytes(),
Bytes::from_static(b"sentinel-community")
);
}
#[test]
fn matches_arbitrary_octets_and_rejects_content_and_length_mismatches() {
let community = Community::from(&b"private\x00\xff"[..]);
assert!(community.matches(b"private\x00\xff"));
assert!(!community.matches(b"private\x00\xfe"));
assert!(!community.matches(b"private\x00"));
assert!(!community.matches(b"private\x00\xff\x00"));
}
}