#![allow(clippy::upper_case_acronyms)] use super::encoding::{ Ascii, Binary, InvalidMetadataValue, InvalidMetadataValueBytes, ValueEncoding, }; use super::key::MetadataKey; use bytes::Bytes; use http::header::HeaderValue; use std::error::Error; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::str::FromStr; use std::{cmp, fmt}; /// Represents a custom metadata field value. /// /// `MetadataValue` is used as the [`MetadataMap`] value. /// /// [`HeaderMap`]: struct.HeaderMap.html /// [`MetadataMap`]: struct.MetadataMap.html #[derive(Clone)] #[repr(transparent)] pub struct MetadataValue { // Note: There are unsafe transmutes that assume that the memory layout // of MetadataValue is identical to HeaderValue pub(crate) inner: HeaderValue, phantom: PhantomData, } /// A possible error when converting a `MetadataValue` to a string representation. /// /// Metadata field values may contain opaque bytes, in which case it is not /// possible to represent the value as a string. #[derive(Debug)] pub struct ToStrError { _priv: (), } /// An ascii metadata value. pub type AsciiMetadataValue = MetadataValue; /// A binary metadata value. pub type BinaryMetadataValue = MetadataValue; impl MetadataValue { /// Convert a static string to a `MetadataValue`. /// /// This function will not perform any copying, however the string is /// checked to ensure that no invalid characters are present. /// /// For Ascii values, only visible ASCII characters (32-127) are permitted. /// For Binary values, the string must be valid base64. /// /// # Panics /// /// This function panics if the argument contains invalid metadata value /// characters. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_static("hello"); /// assert_eq!(val, "hello"); /// ``` /// /// ``` /// # use tonic::metadata::*; /// let val = BinaryMetadataValue::from_static("SGVsbG8hIQ=="); /// assert_eq!(val, "Hello!!"); /// ``` #[inline] pub fn from_static(src: &'static str) -> Self { MetadataValue { inner: VE::from_static(src), phantom: PhantomData, } } /// Convert a `Bytes` directly into a `MetadataValue` without validating. /// For `MetadataValue` the provided parameter must be base64 /// encoded without padding bytes at the end. /// /// # Safety /// /// This function does NOT validate that illegal bytes are not contained /// within the buffer. #[inline] pub unsafe fn from_shared_unchecked(src: Bytes) -> Self { MetadataValue { inner: HeaderValue::from_maybe_shared_unchecked(src), phantom: PhantomData, } } /// Returns true if the `MetadataValue` has a length of zero bytes. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_static(""); /// assert!(val.is_empty()); /// /// let val = AsciiMetadataValue::from_static("hello"); /// assert!(!val.is_empty()); /// ``` #[inline] pub fn is_empty(&self) -> bool { VE::is_empty(self.inner.as_bytes()) } /// Converts a `MetadataValue` to a Bytes buffer. This method cannot /// fail for Ascii values. For Ascii values, `as_bytes` is more convenient /// to use. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_static("hello"); /// assert_eq!(val.to_bytes().unwrap().as_ref(), b"hello"); /// ``` /// /// ``` /// # use tonic::metadata::*; /// let val = BinaryMetadataValue::from_bytes(b"hello"); /// assert_eq!(val.to_bytes().unwrap().as_ref(), b"hello"); /// ``` #[inline] pub fn to_bytes(&self) -> Result { VE::decode(self.inner.as_bytes()) } /// Mark that the metadata value represents sensitive information. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let mut val = AsciiMetadataValue::from_static("my secret"); /// /// val.set_sensitive(true); /// assert!(val.is_sensitive()); /// /// val.set_sensitive(false); /// assert!(!val.is_sensitive()); /// ``` #[inline] pub fn set_sensitive(&mut self, val: bool) { self.inner.set_sensitive(val); } /// Returns `true` if the value represents sensitive data. /// /// Sensitive data could represent passwords or other data that should not /// be stored on disk or in memory. This setting can be used by components /// like caches to avoid storing the value. HPACK encoders must set the /// metadata field to never index when `is_sensitive` returns true. /// /// Note that sensitivity is not factored into equality or ordering. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let mut val = AsciiMetadataValue::from_static("my secret"); /// /// val.set_sensitive(true); /// assert!(val.is_sensitive()); /// /// val.set_sensitive(false); /// assert!(!val.is_sensitive()); /// ``` #[inline] pub fn is_sensitive(&self) -> bool { self.inner.is_sensitive() } /// Converts a `MetadataValue` to a byte slice. For Binary values, the /// return value is base64 encoded. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_static("hello"); /// assert_eq!(val.as_encoded_bytes(), b"hello"); /// ``` /// /// ``` /// # use tonic::metadata::*; /// let val = BinaryMetadataValue::from_bytes(b"Hello!"); /// assert_eq!(val.as_encoded_bytes(), b"SGVsbG8h"); /// ``` #[inline] pub fn as_encoded_bytes(&self) -> &[u8] { self.inner.as_bytes() } /// Converts a HeaderValue to a MetadataValue. This method assumes that the /// caller has made sure that the value is of the correct Ascii or Binary /// value encoding. #[inline] pub(crate) fn unchecked_from_header_value(value: HeaderValue) -> Self { MetadataValue { inner: value, phantom: PhantomData, } } /// Converts a HeaderValue reference to a MetadataValue. This method assumes /// that the caller has made sure that the value is of the correct Ascii or /// Binary value encoding. #[inline] pub(crate) fn unchecked_from_header_value_ref(header_value: &HeaderValue) -> &Self { unsafe { &*(header_value as *const HeaderValue as *const Self) } } /// Converts a HeaderValue reference to a MetadataValue. This method assumes /// that the caller has made sure that the value is of the correct Ascii or /// Binary value encoding. #[inline] pub(crate) fn unchecked_from_mut_header_value_ref(header_value: &mut HeaderValue) -> &mut Self { unsafe { &mut *(header_value as *mut HeaderValue as *mut Self) } } } /// Attempt to convert a byte slice to a `MetadataValue`. /// /// For Ascii metadata values, If the argument contains invalid metadata /// value bytes, an error is returned. Only byte values between 32 and 255 /// (inclusive) are permitted, excluding byte 127 (DEL). /// /// For Binary metadata values this method cannot fail. See also the Binary /// only version of this method `from_bytes`. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::try_from(b"hello\xfa").unwrap(); /// assert_eq!(val, &b"hello\xfa"[..]); /// ``` /// /// An invalid value /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::try_from(b"\n"); /// assert!(val.is_err()); /// ``` impl<'a, VE: ValueEncoding> TryFrom<&'a [u8]> for MetadataValue { type Error = InvalidMetadataValueBytes; #[inline] fn try_from(src: &[u8]) -> Result { VE::from_bytes(src).map(|value| MetadataValue { inner: value, phantom: PhantomData, }) } } /// Attempt to convert a byte slice to a `MetadataValue`. /// /// For Ascii metadata values, If the argument contains invalid metadata /// value bytes, an error is returned. Only byte values between 32 and 255 /// (inclusive) are permitted, excluding byte 127 (DEL). /// /// For Binary metadata values this method cannot fail. See also the Binary /// only version of this method `from_bytes`. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::try_from(b"hello\xfa").unwrap(); /// assert_eq!(val, &b"hello\xfa"[..]); /// ``` /// /// An invalid value /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::try_from(b"\n"); /// assert!(val.is_err()); /// ``` impl<'a, VE: ValueEncoding, const N: usize> TryFrom<&'a [u8; N]> for MetadataValue { type Error = InvalidMetadataValueBytes; #[inline] fn try_from(src: &[u8; N]) -> Result { Self::try_from(src.as_ref()) } } /// Attempt to convert a `Bytes` buffer to a `MetadataValue`. /// /// For `MetadataValue`, if the argument contains invalid metadata /// value bytes, an error is returned. Only byte values between 32 and 255 /// (inclusive) are permitted, excluding byte 127 (DEL). /// /// For `MetadataValue`, if the argument is not valid base64, an /// error is returned. In use cases where the input is not base64 encoded, /// use `from_bytes`; if the value has to be encoded it's not possible to /// share the memory anyways. impl TryFrom for MetadataValue { type Error = InvalidMetadataValueBytes; #[inline] fn try_from(src: Bytes) -> Result { VE::from_shared(src).map(|value| MetadataValue { inner: value, phantom: PhantomData, }) } } /// Attempt to convert a Vec of bytes to a `MetadataValue`. /// /// For `MetadataValue`, if the argument contains invalid metadata /// value bytes, an error is returned. Only byte values between 32 and 255 /// (inclusive) are permitted, excluding byte 127 (DEL). /// /// For `MetadataValue`, if the argument is not valid base64, an /// error is returned. In use cases where the input is not base64 encoded, /// use `from_bytes`; if the value has to be encoded it's not possible to /// share the memory anyways. impl TryFrom> for MetadataValue { type Error = InvalidMetadataValueBytes; #[inline] fn try_from(src: Vec) -> Result { Self::try_from(src.as_slice()) } } /// Attempt to convert a string to a `MetadataValue`. /// /// If the argument contains invalid metadata value characters, an error is /// returned. Only visible ASCII characters (32-127) are permitted. Use /// `from_bytes` to create a `MetadataValue` that includes opaque octets /// (128-255). impl<'a> TryFrom<&'a str> for MetadataValue { type Error = InvalidMetadataValue; #[inline] fn try_from(s: &'a str) -> Result { s.parse() } } /// Attempt to convert a string to a `MetadataValue`. /// /// If the argument contains invalid metadata value characters, an error is /// returned. Only visible ASCII characters (32-127) are permitted. Use /// `from_bytes` to create a `MetadataValue` that includes opaque octets /// (128-255). impl<'a> TryFrom<&'a String> for MetadataValue { type Error = InvalidMetadataValue; #[inline] fn try_from(s: &'a String) -> Result { s.parse() } } /// Attempt to convert a string to a `MetadataValue`. /// /// If the argument contains invalid metadata value characters, an error is /// returned. Only visible ASCII characters (32-127) are permitted. Use /// `from_bytes` to create a `MetadataValue` that includes opaque octets /// (128-255). impl TryFrom for MetadataValue { type Error = InvalidMetadataValue; #[inline] fn try_from(s: String) -> Result { s.parse() } } // is_empty is defined in the generic impl block above #[allow(clippy::len_without_is_empty)] impl MetadataValue { /// Converts a MetadataKey into a `MetadataValue`. /// /// Since every valid MetadataKey is a valid MetadataValue this is done /// infallibly. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_key::("accept".parse().unwrap()); /// assert_eq!(val, AsciiMetadataValue::try_from(b"accept").unwrap()); /// ``` #[inline] pub fn from_key(key: MetadataKey) -> Self { key.into() } /// Returns the length of `self`, in bytes. /// /// This method is not available for `MetadataValue` because that /// cannot be implemented in constant time, which most people would probably /// expect. To get the length of `MetadataValue`, convert it to a /// Bytes value and measure its length. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_static("hello"); /// assert_eq!(val.len(), 5); /// ``` #[inline] pub fn len(&self) -> usize { self.inner.len() } /// Yields a `&str` slice if the `MetadataValue` only contains visible ASCII /// chars. /// /// This function will perform a scan of the metadata value, checking all the /// characters. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_static("hello"); /// assert_eq!(val.to_str().unwrap(), "hello"); /// ``` pub fn to_str(&self) -> Result<&str, ToStrError> { self.inner.to_str().map_err(|_| ToStrError::new()) } /// Converts a `MetadataValue` to a byte slice. For Binary values, use /// `to_bytes`. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = AsciiMetadataValue::from_static("hello"); /// assert_eq!(val.as_bytes(), b"hello"); /// ``` #[inline] pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } } impl MetadataValue { /// Convert a byte slice to a `MetadataValue`. /// /// # Examples /// /// ``` /// # use tonic::metadata::*; /// let val = BinaryMetadataValue::from_bytes(b"hello\xfa"); /// assert_eq!(val, &b"hello\xfa"[..]); /// ``` #[inline] pub fn from_bytes(src: &[u8]) -> Self { // Only the Ascii version of try_from can fail. Self::try_from(src).unwrap() } } impl AsRef<[u8]> for MetadataValue { #[inline] fn as_ref(&self) -> &[u8] { self.inner.as_ref() } } impl fmt::Debug for MetadataValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { VE::fmt(&self.inner, f) } } impl From> for MetadataValue { #[inline] fn from(h: MetadataKey) -> MetadataValue { MetadataValue { inner: h.inner.into(), phantom: PhantomData, } } } macro_rules! from_integers { ($($name:ident: $t:ident => $max_len:expr),*) => {$( impl From<$t> for MetadataValue { fn from(num: $t) -> MetadataValue { MetadataValue { inner: HeaderValue::from(num), phantom: PhantomData, } } } #[test] fn $name() { let n: $t = 55; let val = AsciiMetadataValue::from(n); assert_eq!(val, &n.to_string()); let n = ::std::$t::MAX; let val = AsciiMetadataValue::from(n); assert_eq!(val, &n.to_string()); } )*}; } from_integers! { // integer type => maximum decimal length // u8 purposely left off... AsciiMetadataValue::from(b'3') could be confusing from_u16: u16 => 5, from_i16: i16 => 6, from_u32: u32 => 10, from_i32: i32 => 11, from_u64: u64 => 20, from_i64: i64 => 20 } #[cfg(target_pointer_width = "16")] from_integers! { from_usize: usize => 5, from_isize: isize => 6 } #[cfg(target_pointer_width = "32")] from_integers! { from_usize: usize => 10, from_isize: isize => 11 } #[cfg(target_pointer_width = "64")] from_integers! { from_usize: usize => 20, from_isize: isize => 20 } #[cfg(test)] mod from_metadata_value_tests { use super::*; use crate::metadata::map::MetadataMap; #[test] fn it_can_insert_metadata_key_as_metadata_value() { let mut map = MetadataMap::new(); map.insert( "accept", MetadataKey::::from_bytes(b"hello-world") .unwrap() .into(), ); assert_eq!( map.get("accept").unwrap(), AsciiMetadataValue::try_from(b"hello-world").unwrap() ); } } impl FromStr for MetadataValue { type Err = InvalidMetadataValue; #[inline] fn from_str(s: &str) -> Result, Self::Err> { HeaderValue::from_str(s) .map(|value| MetadataValue { inner: value, phantom: PhantomData, }) .map_err(|_| InvalidMetadataValue::new()) } } impl From> for Bytes { #[inline] fn from(value: MetadataValue) -> Bytes { Bytes::copy_from_slice(value.inner.as_bytes()) } } impl<'a, VE: ValueEncoding> From<&'a MetadataValue> for MetadataValue { #[inline] fn from(t: &'a MetadataValue) -> Self { t.clone() } } // ===== ToStrError ===== impl ToStrError { pub(crate) fn new() -> Self { ToStrError { _priv: () } } } impl fmt::Display for ToStrError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("failed to convert metadata to a str") } } impl Error for ToStrError {} impl Hash for MetadataValue { fn hash(&self, state: &mut H) { self.inner.hash(state) } } impl Hash for MetadataValue { fn hash(&self, state: &mut H) { match self.to_bytes() { Ok(b) => b.hash(state), Err(e) => e.hash(state), } } } // ===== PartialEq / PartialOrd ===== impl PartialEq for MetadataValue { #[inline] fn eq(&self, other: &MetadataValue) -> bool { // Note: Different binary strings that after base64 decoding // will count as the same value for Binary values. Also, // different invalid base64 values count as equal for Binary // values. VE::values_equal(&self.inner, &other.inner) } } impl Eq for MetadataValue {} impl PartialOrd for MetadataValue { #[inline] fn partial_cmp(&self, other: &MetadataValue) -> Option { Some(self.cmp(other)) } } impl Ord for MetadataValue { #[inline] fn cmp(&self, other: &Self) -> cmp::Ordering { self.inner.cmp(&other.inner) } } impl PartialEq for MetadataValue { #[inline] fn eq(&self, other: &str) -> bool { VE::equals(&self.inner, other.as_bytes()) } } impl PartialEq<[u8]> for MetadataValue { #[inline] fn eq(&self, other: &[u8]) -> bool { VE::equals(&self.inner, other) } } impl PartialOrd for MetadataValue { #[inline] fn partial_cmp(&self, other: &str) -> Option { self.inner.partial_cmp(other.as_bytes()) } } impl PartialOrd<[u8]> for MetadataValue { #[inline] fn partial_cmp(&self, other: &[u8]) -> Option { self.inner.partial_cmp(other) } } impl PartialEq> for str { #[inline] fn eq(&self, other: &MetadataValue) -> bool { *other == *self } } impl PartialEq> for [u8] { #[inline] fn eq(&self, other: &MetadataValue) -> bool { *other == *self } } impl PartialOrd> for str { #[inline] fn partial_cmp(&self, other: &MetadataValue) -> Option { self.as_bytes().partial_cmp(other.inner.as_bytes()) } } impl PartialOrd> for [u8] { #[inline] fn partial_cmp(&self, other: &MetadataValue) -> Option { self.partial_cmp(other.inner.as_bytes()) } } impl PartialEq for MetadataValue { #[inline] fn eq(&self, other: &String) -> bool { *self == other[..] } } impl PartialOrd for MetadataValue { #[inline] fn partial_cmp(&self, other: &String) -> Option { self.inner.partial_cmp(other.as_bytes()) } } impl PartialEq> for String { #[inline] fn eq(&self, other: &MetadataValue) -> bool { *other == *self } } impl PartialOrd> for String { #[inline] fn partial_cmp(&self, other: &MetadataValue) -> Option { self.as_bytes().partial_cmp(other.inner.as_bytes()) } } impl<'a, VE: ValueEncoding> PartialEq> for &'a MetadataValue { #[inline] fn eq(&self, other: &MetadataValue) -> bool { **self == *other } } impl<'a, VE: ValueEncoding> PartialOrd> for &'a MetadataValue { #[inline] fn partial_cmp(&self, other: &MetadataValue) -> Option { (**self).partial_cmp(other) } } impl<'a, VE: ValueEncoding, T: ?Sized> PartialEq<&'a T> for MetadataValue where MetadataValue: PartialEq, { #[inline] fn eq(&self, other: &&'a T) -> bool { *self == **other } } impl<'a, VE: ValueEncoding, T: ?Sized> PartialOrd<&'a T> for MetadataValue where MetadataValue: PartialOrd, { #[inline] fn partial_cmp(&self, other: &&'a T) -> Option { self.partial_cmp(*other) } } impl<'a, VE: ValueEncoding> PartialEq> for &'a str { #[inline] fn eq(&self, other: &MetadataValue) -> bool { *other == *self } } impl<'a, VE: ValueEncoding> PartialOrd> for &'a str { #[inline] fn partial_cmp(&self, other: &MetadataValue) -> Option { self.as_bytes().partial_cmp(other.inner.as_bytes()) } } #[test] fn test_debug() { let cases = &[ ("hello", "\"hello\""), ("hello \"world\"", "\"hello \\\"world\\\"\""), ("\u{7FFF}hello", "\"\\xe7\\xbf\\xbfhello\""), ]; for &(value, expected) in cases { let val = AsciiMetadataValue::try_from(value.as_bytes()).unwrap(); let actual = format!("{:?}", val); assert_eq!(expected, actual); } let mut sensitive = AsciiMetadataValue::from_static("password"); sensitive.set_sensitive(true); assert_eq!("Sensitive", format!("{:?}", sensitive)); } #[test] fn test_is_empty() { fn from_str(s: &str) -> MetadataValue { MetadataValue::::unchecked_from_header_value(s.parse().unwrap()) } assert!(from_str::("").is_empty()); assert!(from_str::("").is_empty()); assert!(!from_str::("a").is_empty()); assert!(!from_str::("a").is_empty()); assert!(!from_str::("=").is_empty()); assert!(from_str::("=").is_empty()); assert!(!from_str::("===").is_empty()); assert!(from_str::("===").is_empty()); assert!(!from_str::("=====").is_empty()); assert!(from_str::("=====").is_empty()); } #[test] fn test_from_shared_base64_encodes() { let value = BinaryMetadataValue::try_from(Bytes::from_static(b"Hello")).unwrap(); assert_eq!(value.as_encoded_bytes(), b"SGVsbG8"); } #[test] fn test_value_eq_value() { type BMV = BinaryMetadataValue; type AMV = AsciiMetadataValue; assert_eq!(AMV::from_static("abc"), AMV::from_static("abc")); assert_ne!(AMV::from_static("abc"), AMV::from_static("ABC")); assert_eq!(BMV::from_bytes(b"abc"), BMV::from_bytes(b"abc")); assert_ne!(BMV::from_bytes(b"abc"), BMV::from_bytes(b"ABC")); // Padding is ignored. assert_eq!( BMV::from_static("SGVsbG8hIQ=="), BMV::from_static("SGVsbG8hIQ") ); // Invalid values are all just invalid from this point of view. unsafe { assert_eq!( BMV::from_shared_unchecked(Bytes::from_static(b"..{}")), BMV::from_shared_unchecked(Bytes::from_static(b"{}..")) ); } } #[test] fn test_value_eq_str() { type BMV = BinaryMetadataValue; type AMV = AsciiMetadataValue; assert_eq!(AMV::from_static("abc"), "abc"); assert_ne!(AMV::from_static("abc"), "ABC"); assert_eq!("abc", AMV::from_static("abc")); assert_ne!("ABC", AMV::from_static("abc")); assert_eq!(BMV::from_bytes(b"abc"), "abc"); assert_ne!(BMV::from_bytes(b"abc"), "ABC"); assert_eq!("abc", BMV::from_bytes(b"abc")); assert_ne!("ABC", BMV::from_bytes(b"abc")); // Padding is ignored. assert_eq!(BMV::from_static("SGVsbG8hIQ=="), "Hello!!"); assert_eq!("Hello!!", BMV::from_static("SGVsbG8hIQ==")); } #[test] fn test_value_eq_bytes() { type BMV = BinaryMetadataValue; type AMV = AsciiMetadataValue; assert_eq!(AMV::from_static("abc"), "abc".as_bytes()); assert_ne!(AMV::from_static("abc"), "ABC".as_bytes()); assert_eq!(*"abc".as_bytes(), AMV::from_static("abc")); assert_ne!(*"ABC".as_bytes(), AMV::from_static("abc")); assert_eq!(*"abc".as_bytes(), BMV::from_bytes(b"abc")); assert_ne!(*"ABC".as_bytes(), BMV::from_bytes(b"abc")); // Padding is ignored. assert_eq!(BMV::from_static("SGVsbG8hIQ=="), "Hello!!".as_bytes()); assert_eq!(*"Hello!!".as_bytes(), BMV::from_static("SGVsbG8hIQ==")); } #[test] fn test_ascii_value_hash() { use std::collections::hash_map::DefaultHasher; type AMV = AsciiMetadataValue; fn hash(value: AMV) -> u64 { let mut hasher = DefaultHasher::new(); value.hash(&mut hasher); hasher.finish() } let value1 = AMV::from_static("abc"); let value2 = AMV::from_static("abc"); assert_eq!(value1, value2); assert_eq!(hash(value1), hash(value2)); let value1 = AMV::from_static("abc"); let value2 = AMV::from_static("xyz"); assert_ne!(value1, value2); assert_ne!(hash(value1), hash(value2)); } #[test] fn test_valid_binary_value_hash() { use std::collections::hash_map::DefaultHasher; type BMV = BinaryMetadataValue; fn hash(value: BMV) -> u64 { let mut hasher = DefaultHasher::new(); value.hash(&mut hasher); hasher.finish() } let value1 = BMV::from_bytes(b"abc"); let value2 = BMV::from_bytes(b"abc"); assert_eq!(value1, value2); assert_eq!(hash(value1), hash(value2)); let value1 = BMV::from_bytes(b"abc"); let value2 = BMV::from_bytes(b"xyz"); assert_ne!(value1, value2); assert_ne!(hash(value1), hash(value2)); } #[test] fn test_invalid_binary_value_hash() { use std::collections::hash_map::DefaultHasher; type BMV = BinaryMetadataValue; fn hash(value: BMV) -> u64 { let mut hasher = DefaultHasher::new(); value.hash(&mut hasher); hasher.finish() } unsafe { let value1 = BMV::from_shared_unchecked(Bytes::from_static(b"..{}")); let value2 = BMV::from_shared_unchecked(Bytes::from_static(b"{}..")); assert_eq!(value1, value2); assert_eq!(hash(value1), hash(value2)); } unsafe { let valid = BMV::from_bytes(b"abc"); let invalid = BMV::from_shared_unchecked(Bytes::from_static(b"{}..")); assert_ne!(valid, invalid); assert_ne!(hash(valid), hash(invalid)); } }