diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-04-02 16:19:47 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-04-02 16:23:40 +0200 |
commit | 80f359aa53f5672bca64030535cb5afae080acce (patch) | |
tree | bb74dcc5759dc9de1f55490364b490e13dc36d2e /openpgp/src/serialize/stream | |
parent | caeb69d5988b7877e2ebfa7dbd90191b2c586785 (diff) |
openpgp: Move the writer module to serialize::stream.
Diffstat (limited to 'openpgp/src/serialize/stream')
-rw-r--r-- | openpgp/src/serialize/stream/padding.rs | 6 | ||||
-rw-r--r-- | openpgp/src/serialize/stream/writer/compression_common.rs | 82 | ||||
-rw-r--r-- | openpgp/src/serialize/stream/writer/mod.rs | 573 | ||||
-rw-r--r-- | openpgp/src/serialize/stream/writer/writer_bzip2.rs | 74 | ||||
-rw-r--r-- | openpgp/src/serialize/stream/writer/writer_deflate.rs | 142 |
5 files changed, 875 insertions, 2 deletions
diff --git a/openpgp/src/serialize/stream/padding.rs b/openpgp/src/serialize/stream/padding.rs index 516e1a47..02bbe7ff 100644 --- a/openpgp/src/serialize/stream/padding.rs +++ b/openpgp/src/serialize/stream/padding.rs @@ -49,8 +49,10 @@ use crate::packet::header::CTB; use crate::serialize::{ PartialBodyFilter, Marshal, - writer, - stream::Cookie, + stream::{ + writer, + Cookie, + }, }; use crate::types::{ CompressionAlgorithm, diff --git a/openpgp/src/serialize/stream/writer/compression_common.rs b/openpgp/src/serialize/stream/writer/compression_common.rs new file mode 100644 index 00000000..7ac32d31 --- /dev/null +++ b/openpgp/src/serialize/stream/writer/compression_common.rs @@ -0,0 +1,82 @@ +//! Common code for the compression writers. + +use crate::{ + Error, + Result, +}; + +/// Compression level. +/// +/// This value is used by the encoders to tune their compression +/// strategy. The level is restricted to levels commonly used by +/// compression libraries, `0` to `9`, where `0` means no compression, +/// `1` means fastest compression, `6` being a good default, and +/// meaning `9` best compression. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CompressionLevel(u8); + +impl Default for CompressionLevel { + fn default() -> Self { + Self(6) + } +} + +impl CompressionLevel { + /// Creates a new compression level. + /// + /// `level` must be in range `0..10`, where `0` means no + /// compression, `1` means fastest compression, `6` being a good + /// default, and meaning `9` best compression. + pub fn new(level: u8) -> Result<CompressionLevel> { + if level < 10 { + Ok(Self(level)) + } else { + Err(Error::InvalidArgument( + format!("compression level out of range: {}", level)).into()) + } + } + + /// No compression. + pub fn none() -> CompressionLevel { + Self(0) + } + + /// Fastest compression. + pub fn fastest() -> CompressionLevel { + Self(1) + } + /// Best compression. + pub fn best() -> CompressionLevel { + Self(9) + } +} + +#[cfg(feature = "compression-deflate")] +mod into_deflate_compression { + use flate2::Compression; + use super::*; + + impl From<CompressionLevel> for Compression { + fn from(l: CompressionLevel) -> Self { + Compression::new(l.0 as u32) + } + } +} + +#[cfg(feature = "compression-bzip2")] +mod into_bzip2_compression { + use bzip2::Compression; + use super::*; + + impl From<CompressionLevel> for Compression { + fn from(l: CompressionLevel) -> Self { + if l <= CompressionLevel::fastest() { + Compression::Fastest + } else if l <= CompressionLevel::default() { + Compression::Default + } else { + Compression::Best + } + } + } +} diff --git a/openpgp/src/serialize/stream/writer/mod.rs b/openpgp/src/serialize/stream/writer/mod.rs new file mode 100644 index 00000000..2141ca98 --- /dev/null +++ b/openpgp/src/serialize/stream/writer/mod.rs @@ -0,0 +1,573 @@ +//! Stackable writers. + +#[cfg(feature = "compression-bzip2")] +mod writer_bzip2; +#[cfg(feature = "compression-bzip2")] +pub use self::writer_bzip2::BZ; +#[cfg(feature = "compression-deflate")] +mod writer_deflate; +#[cfg(feature = "compression-deflate")] +pub use self::writer_deflate::{ZIP, ZLIB}; +mod compression_common; +pub use compression_common::CompressionLevel; + +use std::fmt; +use std::io; + +use crate::crypto::{aead, symmetric}; +use crate::types::{ + AEADAlgorithm, + SymmetricAlgorithm, +}; +use crate::{ + Result, + crypto::SessionKey, +}; + +/// A stack of writers. +#[derive(Debug)] +pub struct Stack<'a, C>(BoxStack<'a, C>); + +impl<'a, C> Stack<'a, C> { + pub(crate) fn from(bs: BoxStack<'a, C>) -> Self { + Stack(bs) + } + + pub(crate) fn as_ref(&self) -> &BoxStack<'a, C> { + &self.0 + } + + pub(crate) fn as_mut(&mut self) -> &mut BoxStack<'a, C> { + &mut self.0 + } + + /// Finalizes this writer, returning the underlying writer. + pub fn finalize_one(self) -> Result<Option<Stack<'a, C>>> { + Ok(self.0.into_inner()?.map(|bs| Self::from(bs))) + } + + /// Finalizes all writers, tearing down the whole stack. + pub fn finalize(self) -> Result<()> { + let mut stack = self; + while let Some(s) = stack.finalize_one()? { + stack = s; + } + Ok(()) + } +} + +impl<'a, C> io::Write for Stack<'a, C> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +impl<'a, C> From<Stack<'a, C>> for BoxStack<'a, C> { + fn from(s: Stack<'a, C>) -> Self { + s.0 + } +} + +pub(crate) type BoxStack<'a, C> = Box<dyn Stackable<'a, C> + 'a>; + +/// Makes a writer stackable and provides convenience functions. +pub(crate) trait Stackable<'a, C> : io::Write + fmt::Debug { + /// Recovers the inner stackable. + /// + /// This can fail if the current `Stackable` has buffered data + /// that hasn't been written to the underlying `Stackable`. + fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>>; + + /// Pops the stackable from the stack, detaching it. + /// + /// Returns the detached stack. + /// + /// Note: Only the Signer implements this interface. + fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>>; + + /// Sets the inner stackable. + /// + /// Note: Only the Signer implements this interface. + fn mount(&mut self, new: BoxStack<'a, C>); + + /// Returns a mutable reference to the inner `Writer`, if + /// any. + /// + /// It is a very bad idea to write any data from the inner + /// `Writer`, but it can sometimes be useful to get the cookie. + fn inner_mut(&mut self) -> Option<&mut dyn Stackable<'a, C>>; + + /// Returns a reference to the inner `Writer`. + fn inner_ref(&self) -> Option<&dyn Stackable<'a, C>>; + + /// Sets the cookie and returns the old value. + fn cookie_set(&mut self, cookie: C) -> C; + + /// Returns a reference to the cookie. + fn cookie_ref(&self) -> &C; + + /// Returns a mutable reference to the cookie. + fn cookie_mut(&mut self) -> &mut C; + + /// Returns the number of bytes written to this filter. + fn position(&self) -> u64; + + /// Writes a byte. + fn write_u8(&mut self, b: u8) -> io::Result<()> { + let b : [u8; 1] = [b; 1]; + self.write_all(&b[..]) + } + + /// Writes a big endian `u16`. + fn write_be_u16(&mut self, n: u16) -> io::Result<()> { + let b : [u8; 2] = [ ((n >> 8) & 0xFF) as u8, (n & 0xFF) as u8 ]; + self.write_all(&b[..]) + } + + /// Writes a big endian `u32`. + fn write_be_u32(&mut self, n: u32) -> io::Result<()> { + let b : [u8; 4] = [ (n >> 24) as u8, ((n >> 16) & 0xFF) as u8, + ((n >> 8) & 0xFF) as u8, (n & 0xFF) as u8 ]; + self.write_all(&b[..]) + } +} + +/// Make a `Box<Stackable>` look like a Stackable. +impl <'a, C> Stackable<'a, C> for BoxStack<'a, C> { + fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { + (*self).into_inner() + } + /// Recovers the inner stackable. + fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { + self.as_mut().pop() + } + /// Sets the inner stackable. + fn mount(&mut self, new: BoxStack<'a, C>) { + self.as_mut().mount(new); + } + fn inner_mut(&mut self) -> Option<&mut dyn Stackable<'a, C>> { + self.as_mut().inner_mut() + } + fn inner_ref(&self) -> Option<&dyn Stackable<'a, C>> { + self.as_ref().inner_ref() + } + fn cookie_set(&mut self, cookie: C) -> C { + self.as_mut().cookie_set(cookie) + } + fn cookie_ref(&self) -> &C { + self.as_ref().cookie_ref() + } + fn cookie_mut(&mut self) -> &mut C { + self.as_mut().cookie_mut() + } + fn position(&self) -> u64 { + self.as_ref().position() + } +} + +/// Maps a function over the stack of writers. +#[allow(dead_code)] +pub(crate) fn map<C, F>(head: &dyn Stackable<C>, mut fun: F) + where F: FnMut(&dyn Stackable<C>) -> bool { + let mut ow = Some(head); + while let Some(w) = ow { + if ! fun(w) { + break; + } + ow = w.inner_ref() + } +} + +/// Maps a function over the stack of mutable writers. +#[allow(dead_code)] +pub(crate) fn map_mut<C, F>(head: &mut dyn Stackable<C>, mut fun: F) + where F: FnMut(&mut dyn Stackable<C>) -> bool { + let mut ow = Some(head); + while let Some(w) = ow { + if ! fun(w) { + break; + } + ow = w.inner_mut() + } +} + +/// Dumps the writer stack. +#[allow(dead_code)] +pub(crate) fn dump<C>(head: &dyn Stackable<C>) { + let mut depth = 0; + map(head, |w| { + eprintln!("{}: {:?}", depth, w); + depth += 1; + true + }); +} + +/// The identity writer just relays anything written. +pub struct Identity<'a, C> { + inner: Option<BoxStack<'a, C>>, + cookie: C, +} + +impl<'a, C: 'a> Identity<'a, C> { + /// Makes an identity writer. + pub fn new(inner: Stack<'a, C>, cookie: C) + -> Stack<'a, C> { + Stack::from(Box::new(Self{inner: Some(inner.into()), cookie })) + } +} + +impl<'a, C> fmt::Debug for Identity<'a, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Identity") + .field("inner", &self.inner) + .finish() + } +} + +impl<'a, C> io::Write for Identity<'a, C> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let writer = self.inner.as_mut() + .ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, + "Writer is finalized."))?; + writer.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + let writer = self.inner.as_mut() + .ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, + "Writer is finalized."))?; + writer.flush() + } +} + +impl<'a, C> Stackable<'a, C> for Identity<'a, C> { + /// Recovers the inner stackable. + fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { + Ok(self.inner) + } + /// Recovers the inner stackable. + fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { + Ok(self.inner.take()) + } + /// Sets the inner stackable. + fn mount(&mut self, new: BoxStack<'a, C>) { + self.inner = Some(new); + } + fn inner_ref(&self) -> Option<&dyn Stackable<'a, C>> { + if let Some(ref i) = self.inner { + Some(i) + } else { + None + } + } + fn inner_mut(&mut self) -> Option<&mut dyn Stackable<'a, C>> { + if let Some(ref mut i) = self.inner { + Some(i) + } else { + None + } + } + fn cookie_set(&mut self, cookie: C) -> C { + ::std::mem::replace(&mut self.cookie, cookie) + } + fn cookie_ref(&self) -> &C { + &self.cookie + } + fn cookie_mut(&mut self) -> &mut C { + &mut self.cookie + } + fn position(&self) -> u64 { + self.inner.as_ref().map(|i| i.position()).unwrap_or(0) + } +} + +/// Generic writer wrapping `io::Write`. +pub struct Generic<W: io::Write, C> { + inner: W, + cookie: C, + position: u64, +} + +impl<'a, W: 'a + io::Write, C: 'a> Generic<W, C> { + /// Wraps an `io::Write`r. + pub fn new(inner: W, cookie: C) -> Stack<'a, C> { + Stack::from(Box::new(Self::new_unboxed(inner.into(), cookie))) + } + + fn new_unboxed(inner: W, cookie: C) -> Self { + Generic { + inner, + cookie, + position: 0, + } + } +} + +impl<W: io::Write, C> fmt::Debug for Generic<W, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("writer::Generic") + .finish() + } +} + +impl<W: io::Write, C> io::Write for Generic<W, C> { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + match self.inner.write(bytes) { + Ok(n) => { + self.position += n as u64; + Ok(n) + }, + Err(e) => Err(e), + } + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl<'a, W: io::Write, C> Stackable<'a, C> for Generic<W, C> { + /// Recovers the inner stackable. + fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { + Ok(None) + } + /// Recovers the inner stackable. + fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { + Ok(None) + } + /// Sets the inner stackable. + fn mount(&mut self, _new: BoxStack<'a, C>) { + } + fn inner_mut(&mut self) -> Option<&mut dyn Stackable<'a, C>> { + // If you use Generic to wrap an io::Writer, and you know that + // the io::Writer's inner is also a Stackable, then return a + // reference to the innermost Stackable in your + // implementation. See e.g. writer::ZLIB. + None + } + fn inner_ref(&self) -> Option<&dyn Stackable<'a, C>> { + // If you use Generic to wrap an io::Writer, and you know that + // the io::Writer's inner is also a Stackable, then return a + // reference to the innermost Stackable in your + // implementation. See e.g. writer::ZLIB. + None + } + fn cookie_set(&mut self, cookie: C) -> C { + ::std::mem::replace(&mut self.cookie, cookie) + } + fn cookie_ref(&self) -> &C { + &self.cookie + } + fn cookie_mut(&mut self) -> &mut C { + &mut self.cookie + } + fn position(&self) -> u64 { + self.position + } +} + + +/// Encrypting writer. +pub struct Encryptor<'a, C: 'a> { + inner: Generic<symmetric::Encryptor<BoxStack<'a, C>>, C>, +} + +impl<'a, C: 'a> Encryptor<'a, C> { + /// Makes an encrypting writer. + pub fn new(inner: Stack<'a, C>, cookie: C, algo: SymmetricAlgorithm, + key: &[u8]) + -> Result<Stack<'a, C>> + { + Ok(Stack::from(Box::new(Encryptor { + inner: Generic::new_unboxed( + symmetric::Encryptor::new(algo, key, inner.into())?, + cookie), + }))) + } +} + +impl<'a, C: 'a> fmt::Debug for Encryptor<'a, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("writer::Encryptor") + .field("inner", &self.inner) + .finish() + } +} + +impl<'a, C: 'a> io::Write for Encryptor<'a, C> { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + self.inner.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl<'a, C: 'a> Stackable<'a, C> for Encryptor<'a, C> { + fn into_inner(mut self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { + let inner = self.inner.inner.finish()?; + Ok(Some(inner)) + } + fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { + unreachable!("Only implemented by Signer") + } + fn mount(&mut self, _new: BoxStack<'a, C>) { + unreachable!("Only implemented by Signer") + } + fn inner_mut(&mut self) -> Option<&mut dyn Stackable<'a, C>> { + // XXX: Unfortunately, this doesn't work due to a lifetime mismatch: + // self.inner.inner.get_mut().map(|r| r.as_mut()) + None + } + fn inner_ref(&self) -> Option<&dyn Stackable<'a, C>> { + self.inner.inner.get_ref().map(|r| r.as_ref()) + } + fn cookie_set(&mut self, cookie: C) -> C { + self.inner.cookie_set(cookie) + } + fn cookie_ref(&self) -> &C { + self.inner.cookie_ref() + } + fn cookie_mut(&mut self) -> &mut C { + self.inner.cookie_mut() + } + fn position(&self) -> u64 { + self.inner.position + } +} + + +/// AEAD encrypting writer. +pub struct AEADEncryptor<'a, C: 'a> { + inner: Generic<aead::Encryptor<BoxStack<'a, C>>, C>, +} + +impl<'a, C: 'a> AEADEncryptor<'a, C> { + /// Makes an encrypting writer. + pub fn new(inner: Stack<'a, C>, cookie: C, + cipher: SymmetricAlgorithm, aead: AEADAlgorithm, + chunk_size: usize, iv: &[u8], key: &SessionKey) + -> Result<Stack<'a, C>> + { + Ok(Stack::from(Box::new(AEADEncryptor { + inner: Generic::new_unboxed( + aead::Encryptor::new(1, cipher, aead, chunk_size, iv, key, + inner.into())?, + cookie), + }))) + } +} + +impl<'a, C: 'a> fmt::Debug for AEADEncryptor<'a, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("writer::AEADEncryptor") + .field("inner", &self.inner) + .finish() + } +} + +impl<'a, C: 'a> io::Write for AEADEncryptor<'a, C> { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + self.inner.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl<'a, C: 'a> Stackable<'a, C> for AEADEncryptor<'a, C> { + fn into_inner(mut self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { + let inner = self.inner.inner.finish()?; + Ok(Some(inner)) + } + fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { + unreachable!("Only implemented by Signer") + } + fn mount(&mut self, _new: BoxStack<'a, C>) { + unreachable!("Only implemented by Signer") + } + fn inner_mut(&mut self) -> Option<&mut dyn Stackable<'a, C>> { + // XXX: Unfortunately, this doesn't work due to a lifetime mismatch: + // self.inner.inner.get_mut().map(|r| r.as_mut()) + None + } + fn inner_ref(&self) -> Option<&dyn Stackable<'a, C>> { + self.inner.inner.get_ref().map(|r| r.as_ref()) + } + fn cookie_set(&mut self, cookie: C) -> C { + self.inner.cookie_set(cookie) + } + fn cookie_ref(&self) -> &C { + self.inner.cookie_ref() + } + fn cookie_mut(&mut self) -> &mut C { + self.inner.cookie_mut() + } + fn position(&self) -> u64 { + self.inner.position + } +} + +#[cfg(test)] +mod test { + use std::io::Write; + use super::*; + + #[derive(Debug)] + struct Cookie { + state: &'static str, + } + + #[test] + fn generic_writer() { + let mut inner = Vec::new(); + { + let mut w = Generic::new(&mut inner, Cookie { state: "happy" }); + assert_eq!(w.as_ref().cookie_ref().state, "happy"); + dump(w.as_ref()); + + w.as_mut().cookie_mut().state = "sad"; + assert_eq!(w.as_ref().cookie_ref().state, "sad"); + + w.write_all(b"be happy").unwrap(); + let mut count = 0; + map_mut(w.as_mut(), |g| { + let new = Cookie { state: "happy" }; + let old = g.cookie_set(new); + assert_eq!(old.state, "sad"); + count += 1; + true + }); + assert_eq!(count, 1); + assert_eq!(w.as_ref().cookie_ref().state, "happy"); + } + assert_eq!(&inner, b"be happy"); + } + + #[test] + fn stack() { + let mut inner = Vec::new(); + { + let w = Generic::new(&mut inner, Cookie { state: "happy" }); + dump(w.as_ref()); + + let w = Identity::new(w, Cookie { state: "happy" }); + dump(w.as_ref()); + + let mut count = 0; + map(w.as_ref(), |g| { + assert_eq!(g.cookie_ref().state, "happy"); + count += 1; + true + }); + assert_eq!(count, 2); + } + } + +} diff --git a/openpgp/src/serialize/stream/writer/writer_bzip2.rs b/openpgp/src/serialize/stream/writer/writer_bzip2.rs new file mode 100644 index 00000000..62affd0c --- /dev/null +++ b/openpgp/src/serialize/stream/writer/writer_bzip2.rs @@ -0,0 +1,74 @@ +use bzip2::write::BzEncoder; +use std::fmt; +use std::io; + +use crate::Result; +use super::{Generic, Stack, BoxStack, Stackable, CompressionLevel}; + +/// BZing writer. +pub struct BZ<'a, C: 'a> { + inner: Generic<BzEncoder<BoxStack<'a, C>>, C>, +} + +impl<'a, C: 'a> BZ<'a, C> { + /// Makes a BZ compressing writer. + pub fn new<L>(inner: Stack<'a, C>, cookie: C, level: L) -> Stack<'a, C> + where L: Into<Option<CompressionLevel>> + { + Stack::from(Box::new(BZ { + inner: Generic::new_unboxed( + BzEncoder::new(inner.into(), + level.into().unwrap_or_default().into()), + cookie), + })) + } +} + +impl<'a, C: 'a> fmt::Debug for BZ<'a, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("writer::BZ") + .field("inner", &self.inner) + .finish() + } +} + +impl<'a, C: 'a> io::Write for BZ<'a, C> { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + self.inner.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl<'a, C: 'a> Stackable<'a, C> for BZ<'a, C> { + fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { + let inner = self.inner.inner.finish()?; + Ok(Some(inner)) + } + fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { + unreachable!("Only implemented by Signer") + } + fn mount(&mut self, _new: BoxStack<'a, C>) { + unreachable!("Only implemented by Signer") + } + fn inner_mut(&mut self) -> Option<&mut dyn Stackable<'a, C>> { + Some(self.inner.inner.get_mut()) + } + fn inner_ref(&self) -> Option<&dyn Stackable<'a, C>> { + Some(self.inner.inner.get_ref()) + } + fn cookie_set(&mut self, cookie: C) -> C { + self.inner.cookie_set(cookie) + } + fn cookie_ref(&self) -> &C { + self.inner.cookie_ref() + } + fn cookie_mut(&mut self) -> &mut C { + self.inner.cookie_mut() + } + fn position(&self) -> u64 { + self.inner.position + } +} diff --git a/openpgp/src/serialize/stream/writer/writer_deflate.rs b/openpgp/src/serialize/stream/writer/writer_deflate.rs new file mode 100644 index 00000000..387b3f2c --- /dev/null +++ b/openpgp/src/serialize/stream/writer/writer_deflate.rs @@ -0,0 +1,142 @@ +use flate2::write::{DeflateEncoder, ZlibEncoder}; +use std::fmt; +use std::io; + +use crate::Result; +use super::{Generic, Stack, BoxStack, Stackable, CompressionLevel}; + +/// ZIPing writer. +pub struct ZIP<'a, C: 'a> { + inner: Generic<DeflateEncoder<BoxStack<'a, C>>, C>, |