summaryrefslogtreecommitdiffstats
path: root/openpgp/src/packet
diff options
context:
space:
mode:
Diffstat (limited to 'openpgp/src/packet')
-rw-r--r--openpgp/src/packet/aed.rs166
-rw-r--r--openpgp/src/packet/compressed_data.rs58
-rw-r--r--openpgp/src/packet/container.rs402
-rw-r--r--openpgp/src/packet/header/ctb.rs91
-rw-r--r--openpgp/src/packet/header/mod.rs60
-rw-r--r--openpgp/src/packet/key.rs (renamed from openpgp/src/packet/key/mod.rs)1530
-rw-r--r--openpgp/src/packet/key/conversions.rs430
-rw-r--r--openpgp/src/packet/literal.rs39
-rw-r--r--openpgp/src/packet/marker.rs19
-rw-r--r--openpgp/src/packet/mdc.rs9
-rw-r--r--openpgp/src/packet/mod.rs1250
-rw-r--r--openpgp/src/packet/one_pass_sig.rs46
-rw-r--r--openpgp/src/packet/pkesk.rs119
-rw-r--r--openpgp/src/packet/prelude.rs70
-rw-r--r--openpgp/src/packet/seip.rs40
-rw-r--r--openpgp/src/packet/signature/mod.rs300
-rw-r--r--openpgp/src/packet/signature/subpacket.rs190
-rw-r--r--openpgp/src/packet/skesk.rs43
-rw-r--r--openpgp/src/packet/tag.rs16
-rw-r--r--openpgp/src/packet/trust.rs25
-rw-r--r--openpgp/src/packet/unknown.rs11
-rw-r--r--openpgp/src/packet/user_attribute.rs37
-rw-r--r--openpgp/src/packet/userid/mod.rs44
23 files changed, 3541 insertions, 1454 deletions
diff --git a/openpgp/src/packet/aed.rs b/openpgp/src/packet/aed.rs
index 8af94f00..b7f6e58c 100644
--- a/openpgp/src/packet/aed.rs
+++ b/openpgp/src/packet/aed.rs
@@ -1,4 +1,77 @@
//! AEAD encrypted data packets.
+//!
+//! An encryption container using [Authenticated Encryption with
+//! Additional Data].
+//!
+//! The AED packet is a new packet specified in [Section 5.16 of RFC
+//! 4880bis]. Its aim is to replace the [SEIP packet], whose security
+//! has been partially compromised. SEIP's weaknesses includes its
+//! use of CFB mode (e.g., EFAIL-style CFB gadgets, see Section 5.3 of
+//! the [EFAIL paper]), its use of [SHA-1] for integrity protection, and
+//! the ability to [downgrade SEIP packets] to much weaker SED
+//! packets.
+//!
+//! Although the decision to use AEAD is uncontroversial, the design
+//! specified in RFC 4880bis is. According to [RFC 5116], decrypted
+//! AEAD data can only be released for processing after its
+//! authenticity has been checked:
+//!
+//! > [The authenticated decryption operation] has only a single
+//! > output, either a plaintext value P or a special symbol FAIL that
+//! > indicates that the inputs are not authentic.
+//!
+//! The controversy has to do with streaming, which OpenPGP has
+//! traditionally supported. Streaming a message means that the
+//! amount of data that needs to be buffered when processing a message
+//! is independent of the message's length.
+//!
+//! At first glance, the AEAD mechanism in RFC 4880bis appears to
+//! support this mode of operation: instead of encrypting the whole
+//! message using AEAD, which would require buffering all of the
+//! plaintext when decrypting the message, the message is chunked, the
+//! individual chunks are linked together, and AEAD is used to encrypt
+//! and protect each individual chunk. Because the plaintext from an
+//! individual chunk can be integrity checked, an implementation only
+//! needs to buffer a chunk worth of data.
+//!
+//! Unfortunately, RFC 4880bis allows chunk sizes that are, in
+//! practice, unbounded. Specifically, a chunk can be up to 4
+//! exbibytes in size. Thus when encountering messages that can't be
+//! buffered, an OpenPGP implementation has a choice: it can either
+//! release data that has not been integrity checked and violate RFC
+//! 5116, or it can fail to process the message. As of 2020, [GnuPG]
+//! and [RNP] process unauthenticated plaintext. From a user
+//! perspective, it then appears that implementations that choose to
+//! follow RFC 5116 are impaired: "GnuPG can decrypt it," they think,
+//! "why can't Sequoia?" This creates pressure on other
+//! implementations to also behave insecurely.
+//!
+//! [Werner argues] that AEAD is not about authenticating the data.
+//! That is the purpose of the signature. The reason to introduce
+//! AEAD is to get the benefits of more modern cryptography, and to be
+//! able to more quickly detect rare transmission errors. Our
+//! position is that an integrity check provides real protection: it
+//! can detect modified ciphertext. And, if we are going to stream,
+//! then this protection is essential as it protects the user from
+//! real, demonstrated attacks like [EFAIL].
+//!
+//! RFC 4880bis has not been finalized. So, it is still possible that
+//! the AEAD mechanism will change (which is why the AED packet is
+//! marked as experimental). Despite our concerns, because other
+//! OpenPGP implementations already emit the AEAD packet, we provide
+//! *experimental* support for it in Sequoia.
+//!
+//! [Authenticated Encryption with Additional Data]: https://en.wikipedia.org/wiki/Authenticated_encryption
+//! [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.16
+//! [EFAIL paper]: https://www.usenix.org/conference/usenixsecurity18/presentation/poddebniak
+//! [SHA-1]: https://sha-mbles.github.io/
+//! [SEIP packet]: https://tools.ietf.org/html/rfc4880#section-5.13
+//! [RFC 5116]: https://tools.ietf.org/html/rfc5116#section-2.2
+//! [downgrade SEIP packets]: https://mailarchive.ietf.org/arch/msg/openpgp/JLn7sL6TqikUf-cD34lN7kof7_A/
+//! [GnuPG]: https://mailarchive.ietf.org/arch/msg/openpgp/fmQgRm94jhvPLEOi0J-o7A8LpkY/
+//! [RNP]: https://github.com/rnpgp/rnp/issues/807
+//! [Werner argues]: https://mailarchive.ietf.org/arch/msg/openpgp/J428Mqq3-pHTU4C76EgP5sPkvtA
+//! [EFAIL]: https://efail.de/
use crate::types::{
AEADAlgorithm,
@@ -11,13 +84,31 @@ use crate::Result;
/// Holds an AEAD encrypted data packet.
///
-/// An AEAD encrypted data packet is a container. See [Section 5.16
-/// of RFC 4880bis] for details.
+/// An AEAD encrypted data packet holds encrypted data. The data
+/// contains additional OpenPGP packets. See [Section 5.16 of RFC
+/// 4880bis] for details.
+///
+/// An AED packet is not normally instantiated directly. In most
+/// cases, you'll create one as a side-effect of encrypting a message
+/// using the [streaming serializer], or parsing an encrypted message
+/// using the [`PacketParser`].
+///
+/// This feature is
+/// [experimental](../../index.html#experimental-features). It has
+/// not been standardized and we advise users to not emit AED packets.
///
/// [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.16
+/// [streaming serializer]: ../serialize/stream/index.html
+/// [`PacketParser`]: ../parse/index.html
+///
+/// # A note on equality
///
-/// This feature is [experimental](../../index.html#experimental-features).
-#[derive(Clone, Debug)]
+/// An unprocessed (encrypted) `AED` packet is never considered equal
+/// to a processed (decrypted) one. Likewise, a processed (decrypted)
+/// packet is never considered equal to a structured (parsed) one.
+// IMPORTANT: If you add fields to this struct, you need to explicitly
+// IMPORTANT: implement PartialEq, Eq, and Hash.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AED1 {
/// CTB packet header fields.
pub(crate) common: packet::Common,
@@ -26,7 +117,7 @@ pub struct AED1 {
/// AEAD algorithm.
aead: AEADAlgorithm,
/// Chunk size.
- chunk_size: usize,
+ chunk_size: u64,
/// Initialization vector for the AEAD algorithm.
iv: Box<[u8]>,
@@ -34,25 +125,16 @@ pub struct AED1 {
container: packet::Container,
}
-impl PartialEq for AED1 {
- fn eq(&self, other: &AED1) -> bool {
- self.sym_algo == other.sym_algo
- && self.aead == other.aead
- && self.chunk_size == other.chunk_size
- && self.iv == other.iv
- && self.container == other.container
+impl std::ops::Deref for AED1 {
+ type Target = packet::Container;
+ fn deref(&self) -> &Self::Target {
+ &self.container
}
}
-impl Eq for AED1 {}
-
-impl std::hash::Hash for AED1 {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- std::hash::Hash::hash(&self.sym_algo, state);
- std::hash::Hash::hash(&self.aead, state);
- std::hash::Hash::hash(&self.chunk_size, state);
- std::hash::Hash::hash(&self.iv, state);
- std::hash::Hash::hash(&self.container, state);
+impl std::ops::DerefMut for AED1 {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.container
}
}
@@ -60,7 +142,7 @@ impl AED1 {
/// Creates a new AED1 object.
pub fn new(sym_algo: SymmetricAlgorithm,
aead: AEADAlgorithm,
- chunk_size: usize,
+ chunk_size: u64,
iv: Box<[u8]>) -> Result<Self> {
if chunk_size.count_ones() != 1 {
return Err(Error::InvalidArgument(
@@ -76,10 +158,10 @@ impl AED1 {
Ok(AED1 {
common: Default::default(),
- sym_algo: sym_algo,
- aead: aead,
- chunk_size: chunk_size,
- iv: iv,
+ sym_algo,
+ aead,
+ chunk_size,
+ iv,
container: Default::default(),
})
}
@@ -89,7 +171,7 @@ impl AED1 {
self.sym_algo
}
- /// Sets the sym_algo algorithm.
+ /// Sets the symmetric algorithm.
pub fn set_symmetric_algo(&mut self, sym_algo: SymmetricAlgorithm)
-> SymmetricAlgorithm {
::std::mem::replace(&mut self.sym_algo, sym_algo)
@@ -106,12 +188,12 @@ impl AED1 {
}
/// Gets the chunk size.
- pub fn chunk_size(&self) -> usize {
+ pub fn chunk_size(&self) -> u64 {
self.chunk_size
}
- /// Gets the chunk size.
- pub fn set_chunk_size(&mut self, chunk_size: usize) -> Result<()> {
+ /// Sets the chunk size.
+ pub fn set_chunk_size(&mut self, chunk_size: u64) -> Result<()> {
if chunk_size.count_ones() != 1 {
return Err(Error::InvalidArgument(
format!("chunk size is not a power of two: {}", chunk_size))
@@ -128,9 +210,9 @@ impl AED1 {
Ok(())
}
- /// Gets the size of a chunk with digest.
- pub fn chunk_digest_size(&self) -> Result<usize> {
- Ok(self.chunk_size + self.aead.digest_size()?)
+ /// Gets the size of a chunk with a digest.
+ pub fn chunk_digest_size(&self) -> Result<u64> {
+ Ok(self.chunk_size + self.aead.digest_size()? as u64)
}
/// Gets the initialization vector for the AEAD algorithm.
@@ -144,8 +226,6 @@ impl AED1 {
}
}
-impl_container_forwards!(AED1);
-
impl From<AED1> for Packet {
fn from(p: AED1) -> Self {
super::AED::from(p).into()
@@ -157,19 +237,3 @@ impl From<AED1> for super::AED {
super::AED::V1(p)
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn deref() {
- let mut s = AED1::new(SymmetricAlgorithm::AES128,
- AEADAlgorithm::EAX,
- 64,
- vec![].into_boxed_slice()).unwrap();
- assert_eq!(s.body(), &[]);
- s.set_body(vec![0, 1, 2]);
- assert_eq!(s.body(), &[0, 1, 2]);
- }
-}
diff --git a/openpgp/src/packet/compressed_data.rs b/openpgp/src/packet/compressed_data.rs
index bf89956f..4f379496 100644
--- a/openpgp/src/packet/compressed_data.rs
+++ b/openpgp/src/packet/compressed_data.rs
@@ -1,5 +1,8 @@
use std::fmt;
+#[cfg(any(test, feature = "quickcheck"))]
+use quickcheck::{Arbitrary, Gen};
+
use crate::packet;
use crate::Packet;
use crate::types::CompressionAlgorithm;
@@ -14,7 +17,9 @@ use crate::types::CompressionAlgorithm;
/// of a `CompressedData` packet.
///
/// [Section 5.6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.6
-#[derive(Clone)]
+// IMPORTANT: If you add fields to this struct, you need to explicitly
+// IMPORTANT: implement PartialEq, Eq, and Hash.
+#[derive(Clone, PartialEq, Eq, Hash)]
pub struct CompressedData {
/// CTB packet header fields.
pub(crate) common: packet::Common,
@@ -25,19 +30,16 @@ pub struct CompressedData {
container: packet::Container,
}
-impl PartialEq for CompressedData {
- fn eq(&self, other: &CompressedData) -> bool {
- self.algo == other.algo
- && self.container == other.container
+impl std::ops::Deref for CompressedData {
+ type Target = packet::Container;
+ fn deref(&self) -> &Self::Target {
+ &self.container
}
}
-impl Eq for CompressedData {}
-
-impl std::hash::Hash for CompressedData {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- std::hash::Hash::hash(&self.algo, state);
- std::hash::Hash::hash(&self.container, state);
+impl std::ops::DerefMut for CompressedData {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.container
}
}
@@ -45,10 +47,7 @@ impl fmt::Debug for CompressedData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("CompressedData")
.field("algo", &self.algo)
- .field("children", &self.container.children_ref())
- .field("body (bytes)",
- &self.container.body().len())
- .field("body_digest", &self.container.body_digest())
+ .field("container", &self.container)
.finish()
}
}
@@ -58,7 +57,7 @@ impl CompressedData {
pub fn new(algo: CompressionAlgorithm) -> Self {
CompressedData {
common: Default::default(),
- algo: algo,
+ algo,
container: Default::default(),
}
}
@@ -76,7 +75,7 @@ impl CompressedData {
/// Adds a new packet to the container.
#[cfg(test)]
pub fn push(mut self, packet: Packet) -> Self {
- self.container.children_mut().push(packet);
+ self.container.children_mut().unwrap().push(packet);
self
}
@@ -86,15 +85,34 @@ impl CompressedData {
/// packet, etc.
#[cfg(test)]
pub fn insert(mut self, i: usize, packet: Packet) -> Self {
- self.container.children_mut().insert(i, packet);
+ self.container.children_mut().unwrap().insert(i, packet);
self
}
}
-impl_container_forwards!(CompressedData);
-
impl From<CompressedData> for Packet {
fn from(s: CompressedData) -> Self {
Packet::CompressedData(s)
}
}
+
+#[cfg(any(test, feature = "quickcheck"))]
+impl Arbitrary for CompressedData {
+ fn arbitrary<G: Gen>(g: &mut G) -> Self {
+ use rand::Rng;
+ use crate::serialize::SerializeInto;
+ loop {
+ let a = CompressionAlgorithm::from(g.gen_range(0, 4));
+ if a.is_supported() {
+ let mut c = CompressedData::new(a);
+ // We arbitrarily chose to create packets with
+ // processed bodies, so that
+ // Packet::from_bytes(c.to_vec()) will roundtrip them.
+ c.set_body(packet::Body::Processed(
+ Packet::arbitrary(g).to_vec().unwrap()
+ ));
+ return c;
+ }
+ }
+ }
+}
diff --git a/openpgp/src/packet/container.rs b/openpgp/src/packet/container.rs
index 185659bb..2cc6c655 100644
--- a/openpgp/src/packet/container.rs
+++ b/openpgp/src/packet/container.rs
@@ -15,17 +15,75 @@ use crate::{
types::HashAlgorithm,
};
-/// Holds zero or more OpenPGP packets.
+/// A packet's body holds either unprocessed bytes, processed bytes,
+/// or packets.
///
-/// This is used by OpenPGP container packets, like the compressed
-/// data packet, to store the containing packets.
+/// We conceptually divide packets into two parts: the header and the
+/// body. Whereas the header is read eagerly when the packet is
+/// deserialized, the body is only read on demand.
+///
+/// A packet's body is stored here either when configured via
+/// [`PacketParserBuilder::buffer_unread_content`], when one of the
+/// [`PacketPile`] deserialization routines is used, or on demand for
+/// a particular packet using the
+/// [`PacketParser::buffer_unread_content`] method.
+///
+/// [`PacketParserBuilder::buffer_unread_content`]: ../parse/struct.PacketParserBuilder.html#method.buffer_unread_content
+/// [`PacketPile`]: ../struct.PacketPile.html
+/// [`PacketParser::buffer_unread_content`]: ../parse/struct.PacketParser.html#method.buffer_unread_content
+///
+/// There are three different types of packets:
+///
+/// - Packets like the [`UserID`] and [`Signature`] packets, don't
+/// actually have a body.
+///
+/// [`UserID`]: ../packet/struct.UserID.html
+/// [`Signature`]: ../packet/signature/struct.Signature.html
+///
+/// - One packet, the literal data packet, includes unstructured
+/// data. That data is stored in [`Literal`].
+///
+/// [`Literal`]: ../packet/struct.Literal.html
+///
+/// - Some packets are containers. If the parser does not parse the
+/// packet's child, either because the caller used
+/// [`PacketParser::next`] to get the next packet, or the maximum
+/// recursion depth was reached, then the packets can be stored here
+/// as a byte stream. (If the caller so chooses, the content can be
+/// parsed later using the regular deserialization routines, since
+/// the content is just an OpenPGP message.)
+///
+/// [`PacketParser::next`]: ../parse/struct.PacketParser.html#method.next
#[derive(Clone)]
-pub(crate) struct Container {
+pub enum Body {
+ /// Unprocessed packet body.
+ ///
+ /// The body has not been processed, i.e. it is still encrypted.
+ ///
+ /// Note: if some of a packet's data is streamed, and the
+ /// `PacketParser` is configured to buffer unread content, then
+ /// this is not the packet's entire content; it is just the unread
+ /// content.
+ Unprocessed(Vec<u8>),
+
+ /// Processed packed body.
+ ///
+ /// The body has been processed, i.e. decompressed or decrypted,
+ /// but not parsed into packets.
+ ///
+ /// Note: if some of a packet's data is streamed, and the
+ /// `PacketParser` is configured to buffer unread content, then
+ /// this is not the packet's entire content; it is just the unread
+ /// content.
+ Processed(Vec<u8>),
+
+ /// Parsed packet body.
+ ///
/// Used by container packets (such as the encryption and
/// compression packets) to reference their immediate children.
/// This results in a tree structure.
///
- /// This is automatically populated when using the `PacketPile`
+ /// This is automatically populated when using the [`PacketPile`]
/// deserialization routines, e.g., [`PacketPile::from_file`]. By
/// default, it is *not* automatically filled in by the
/// [`PacketParser`] deserialization routines; this needs to be
@@ -34,59 +92,29 @@ pub(crate) struct Container {
/// [`PacketPile`]: ../struct.PacketPile.html
/// [`PacketPile::from_file`]: ../struct.PacketPile.html#method.from_file
/// [`PacketParser`]: ../parse/struct.PacketParser.html
- pub(crate) packets: Vec<Packet>,
+ Structured(Vec<Packet>),
+}
+/// Holds packet bodies.
+///
+/// This is used by OpenPGP container packets, like the compressed
+/// data packet, to store the containing packets.
+#[derive(Clone)]
+pub struct Container {
/// Holds a packet's body.
- ///
- /// We conceptually divide packets into two parts: the header and
- /// the body. Whereas the header is read eagerly when the packet
- /// is deserialized, the body is only read on demand.
- ///
- /// A packet's body is stored here either when configured via
- /// [`PacketParserBuilder::buffer_unread_content`], when one of
- /// the [`PacketPile`] deserialization routines is used, or on demand
- /// for a particular packet using the
- /// [`PacketParser::buffer_unread_content`] method.
- ///
- /// [`PacketParserBuilder::buffer_unread_content`]: ../parse/struct.PacketParserBuilder.html#method.buffer_unread_content
- /// [`PacketPile`]: ../struct.PacketPile.html
- /// [`PacketParser::buffer_unread_content`]: ../parse/struct.PacketParser.html#method.buffer_unread_content
- ///
- /// There are three different types of packets:
- ///
- /// - Packets like the [`UserID`] and [`Signature`] packets,
- /// don't actually have a body. These packets don't use this
- /// field.
- ///
- /// [`UserID`]: ../packet/struct.UserID.html
- /// [`Signature`]: ../packet/signature/struct.Signature.html
- ///
- /// - One packet, the literal data packet, includes unstructured
- /// data. That data is stored in [`Literal`].
- ///
- /// [`Literal`]: ../packet/struct.Literal.html
- ///
- /// - Some packets are containers. If the parser does not parse
- /// the packet's child, either because the caller used
- /// [`PacketParser::next`] to get the next packet, or the
- /// maximum recursion depth was reached, then the packets can
- /// be stored here as a byte stream. (If the caller so
- /// chooses, the content can be parsed later using the regular
- /// deserialization routines, since the content is just an
- /// OpenPGP message.)
- ///
- /// [`PacketParser::next`]: ../parse/struct.PacketParser.html#method.next
- ///
- /// Note: if some of a packet's data is processed, and the
- /// `PacketParser` is configured to buffer unread content, then
- /// this is not the packet's entire content; it is just the unread
- /// content.
- body: Vec<u8>,
+ body: Body,
/// We compute a digest over the body to implement comparison.
body_digest: Vec<u8>,
}
+impl std::ops::Deref for Container {
+ type Target = Body;
+ fn deref(&self) -> &Self::Target {
+ &self.body
+ }
+}
+
// Pick the fastest hash function from the SHA2 family for the
// architectures word size. On 64-bit architectures, SHA512 is almost
// twice as fast, but on 32-bit ones, SHA256 is faster.
@@ -97,8 +125,16 @@ const CONTAINER_BODY_HASH: HashAlgorithm = HashAlgorithm::SHA256;
impl PartialEq for Container {
fn eq(&self, other: &Container) -> bool {
- self.packets == other.packets
- && self.body_digest == other.body_digest
+ use Body::*;
+ match (&self.body, &other.body) {
+ (Unprocessed(_), Unprocessed(_)) =>
+ self.body_digest == other.body_digest,
+ (Processed(_), Processed(_)) =>
+ self.body_digest == other.body_digest,
+ (Structured(a), Structured(b)) =>
+ a == b,
+ _ => false,
+ }
}
}
@@ -106,17 +142,19 @@ impl Eq for Container {}
impl Hash for Container {
fn hash<H: Hasher>(&self, state: &mut H) {
- self.packets.hash(state);
- self.body_digest.hash(state);
+ if let Body::Structured(packets) = &self.body {
+ packets.hash(state);
+ } else {
+ self.body_digest.hash(state);
+ }
}
}
impl Default for Container {
fn default() -> Self {
Self {
- packets: Vec::with_capacity(0),
- body: Vec::with_capacity(0),
- body_digest: Self::empty_body_digest(),
+ body: Body::Structured(Vec::with_capacity(0)),
+ body_digest: Vec::with_capacity(0),
}
}
}
@@ -124,83 +162,122 @@ impl Default for Container {
impl From<Vec<Packet>> for Container {
fn from(packets: Vec<Packet>) -> Self {
Self {
- packets,
- body: Vec::with_capacity(0),
- body_digest: Self::empty_body_digest(),
+ body: Body::Structured(packets),
+ body_digest: Vec::with_capacity(0),
}
}
}
impl fmt::Debug for Container {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let threshold = 16;
- let prefix = &self.body[..std::cmp::min(threshold, self.body.len())];
- let mut prefix_fmt = crate::fmt::hex::encode(prefix);
- if self.body.len() > threshold {
- prefix_fmt.push_str("...");
+ fn fmt_bytes(f: &mut fmt::Formatter, tag: &str, bytes: &[u8],
+ digest: String)
+ -> fmt::Result
+ {
+ let threshold = 16;
+ let prefix = &bytes[..std::cmp::min(threshold, bytes.len())];
+ let mut prefix_fmt = crate::fmt::hex::encode(prefix);
+ if bytes.len() > threshold {
+ prefix_fmt.push_str("...");
+ }
+ prefix_fmt.push_str(&format!(" ({} bytes)", bytes.len())[..]);
+
+ f.debug_struct("Container")
+ .field(tag, &prefix_fmt)
+ .field("digest", &digest)
+ .finish()
}
- prefix_fmt.push_str(&format!(" ({} bytes)", self.body.len())[..]);
- f.debug_struct("Container")
- .field("packets", &self.packets)
- .field("body", &prefix_fmt)
- .field("body_digest", &self.body_digest())
- .finish()
+ use Body::*;
+ match &self.body {
+ Unprocessed(bytes) =>
+ fmt_bytes(f, "unprocessed", bytes, self.body_digest()),
+ Processed(bytes) =>
+ fmt_bytes(f, "processed", bytes, self.body_digest()),
+ Structured(packets) =>
+ f.debug_struct("Container").field("packets", packets).finish(),
+ }
}
}
impl Container {
+ pub(crate) fn default_unprocessed() -> Self {
+ Self {
+ body: Body::Unprocessed(Vec::with_capacity(0)),
+ body_digest: Self::empty_body_digest(),
+ }
+ }
+