use super::cmp::CanonicalOrd;
use super::scan::{BadSymbol, Scanner, Symbol, SymbolCharsError};
use super::wire::{Compose, ParseError};
#[cfg(feature = "bytes")]
use bytes::BytesMut;
use core::{cmp, fmt, hash, str};
use octseq::builder::FreezeBuilder;
#[cfg(feature = "serde")]
use octseq::serde::{DeserializeOctets, SerializeOctets};
use octseq::{
EmptyBuilder, FromBuilder, IntoBuilder, Octets, OctetsBuilder,
OctetsFrom, Parser, ShortBuf, Truncate,
};
#[cfg(feature = "std")]
use std::vec::Vec;
#[derive(Clone)]
pub struct CharStr<Octs: ?Sized>(Octs);
impl CharStr<()> {
pub const MAX_LEN: usize = 255;
}
impl<Octs: ?Sized> CharStr<Octs> {
#[must_use]
pub fn empty() -> Self
where
Octs: From<&'static [u8]>,
{
CharStr(b"".as_ref().into())
}
pub fn from_octets(octets: Octs) -> Result<Self, CharStrError>
where
Octs: AsRef<[u8]> + Sized,
{
CharStr::check_slice(octets.as_ref())?;
Ok(unsafe { Self::from_octets_unchecked(octets) })
}
pub unsafe fn from_octets_unchecked(octets: Octs) -> Self
where
Octs: Sized,
{
CharStr(octets)
}
}
impl CharStr<[u8]> {
pub fn from_slice(slice: &[u8]) -> Result<&Self, CharStrError> {
Self::check_slice(slice)?;
Ok(unsafe { Self::from_slice_unchecked(slice) })
}
#[must_use]
pub fn empty_slice() -> &'static Self {
unsafe { Self::from_slice_unchecked(b"".as_ref()) }
}
#[must_use]
pub unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self {
&*(slice as *const [u8] as *const Self)
}
unsafe fn from_slice_mut_unchecked(slice: &mut [u8]) -> &mut Self {
&mut *(slice as *mut [u8] as *mut Self)
}
fn check_slice(slice: &[u8]) -> Result<(), CharStrError> {
if slice.len() > CharStr::MAX_LEN {
Err(CharStrError)
} else {
Ok(())
}
}
}
impl<Octs: ?Sized> CharStr<Octs> {
#[must_use]
pub fn builder() -> CharStrBuilder<Octs::Builder>
where
Octs: IntoBuilder,
Octs::Builder: EmptyBuilder,
{
CharStrBuilder::new()
}
pub fn into_builder(self) -> CharStrBuilder<Octs::Builder>
where
Octs: IntoBuilder + Sized,
<Octs as IntoBuilder>::Builder: AsRef<[u8]>,
{
unsafe {
CharStrBuilder::from_builder_unchecked(IntoBuilder::into_builder(
self.0,
))
}
}
pub fn into_octets(self) -> Octs
where
Octs: Sized,
{
self.0
}
pub fn for_slice(&self) -> &CharStr<[u8]>
where
Octs: AsRef<[u8]>,
{
unsafe { CharStr::from_slice_unchecked(self.0.as_ref()) }
}
pub fn for_slice_mut(&mut self) -> &mut CharStr<[u8]>
where
Octs: AsMut<[u8]>,
{
unsafe { CharStr::from_slice_mut_unchecked(self.0.as_mut()) }
}
pub fn as_slice(&self) -> &[u8]
where
Octs: AsRef<[u8]>,
{
self.0.as_ref()
}
pub fn as_slice_mut(&mut self) -> &mut [u8]
where
Octs: AsMut<[u8]>,
{
self.0.as_mut()
}
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError>
where
Octs: Sized,
{
let len = parser.parse_u8()? as usize;
parser
.parse_octets(len)
.map(|bytes| unsafe { Self::from_octets_unchecked(bytes) })
.map_err(Into::into)
}
}
impl<Octs: AsRef<[u8]> + ?Sized> CharStr<Octs> {
pub fn len(&self) -> usize {
self.as_slice().len()
}
pub fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
pub fn iter(&self) -> Iter {
Iter {
octets: self.as_slice(),
}
}
}
impl CharStr<[u8]> {
pub fn skip<Src: Octets + ?Sized>(
parser: &mut Parser<Src>,
) -> Result<(), ParseError> {
let len = parser.parse_u8()?;
parser.advance(len.into()).map_err(Into::into)
}
}
impl<Octs: AsRef<[u8]> + ?Sized> CharStr<Octs> {
pub fn compose_len(&self) -> u16 {
u16::try_from(self.0.as_ref().len() + 1).expect("long charstr")
}
pub fn compose<Target: OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
u8::try_from(self.0.as_ref().len())
.expect("long charstr")
.compose(target)?;
target.append_slice(self.0.as_ref())
}
}
impl<Octets> CharStr<Octets> {
pub fn scan<S: Scanner<Octets = Octets>>(
scanner: &mut S,
) -> Result<Self, S::Error> {
scanner.scan_charstr()
}
}
impl<Octs, SrcOcts> OctetsFrom<CharStr<SrcOcts>> for CharStr<Octs>
where
Octs: OctetsFrom<SrcOcts>,
{
type Error = Octs::Error;
fn try_octets_from(
source: CharStr<SrcOcts>,
) -> Result<Self, Self::Error> {
Octs::try_octets_from(source.0)
.map(|octets| unsafe { Self::from_octets_unchecked(octets) })
}
}
impl<Octets> str::FromStr for CharStr<Octets>
where
Octets: FromBuilder,
<Octets as FromBuilder>::Builder: OctetsBuilder
+ FreezeBuilder<Octets = Octets>
+ EmptyBuilder
+ AsRef<[u8]>,
{
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut builder =
CharStrBuilder::<<Octets as FromBuilder>::Builder>::with_capacity(
s.len(),
);
let mut chars = s.chars();
while let Some(symbol) = Symbol::from_chars(&mut chars)? {
if builder.len() == CharStr::MAX_LEN {
return Err(FromStrError::LongString);
}
builder.append_slice(&[symbol.into_octet()?])?
}
Ok(builder.finish())
}
}
impl<Octets: AsRef<U> + ?Sized, U: ?Sized> AsRef<U> for CharStr<Octets> {
fn as_ref(&self) -> &U {
self.0.as_ref()
}
}
impl<Octets: AsMut<U> + ?Sized, U: ?Sized> AsMut<U> for CharStr<Octets> {
fn as_mut(&mut self) -> &mut U {
self.0.as_mut()
}
}
impl<T, U> PartialEq<U> for CharStr<T>
where
T: AsRef<[u8]> + ?Sized,
U: AsRef<[u8]> + ?Sized,
{
fn eq(&self, other: &U) -> bool {
self.as_slice().eq_ignore_ascii_case(other.as_ref())
}
}
impl<T: AsRef<[u8]> + ?Sized> Eq for CharStr<T> {}
impl<T, U> PartialOrd<U> for CharStr<T>
where
T: AsRef<[u8]> + ?Sized,
U: AsRef<[u8]> + ?Sized,
{
fn partial_cmp(&self, other: &U) -> Option<cmp::Ordering> {
self.0
.as_ref()
.iter()
.map(u8::to_ascii_lowercase)
.partial_cmp(other.as_ref().iter().map(u8::to_ascii_lowercase))
}
}
impl<T: AsRef<[u8]> + ?Sized> Ord for CharStr<T> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0
.as_ref()
.iter()
.map(u8::to_ascii_lowercase)
.cmp(other.0.as_ref().iter().map(u8::to_ascii_lowercase))
}
}
impl<T, U> CanonicalOrd<CharStr<U>> for CharStr<T>
where
T: AsRef<[u8]> + ?Sized,
U: AsRef<[u8]> + ?Sized,
{
fn canonical_cmp(&self, other: &CharStr<U>) -> cmp::Ordering {
match self.0.as_ref().len().cmp(&other.0.as_ref().len()) {
cmp::Ordering::Equal => {}
other => return other,
}
self.as_slice().cmp(other.as_slice())
}
}
impl<T: AsRef<[u8]> + ?Sized> hash::Hash for CharStr<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.0
.as_ref()
.iter()
.map(u8::to_ascii_lowercase)
.for_each(|ch| ch.hash(state))
}
}
impl<T: AsRef<[u8]> + ?Sized> fmt::Display for CharStr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for &ch in self.0.as_ref() {
fmt::Display::fmt(&Symbol::from_octet(ch), f)?;
}
Ok(())
}
}
impl<T: AsRef<[u8]> + ?Sized> fmt::LowerHex for CharStr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for ch in self.0.as_ref() {
write!(f, "{:02x}", ch)?;
}
Ok(())
}
}
impl<T: AsRef<[u8]> + ?Sized> fmt::UpperHex for CharStr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for ch in self.0.as_ref() {
write!(f, "{:02X}", ch)?;
}
Ok(())
}
}
impl<T: AsRef<[u8]> + ?Sized> fmt::Debug for CharStr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("CharStr")
.field(&format_args!("{}", self))
.finish()
}
}
impl<T: AsRef<[u8]>> IntoIterator for CharStr<T> {
type Item = u8;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
IntoIter::new(self.0)
}
}
impl<'a, T: AsRef<[u8]> + ?Sized + 'a> IntoIterator for &'a CharStr<T> {
type Item = u8;
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
Iter::new(self.0.as_ref())
}
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for CharStr<T>
where
T: AsRef<[u8]> + SerializeOctets + ?Sized,
{
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_newtype_struct(
"CharStr",
&format_args!("{}", self),
)
} else {
serializer.serialize_newtype_struct(
"CharStr",
&self.0.as_serialized_octets(),
)
}
}
}
#[cfg(feature = "serde")]
impl<'de, Octets> serde::Deserialize<'de> for CharStr<Octets>
where
Octets: FromBuilder + DeserializeOctets<'de>,
<Octets as FromBuilder>::Builder: OctetsBuilder
+ FreezeBuilder<Octets = Octets>
+ EmptyBuilder
+ AsRef<[u8]>,
{
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
use core::marker::PhantomData;
use core::str::FromStr;
struct InnerVisitor<'de, T: DeserializeOctets<'de>>(T::Visitor);
impl<'de, Octets> serde::de::Visitor<'de> for InnerVisitor<'de, Octets>
where
Octets: FromBuilder + DeserializeOctets<'de>,
<Octets as FromBuilder>::Builder: OctetsBuilder
+ FreezeBuilder<Octets = Octets>
+ EmptyBuilder
+ AsRef<[u8]>,
{
type Value = CharStr<Octets>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a character string")
}
fn visit_str<E: serde::de::Error>(
self,
v: &str,
) -> Result<Self::Value, E> {
CharStr::from_str(v).map_err(E::custom)
}
fn visit_borrowed_bytes<E: serde::de::Error>(
self,
value: &'de [u8],
) -> Result<Self::Value, E> {
self.0.visit_borrowed_bytes(value).and_then(|octets| {
CharStr::from_octets(octets).map_err(E::custom)
})
}
#[cfg(feature = "std")]
fn visit_byte_buf<E: serde::de::Error>(
self,
value: std::vec::Vec<u8>,
) -> Result<Self::Value, E> {
self.0.visit_byte_buf(value).and_then(|octets| {
CharStr::from_octets(octets).map_err(E::custom)
})
}
}
struct NewtypeVisitor<T>(PhantomData<T>);
impl<'de, Octets> serde::de::Visitor<'de> for NewtypeVisitor<Octets>
where
Octets: FromBuilder + DeserializeOctets<'de>,
<Octets as FromBuilder>::Builder: OctetsBuilder
+ FreezeBuilder<Octets = Octets>
+ EmptyBuilder
+ AsRef<[u8]>,
{
type Value = CharStr<Octets>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a character string")
}
fn visit_newtype_struct<D: serde::Deserializer<'de>>(
self,
deserializer: D,
) -> Result<Self::Value, D::Error> {
if deserializer.is_human_readable() {
deserializer
.deserialize_str(InnerVisitor(Octets::visitor()))
} else {
Octets::deserialize_with_visitor(
deserializer,
InnerVisitor(Octets::visitor()),
)
}
}
}
deserializer.deserialize_newtype_struct(
"CharStr",
NewtypeVisitor(PhantomData),
)
}
}
#[derive(Clone)]
pub struct CharStrBuilder<Builder>(Builder);
impl<Builder: EmptyBuilder> CharStrBuilder<Builder> {
#[must_use]
pub fn new() -> Self {
CharStrBuilder(Builder::empty())
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
CharStrBuilder(Builder::with_capacity(capacity))
}
}
impl<Builder: OctetsBuilder + AsRef<[u8]>> CharStrBuilder<Builder> {
unsafe fn from_builder_unchecked(builder: Builder) -> Self {
CharStrBuilder(builder)
}
pub fn from_builder(builder: Builder) -> Result<Self, CharStrError> {
if builder.as_ref().len() > CharStr::MAX_LEN {
Err(CharStrError)
} else {
Ok(unsafe { Self::from_builder_unchecked(builder) })
}
}
}
#[cfg(feature = "std")]
impl CharStrBuilder<Vec<u8>> {
#[must_use]
pub fn new_vec() -> Self {
Self::new()
}
#[must_use]
pub fn vec_with_capacity(capacity: usize) -> Self {
Self::with_capacity(capacity)
}
}
#[cfg(feature = "bytes")]
impl CharStrBuilder<BytesMut> {
pub fn new_bytes() -> Self {
Self::new()
}
pub fn bytes_with_capacity(capacity: usize) -> Self {
Self::with_capacity(capacity)
}
}
impl<Builder> CharStrBuilder<Builder> {
pub fn as_slice(&self) -> &[u8]
where
Builder: AsRef<[u8]>,
{
self.0.as_ref()
}
pub fn finish(self) -> CharStr<Builder::Octets>
where
Builder: FreezeBuilder,
{
unsafe { CharStr::from_octets_unchecked(self.0.freeze()) }
}
}
impl<Builder: AsRef<[u8]>> CharStrBuilder<Builder> {
pub fn len(&self) -> usize {
self.as_slice().len()
}
pub fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
}
impl<Builder: EmptyBuilder> Default for CharStrBuilder<Builder> {
fn default() -> Self {
Self::new()
}
}
impl<Builder> OctetsBuilder for CharStrBuilder<Builder>
where
Builder: OctetsBuilder + AsRef<[u8]>,
{
type AppendError = ShortBuf;
fn append_slice(
&mut self,
slice: &[u8],
) -> Result<(), Self::AppendError> {
if self.0.as_ref().len() + slice.len() > CharStr::MAX_LEN {
return Err(ShortBuf);
}
self.0.append_slice(slice).map_err(Into::into)
}
}
impl<Builder: Truncate> Truncate for CharStrBuilder<Builder> {
fn truncate(&mut self, len: usize) {
self.0.truncate(len)
}
}
impl<Builder: AsRef<[u8]>> AsRef<[u8]> for CharStrBuilder<Builder> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<Builder: AsMut<[u8]>> AsMut<[u8]> for CharStrBuilder<Builder> {
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut()
}
}
pub struct IntoIter<T> {
octets: T,
len: usize,
pos: usize,
}
impl<T: AsRef<[u8]>> IntoIter<T> {
pub(crate) fn new(octets: T) -> Self {
IntoIter {
len: octets.as_ref().len(),
octets,
pos: 0,
}
}
}
impl<T: AsRef<[u8]>> Iterator for IntoIter<T> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
if self.pos == self.len {
None
} else {
let res = self.octets.as_ref()[self.pos];
self.pos += 1;
Some(res)
}
}
}
pub struct Iter<'a> {
octets: &'a [u8],
}
impl<'a> Iter<'a> {
pub(crate) fn new(octets: &'a [u8]) -> Self {
Iter { octets }
}
}
impl<'a> Iterator for Iter<'a> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
let (res, octets) = self.octets.split_first()?;
self.octets = octets;
Some(*res)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CharStrError;
impl fmt::Display for CharStrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("long character string")
}
}
#[cfg(feature = "std")]
impl std::error::Error for CharStrError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum FromStrError {
LongString,
SymbolChars(SymbolCharsError),
BadSymbol(BadSymbol),
ShortBuf,
}
impl From<SymbolCharsError> for FromStrError {
fn from(err: SymbolCharsError) -> FromStrError {
FromStrError::SymbolChars(err)
}
}
impl From<BadSymbol> for FromStrError {
fn from(err: BadSymbol) -> FromStrError {
FromStrError::BadSymbol(err)
}
}
impl From<ShortBuf> for FromStrError {
fn from(_: ShortBuf) -> FromStrError {
FromStrError::ShortBuf
}
}
impl fmt::Display for FromStrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
FromStrError::LongString => {
f.write_str("character string with more than 255 octets")
}
FromStrError::SymbolChars(ref err) => err.fmt(f),
FromStrError::BadSymbol(ref err) => err.fmt(f),
FromStrError::ShortBuf => ShortBuf.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromStrError {}
#[cfg(test)]
#[cfg(feature = "std")]
mod test {
use super::*;
use octseq::builder::infallible;
use std::vec::Vec;
type CharStrRef<'a> = CharStr<&'a [u8]>;
#[test]
fn from_slice() {
assert_eq!(
CharStr::from_slice(b"01234").unwrap().as_slice(),
b"01234"
);
assert_eq!(CharStr::from_slice(b"").unwrap().as_slice(), b"");
assert!(CharStr::from_slice(&vec![0; 255]).is_ok());
assert!(CharStr::from_slice(&vec![0; 256]).is_err());
}
#[test]
fn from_octets() {
assert_eq!(
CharStr::from_octets("01234").unwrap().as_slice(),
b"01234"
);
assert_eq!(CharStr::from_octets("").unwrap().as_slice(), b"");
assert!(CharStr::from_octets(vec![0; 255]).is_ok());
assert!(CharStr::from_octets(vec![0; 256]).is_err());
}
#[test]
fn from_str() {
use std::str::{from_utf8, FromStr};
type Cs = CharStr<Vec<u8>>;
assert_eq!(Cs::from_str("foo").unwrap().as_slice(), b"foo");
assert_eq!(Cs::from_str("f\\oo").unwrap().as_slice(), b"foo");
assert_eq!(Cs::from_str("foo\\112").unwrap().as_slice(), b"foo\x70");
assert_eq!(
Cs::from_str("\"foo\\\"2\"").unwrap().as_slice(),
b"\"foo\"2\""
);
assert_eq!(Cs::from_str("06 dii").unwrap().as_slice(), b"06 dii");
assert!(Cs::from_str("0\\").is_err());
assert!(Cs::from_str("0\\2").is_err());
assert!(Cs::from_str("0\\2a").is_err());
assert!(Cs::from_str("ö").is_err());
assert!(Cs::from_str("\x06").is_err());
assert!(Cs::from_str(from_utf8(&[b'a'; 256]).unwrap()).is_err());
}
#[test]
fn parse() {
let mut parser = Parser::from_static(b"12\x03foo\x02bartail");
parser.advance(2).unwrap();
let foo = CharStrRef::parse(&mut parser).unwrap();
let bar = CharStrRef::parse(&mut parser).unwrap();
assert_eq!(foo.as_slice(), b"foo");
assert_eq!(bar.as_slice(), b"ba");
assert_eq!(parser.peek_all(), b"rtail");
assert!(
CharStrRef::parse(&mut Parser::from_static(b"\x04foo")).is_err(),
)
}
#[test]
fn compose() {
let mut target = Vec::new();
let val = CharStr::from_slice(b"foo").unwrap();
infallible(val.compose(&mut target));
assert_eq!(target, b"\x03foo".as_ref());
let mut target = Vec::new();
let val = CharStr::from_slice(b"").unwrap();
infallible(val.compose(&mut target));
assert_eq!(target, &b"\x00"[..]);
}
fn are_eq(l: &[u8], r: &[u8]) -> bool {
CharStr::from_slice(l).unwrap() == CharStr::from_slice(r).unwrap()
}
#[test]
fn eq() {
assert!(are_eq(b"abc", b"abc"));
assert!(!are_eq(b"abc", b"def"));
assert!(!are_eq(b"abc", b"ab"));
assert!(!are_eq(b"abc", b"abcd"));
assert!(are_eq(b"ABC", b"abc"));
assert!(!are_eq(b"ABC", b"def"));
assert!(!are_eq(b"ABC", b"ab"));
assert!(!are_eq(b"ABC", b"abcd"));
assert!(are_eq(b"", b""));
assert!(!are_eq(b"", b"A"));
}
fn is_ord(l: &[u8], r: &[u8], order: cmp::Ordering) {
assert_eq!(
CharStr::from_slice(l)
.unwrap()
.cmp(CharStr::from_slice(r).unwrap()),
order
)
}
#[test]
fn ord() {
use std::cmp::Ordering::*;
is_ord(b"abc", b"ABC", Equal);
is_ord(b"abc", b"a", Greater);
is_ord(b"abc", b"A", Greater);
is_ord(b"a", b"BC", Less);
}
#[test]
fn append_slice() {
let mut o = CharStrBuilder::new_vec();
o.append_slice(b"foo").unwrap();
assert_eq!(o.finish().as_slice(), b"foo");
let mut o = CharStrBuilder::from_builder(vec![0; 254]).unwrap();
o.append_slice(b"f").unwrap();
assert_eq!(o.len(), 255);
assert!(o.append_slice(b"f").is_err());
let mut o =
CharStrBuilder::from_builder(vec![b'f', b'o', b'o']).unwrap();
o.append_slice(b"bar").unwrap();
assert_eq!(o.as_ref(), b"foobar");
assert!(o.append_slice(&[0u8; 250][..]).is_err());
o.append_slice(&[0u8; 249][..]).unwrap();
assert_eq!(o.len(), 255);
}
#[cfg(feature = "serde")]
#[test]
fn ser_de() {
use serde_test::{assert_tokens, Configure, Token};
assert_tokens(
&CharStr::from_octets(vec![b'f', b'o', 0x12])
.unwrap()
.compact(),
&[
Token::NewtypeStruct { name: "CharStr" },
Token::ByteBuf(b"fo\x12"),
],
);
assert_tokens(
&CharStr::from_octets(vec![b'f', b'o', 0x12])
.unwrap()
.readable(),
&[
Token::NewtypeStruct { name: "CharStr" },
Token::Str("fo\\018"),
],
);
}
}