From 9cdda3f7c7dafa260cda9dd1d61931b81d891fe7 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 22 May 2019 14:43:24 +0200 Subject: [PATCH] tools: add helpful vector and read operations After importing the I/O ops trait via: use crate::tools::io::ops::*; Instead of: let mut buffer = vec![0u8; 65536]; file.read_exact(&mut buffer)?; use: let buffer = file.read_exact_allocated(65536)?; After importing the vector helpers via: use crate::tools::vec::{self, ops::*}; For a buffer which *could* be uninitialized but you prefer zero-initialization anyway for security reasons, instead of: let mut buffer = vec![0u8; len]; use: let mut buffer = vec::undefined(len); which zero-initializes, but, if the `valgrind` feature flag is enabled, marks the vector as having undefined contents, so reading from it will cause valgrind errors. Signed-off-by: Wolfgang Bumiller --- Cargo.toml | 5 + src/tools.rs | 2 + src/tools/io.rs | 303 +++++++++++++++++++++++++++++++++++++++++++ src/tools/io/ops.rs | 203 +++++++++++++++++++++++++++++ src/tools/vec.rs | 118 +++++++++++++++++ src/tools/vec/ops.rs | 90 +++++++++++++ 6 files changed, 721 insertions(+) create mode 100644 src/tools/io.rs create mode 100644 src/tools/io/ops.rs create mode 100644 src/tools/vec.rs create mode 100644 src/tools/vec/ops.rs diff --git a/Cargo.toml b/Cargo.toml index a71aab28..55e2f572 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,11 @@ pam = "0.7" zstd = "0.4" xdg = "2.2" mio = "0.6" +valgrind_request = { version = "1.1", optional = true } + +[features] +default = [] +valgrind = ["valgrind_request"] [replace] "zstd-sys:1.4.8" = { path = "zstd-sys" } diff --git a/src/tools.rs b/src/tools.rs index 72fa5f2a..2dbc3735 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -37,6 +37,8 @@ pub mod read; pub mod write; pub mod acl; pub mod xattr; +pub mod vec; +pub mod io; mod process_locker; pub use process_locker::*; diff --git a/src/tools/io.rs b/src/tools/io.rs new file mode 100644 index 00000000..653d479f --- /dev/null +++ b/src/tools/io.rs @@ -0,0 +1,303 @@ +//! Module providing I/O helpers (sync and async). +//! +//! The [`ops`](io::ops) module provides helper traits for types implementing [`Read`](std::io::Read). +//! +//! The top level functions in of this module here are used for standalone implementations of +//! various functionality which is actually intended to be available as methods to types +//! implementing `AsyncRead`, which, however, without async/await cannot be methods due to them +//! having non-static lifetimes in that case. +//! +//! ``` +//! use std::io; +//! +//! use crate::tools::io::read_exact_allocated; +//! use crate::tools::vec::{self, ops::*}; +//! +//! // Currently usable: +//! fn do_something() -> impl Future, Error = io::Error> { +//! tokio::fs::File::open("some.file") +//! .and_then(|file| read_exact_allocated(file, unsafe { vec::uninitialized(1024) })) +//! .and_then(|(file, mut buffer)| { +//! so_something_with(&buffer); +//! // append more data: +//! tokio::io::read_exact(file, unsafe { buffer.grow_uninitialized(1024) }) +//! }) +//! .and_then(|(_file, bigger_buffer)| { +//! use_the(bigger_buffer); +//! Ok(bigger_buffer) +//! }); +//! } +//! +//! // Future async/await variant: +//! async fn do_something() -> Vec { +//! let mut file = tokio::fs::File::open("some.file").await?; +//! let mut buffer = file.read_exact_allocated(1024).await?; +//! do_something_with(buffer); +//! file.append_to_vec(&mut buffer, 1024).await?; +//! buffer +//! } +//! ``` + +use std::io; + +use futures::Future; +use futures::{Async, Poll}; +use tokio::io::AsyncRead; + +use crate::tools::vec::{self, ops::*}; + +pub mod ops; + +/// Create a future which reads an exact amount of bytes from an input. +/// +/// The future's output is a tuple containing the input and a newly allocated `Vec` containing +/// the data. +/// +/// Example: +/// ``` +/// tokio::fs::File::open("some.file") +/// .and_then(|file| read_exact_allocated(file, 1024)) +/// .and_then(|(_file, data)| { +/// use_the(data); +/// }) +/// ``` +pub fn read_exact_allocated(reader: R, size: usize) -> ReadExactAllocated { + ReadExactAllocated(Some(reader), None, size) +} + +/// A future returned by [`read_exact_allocated`]. +pub struct ReadExactAllocated(Option, Option>, usize); + +impl Future for ReadExactAllocated { + type Item = (R, Vec); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + assert!(self.0.is_some(), "polled after ready"); + + // allocation happens on first poll: + if self.1.is_none() { + self.1 = Some(unsafe { vec::uninitialized(self.2) }); + // now self.2 is the position: + self.2 = 0; + } + + let mut buffer = self.1.take().unwrap(); + + loop { + match self.0.as_mut().unwrap().poll_read(&mut buffer[self.2..]) { + Ok(Async::Ready(0)) => { + self.0 = None; + return Err(io::Error::from(io::ErrorKind::UnexpectedEof)); + } + Ok(Async::Ready(some)) => { + self.2 += some; + if self.2 == buffer.len() { + self.0 = None; + return Ok(Async::Ready((self.0.take().unwrap(), buffer))); + } + continue; + } + Ok(Async::NotReady) => { + self.1 = Some(buffer); + return Ok(Async::NotReady); + } + Err(err) => { + self.0 = None; + return Err(err); + } + } + } + } +} + +/// Create a future which appends up to at most `size` bytes to a vector, growing it as needed. +/// +/// This will grow the vector as if a single `.reserve(amount_to_read)` call was made and fill it +/// with as much data as a single read call will provide. +/// +/// The future's output is a tuple containing the input, the vector and the number of bytes +/// actually read. +/// +/// Example: +/// ``` +/// tokio::fs::File::open("some.file") +/// .and_then(|file| append_to_vec(file, Vec::new(), 1024)) +/// .and_then(|(_file, data, size)| { +/// assert!(data.len() == size); +/// println!("Actually got {} bytes of data.", size); +/// use_the(data); +/// }) +/// ``` +pub fn append_to_vec(reader: R, mut vector: V, size: usize) -> AppendToVec +where + R: AsyncRead, + V: AsMut>, +{ + let pos = vector.as_mut().len(); + unsafe { + vector.as_mut().grow_uninitialized(size); + } + AppendToVec(Some(reader), Some(vector), pos) +} + +pub struct AppendToVec(Option, Option, usize) +where + R: AsyncRead, + V: AsMut>; + +impl Future for AppendToVec +where + R: AsyncRead, + V: AsMut>, +{ + type Item = (R, V, usize); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + assert!(self.0.is_some() && self.1.is_some(), "polled after ready"); + + let mut output = self.1.take().unwrap(); + match self.0.as_mut().unwrap().poll_read(&mut output.as_mut()[self.2..]) { + Ok(Async::Ready(some)) => { + unsafe { + output.as_mut().set_len(self.2 + some); + } + return Ok(Async::Ready((self.0.take().unwrap(), output, some))); + } + Ok(Async::NotReady) => { + self.1 = Some(output); + return Ok(Async::NotReady); + } + Err(err) => { + self.0 = None; + return Err(err); + } + } + } +} + +/// Create a future which appends an exact amount of bytes to a vector, growing it as needed. +/// +/// This will grow the vector as if a single `.reserve(amount_to_read)` call was made and fill it +/// as much data as requested. If not enough data is available, this produces an +/// [`io::Error`](std::io::Error) of kind +/// [`ErrorKind::UnexpectedEof`](std::io::ErrorKind::UnexpectedEof). +/// +/// The future's output is a tuple containing the input and the vector. +/// +/// Example: +/// ``` +/// tokio::fs::File::open("some.file") +/// .and_then(|file| append_exact_to_vec(file, Vec::new(), 1024)) +/// .and_then(|(_file, data)| { +/// assert!(data.len() == size); +/// println!("Actually got {} bytes of data.", size); +/// use_the(data); +/// }) +/// ``` +pub fn append_exact_to_vec(reader: R, mut vector: V, size: usize) -> AppendExactToVec +where + R: AsyncRead, + V: AsMut>, +{ + let pos = vector.as_mut().len(); + unsafe { + vector.as_mut().grow_uninitialized(size); + } + AppendExactToVec(Some(reader), Some(vector), pos) +} + +pub struct AppendExactToVec(Option, Option, usize) +where + R: AsyncRead, + V: AsMut>; + +impl Future for AppendExactToVec +where + R: AsyncRead, + V: AsMut>, +{ + type Item = (R, V); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + assert!(self.0.is_some() && self.1.is_some(), "polled after ready"); + + let mut output = self.1.take().unwrap(); + loop { + match self.0.as_mut().unwrap().poll_read(&mut output.as_mut()[self.2..]) { + Ok(Async::Ready(0)) => { + self.0 = None; + return Err(io::Error::from(io::ErrorKind::UnexpectedEof)); + } + Ok(Async::Ready(some)) => { + self.2 += some; + if self.2 == output.as_mut().len() { + self.0 = None; + return Ok(Async::Ready((self.0.take().unwrap(), output))); + } + continue; + } + Ok(Async::NotReady) => { + self.1 = Some(output); + return Ok(Async::NotReady); + } + Err(err) => { + self.0 = None; + return Err(err); + } + } + } + } +} + + +/* + * TODO: A trait such as the one below is only useful inside `async fn`, so this partwill have to + * wait... + * + * When we have async/await we can finish this and move it into io/async_read.rs + +/// Some additional related functionality for types implementing `AsyncRead`. Note that most of +/// these methods map to functions from the [`io`](super::io) module, which are standalone +/// variants. +/// +/// This trait only works with standard futures or as part of `poll_fn` bodies, due to it requiring +/// non-static lifetimes on futures. +pub trait AsyncReadExtOps: AsyncRead + Sized { + /// Read data into a newly allocated vector. This is a shortcut for: + /// ``` + /// let mut data = Vec::with_capacity(len); + /// unsafe { + /// data.set_len(len); + /// } + /// reader.read_exact(&mut data) + /// ``` + /// + /// With this trait, we just use: + /// ``` + /// use crate::tools::vec::ops::*; + /// + /// let data = reader.read_exact_allocated(len).await?; + /// ``` + fn read_exact_allocated(&mut self, size: usize) -> ReadExactAllocated<&mut Self> { + ReadExactAllocated(crate::tools::io::read_exact_allocated(self, size)) + } +} + +impl AsyncReadExtOps for T { +} + +pub struct ReadExactAllocated(crate::tools::io::ReadExactAllocated); + +impl futures::Future for ReadExactAllocated { + type Item = Vec; + type Error = io::Error; + + fn poll(&mut self) -> futures::Poll { + let (_this, data) = futures::try_ready!(self.0.poll()); + Ok(futures::Async::Ready(data)) + } +} + */ diff --git a/src/tools/io/ops.rs b/src/tools/io/ops.rs new file mode 100644 index 00000000..aaebbd86 --- /dev/null +++ b/src/tools/io/ops.rs @@ -0,0 +1,203 @@ +//! This module provides additional operations for handling byte buffers for types implementing +//! [`Read`](std::io::Read). +//! +//! See the [`ReadExtOps`](ops::ReadExtOps) trait for examples. + +use std::io; + +use endian_trait::Endian; + +use crate::tools::vec::{self, ops::*}; + +/// Adds some additional related functionality for types implementing [`Read`](std::io::Read). +/// +/// Particularly for reading into a newly allocated buffer, appending to a `Vec` or reading +/// values of a specific endianess (types implementing [`Endian`]). +/// +/// Examples: +/// ``` +/// use crate::tools::io::ops::*; +/// +/// let mut file = std::fs::File::open("some.data")?; +/// +/// // read some bytes into a newly allocated Vec: +/// let mut data = file.read_exact_allocated(header.data_size as usize)?; +/// +/// // appending data to a vector: +/// let actually_appended = file.append_to_vec(&mut data, length)?; // .read() version +/// file.append_exact_to_vec(&mut data, length)?; // .read_exact() version +/// ``` +/// +/// Or for reading values of a defined representation and endianess: +/// +/// ``` +/// #[derive(Endian)] +/// #[repr(C)] +/// struct Header { +/// version: u16, +/// data_size: u16, +/// } +/// +/// // We have given `Header` a proper binary representation via `#[repr]`, so this is safe: +/// let header: Header = unsafe { file.read_le_value()? }; +/// let mut blob = file.read_exact_allocated(header.data_size as usize)?; +/// ``` +/// +/// [`Endian`]: https://docs.rs/endian_trait/0.6/endian_trait/trait.Endian.html +pub trait ReadExtOps { + /// Read data into a newly allocated vector. This is a shortcut for: + /// ``` + /// let mut data = Vec::with_capacity(len); + /// unsafe { + /// data.set_len(len); + /// } + /// reader.read_exact(&mut data)?; + /// ``` + /// + /// With this trait, we just use: + /// ``` + /// use crate::tools::vec::ops::*; + /// + /// let data = reader.read_exact_allocated(len); + /// ``` + fn read_exact_allocated(&mut self, size: usize) -> io::Result>; + + /// Append data to a vector, growing it as necessary. Returns the amount of data appended. + fn append_to_vec(&mut self, out: &mut Vec, size: usize) -> io::Result; + + /// Append an exact amount of data to a vector, growing it as necessary. + fn append_exact_to_vec(&mut self, out: &mut Vec, size: usize) -> io::Result<()>; + + /// Read a value with host endianess. + /// + /// This is limited to types implementing the [`Endian`] trait under the assumption that + /// this is only done for types which are supposed to be read/writable directly. + /// + /// There's no way to directly depend on a type having a specific `#[repr(...)]`, therefore + /// this is considered unsafe. + /// + /// ``` + /// use crate::tools::vec::ops::*; + /// + /// #[derive(Endian)] + /// #[repr(C, packed)] + /// struct Data { + /// value: u16, + /// count: u32, + /// } + /// + /// let mut file = std::fs::File::open("my-raw.dat")?; + /// // We know `Data` has a safe binary representation (#[repr(C, packed)]), so we can + /// // safely use our helper: + /// let data: Data = unsafe { file.read_host_value()? }; + /// ``` + /// + /// [`Endian`]: https://docs.rs/endian_trait/0.6/endian_trait/trait.Endian.html + unsafe fn read_host_value(&mut self) -> io::Result; + + /// Read a little endian value. + /// + /// The return type is required to implement the [`Endian`] trait, and we make the + /// assumption that this is only done for types which are supposed to be read/writable + /// directly. + /// + /// There's no way to directly depend on a type having a specific `#[repr(...)]`, therefore + /// this is considered unsafe. + /// + /// ``` + /// use crate::tools::vec::ops::*; + /// + /// #[derive(Endian)] + /// #[repr(C, packed)] + /// struct Data { + /// value: u16, + /// count: u32, + /// } + /// + /// let mut file = std::fs::File::open("my-little-endian.dat")?; + /// // We know `Data` has a safe binary representation (#[repr(C, packed)]), so we can + /// // safely use our helper: + /// let data: Data = unsafe { file.read_le_value()? }; + /// ``` + /// + /// [`Endian`]: https://docs.rs/endian_trait/0.6/endian_trait/trait.Endian.html + unsafe fn read_le_value(&mut self) -> io::Result; + + /// Read a big endian value. + /// + /// The return type is required to implement the [`Endian`] trait, and we make the + /// assumption that this is only done for types which are supposed to be read/writable + /// directly. + /// + /// There's no way to directly depend on a type having a specific `#[repr(...)]`, therefore + /// this is considered unsafe. + /// + /// ``` + /// use crate::tools::vec::ops::*; + /// + /// #[derive(Endian)] + /// #[repr(C, packed)] + /// struct Data { + /// value: u16, + /// count: u32, + /// } + /// + /// let mut file = std::fs::File::open("my-big-endian.dat")?; + /// // We know `Data` has a safe binary representation (#[repr(C, packed)]), so we can + /// // safely use our helper: + /// let data: Data = unsafe { file.read_be_value()? }; + /// ``` + /// + /// [`Endian`]: https://docs.rs/endian_trait/0.6/endian_trait/trait.Endian.html + unsafe fn read_be_value(&mut self) -> io::Result; +} + +impl ReadExtOps for R { + fn read_exact_allocated(&mut self, size: usize) -> io::Result> { + let mut out = unsafe { vec::uninitialized(size) }; + self.read_exact(&mut out)?; + Ok(out) + } + + fn append_to_vec(&mut self, out: &mut Vec, size: usize) -> io::Result { + let pos = out.len(); + unsafe { + out.grow_uninitialized(size); + } + let got = self.read(&mut out[pos..])?; + unsafe { + out.set_len(pos + got); + } + Ok(got) + } + + fn append_exact_to_vec(&mut self, out: &mut Vec, size: usize) -> io::Result<()> { + let pos = out.len(); + unsafe { + out.grow_uninitialized(size); + } + self.read_exact(&mut out[pos..])?; + Ok(()) + } + + unsafe fn read_host_value(&mut self) -> io::Result { + let mut value: T = std::mem::uninitialized(); + self.read_exact(std::slice::from_raw_parts_mut( + &mut value as *mut T as *mut u8, + std::mem::size_of::(), + ))?; + Ok(value) + } + + unsafe fn read_le_value(&mut self) -> io::Result { + Ok(self.read_host_value::()?. + from_le() + ) + } + + unsafe fn read_be_value(&mut self) -> io::Result { + Ok(self.read_host_value::()? + .from_be() + ) + } +} diff --git a/src/tools/vec.rs b/src/tools/vec.rs new file mode 100644 index 00000000..6cdf0402 --- /dev/null +++ b/src/tools/vec.rs @@ -0,0 +1,118 @@ +//! Byte vector helpers. +//! +//! We have a lot of I/O code such as: +//! ``` +//! let mut buffer = vec![0u8; header_size]; +//! file.read_exact(&mut buffer)?; +//! ``` +//! (We even have this case with a 4M buffer!) +//! +//! This needlessly initializes the buffer to zero (which not only wastes time (an insane amount of +//! time on debug builds, actually) but also prevents tools such as valgrind from pointing out +//! access to actually uninitialized data, which may hide bugs...) +//! +//! This module provides some helpers for this kind of code. Many of these are supposed to stay on +//! a lower level, with I/O helpers for types implementing [`Read`](std::io::Read) being available +//! in the [`tools::io`](crate::tools::io) module. +//! +//! Examples: +//! ``` +//! use crate::tools::vec::{self, ops::*}; +//! +//! let mut buffer = vec::undefined(size); // A zero-initialized buffer with valgrind support +//! +//! let mut buffer = unsafe { vec::uninitialized(size) }; // an actually uninitialized buffer +//! vec::clear(&mut buffer); // zero out an &mut [u8] +//! +//! vec::clear(unsafe { +//! buffer.grow_unintialized(more); // grow the buffer with uninitialized bytes +//! }); +//! ``` + +pub mod ops; + +/// Create an uninitialized byte vector of a specific size. +/// +/// This is just a shortcut for: +/// ``` +/// let mut v = Vec::with_capacity(len); +/// unsafe { +/// v.set_len(len); +/// } +/// ``` +#[inline] +pub unsafe fn uninitialized(len: usize) -> Vec { + let mut out = Vec::with_capacity(len); + out.set_len(len); + out +} + +/// Shortcut to zero out a slice of bytes. +#[inline] +pub fn clear(data: &mut [u8]) { + unsafe { + std::ptr::write_bytes(data.as_mut_ptr(), 0, data.len()); + } +} + +/// Create a newly allocated, zero initialized byte vector. +#[inline] +pub fn zeroed(len: usize) -> Vec { + unsafe { + let mut out = uninitialized(len); + clear(&mut out); + out + } +} + +/// Create a newly allocated byte vector of a specific size with "undefined" content. +/// +/// The data will be zero initialized, but, if the `valgrind` feature is activated, it will be +/// marked as uninitialized for debugging. +#[inline] +pub fn undefined(len: usize) -> Vec { + undefined_impl(len) +} + +#[cfg(not(feature = "valgrind"))] +fn undefined_impl(len: usize) -> Vec { + zeroed(len) +} + +#[cfg(feature = "valgrind")] +fn undefined_impl(len: usize) -> Vec { + let out = zeroed(len); + vg::make_slice_undefined(&out[..]); + out +} + +#[cfg(feature = "valgrind")] +mod vg { + type ValgrindValue = valgrind_request::Value; + + /// Mark a memory region as undefined when using valgrind, causing it to treat read access to + /// it as error. + #[inline] + pub(crate) fn make_mem_undefined(addr: *const u8, len: usize) -> ValgrindValue { + const MAKE_MEM_UNDEFINED: ValgrindValue = + (((b'M' as ValgrindValue) << 24) | ((b'C' as ValgrindValue) << 16)) + 1; + unsafe { + valgrind_request::do_client_request( + 0, + &[ + MAKE_MEM_UNDEFINED, + addr as usize as ValgrindValue, + len as ValgrindValue, + 0, 0, 0, + ], + ) + } + } + + /// Mark a slice of bytes as undefined when using valgrind, causing it to treat read access to + /// it as error. + #[inline] + pub(crate) fn make_slice_undefined(data: &[u8]) -> ValgrindValue { + make_mem_undefined(data.as_ptr(), data.len()) + } +} diff --git a/src/tools/vec/ops.rs b/src/tools/vec/ops.rs new file mode 100644 index 00000000..38b2f38a --- /dev/null +++ b/src/tools/vec/ops.rs @@ -0,0 +1,90 @@ +//! This module provides additional operations for `Vec`. +//! +//! Example: +//! ``` +//! use crate::tools::vec::{self, ops::*}; +//! +//! fn append_1024_to_vec(input: T, buffer: &mut Vec) -> std::io::Result<()> { +//! input.read_exact(unsafe { buffer.grow_uninitialized(1024) }) +//! } +//! ``` + +/// Some additional byte vector operations useful for I/O code. +/// Example: +/// ``` +/// use crate::tools::vec::{self, ops::*}; +/// +/// let mut data = file.read_exact_allocated(1024)?; +/// do_something(); +/// file.read_exact(unsafe { +/// data.grow_uninitialized(1024); +/// })?; +/// ``` +/// +/// Note that this module also provides a safe alternative for the case where +/// `grow_uninitialized()` is directly followed by a `read_exact()` call via the [`ReadExtOps`] +/// trait: +/// ``` +/// file.append_to_vec(&mut data, 1024)?; +/// ``` +/// +/// [`ReadExtOps`]: crate::tools::io::ops::ReadExtOps +pub trait VecU8ExtOps { + /// Grow a vector without initializing its elements. The difference to simply using `reserve` + /// is that it also updates the actual length, making the newly allocated data part of the + /// slice. + /// + /// This is a shortcut for: + /// ``` + /// vec.reserve(more); + /// let total = vec.len() + more; + /// unsafe { + /// vec.set_len(total); + /// } + /// ``` + /// + /// This returns a mutable slice to the newly allocated space, so it can be used inline: + /// ``` + /// file.read_exact(unsafe { buffer.grow_uninitialized(1024) })?; + /// ``` + /// + /// Although for the above case it is recommended to use the even shorter version from the + /// [`ReadExtOps`] trait: + /// ``` + /// // use crate::tools::vec::ops::ReadExtOps; + /// file.append_to_vec(&mut buffer, 1024)?; + /// ``` + /// + /// [`ReadExtOps`]: crate::tools::io::ops::ReadExtOps + unsafe fn grow_uninitialized(&mut self, more: usize) -> &mut [u8]; + + /// Resize a vector to a specific size without initializing its data. This is a shortcut for: + /// ``` + /// if new_size <= vec.len() { + /// vec.truncate(new_size); + /// } else { + /// unsafe { + /// vec.grow_uninitialized(new_size - vec.len()); + /// } + /// } + /// ``` + unsafe fn resize_uninitialized(&mut self, total: usize); +} + +impl VecU8ExtOps for Vec { + unsafe fn grow_uninitialized(&mut self, more: usize) -> &mut [u8] { + let old_len = self.len(); + self.reserve(more); + let total = old_len + more; + self.set_len(total); + &mut self[old_len..] + } + + unsafe fn resize_uninitialized(&mut self, new_size: usize) { + if new_size <= self.len() { + self.truncate(new_size); + } else { + self.grow_uninitialized(new_size - self.len()); + } + } +}