summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2020-03-26 17:21:32 +0100
committerJustus Winter <justus@sequoia-pgp.org>2020-03-26 17:21:32 +0100
commit89337646884b59c894329432eea960be4b3e335e (patch)
tree349136c28ee05b1ba71773c3ff8b1a0d7e2865a2
parente26c4d5a8fb582ec2a3c4c373536913aa2d0a468 (diff)
openpgp: Change packet bodies to be tristate.
- Packet bodies can now be either unprocessed (e.g. compressed, encrypted), processed (e.g. uncompressed, decrypted), or structured (e.g. parsed into packets). - Make the container types deref to Container, and container deref to packet bodies. - This cleanly avoids the confusion when serializing containers: We can serialize compressed data packets with either body, but we can only serialize encryption containers with unprocessed bodies. - Fixes #187.
-rw-r--r--openpgp/src/message/mod.rs13
-rw-r--r--openpgp/src/packet/aed.rs15
-rw-r--r--openpgp/src/packet/compressed_data.rs24
-rw-r--r--openpgp/src/packet/container.rs402
-rw-r--r--openpgp/src/packet/literal.rs2
-rw-r--r--openpgp/src/packet/mod.rs14
-rw-r--r--openpgp/src/packet/prelude.rs2
-rw-r--r--openpgp/src/packet/seip.rs15
-rw-r--r--openpgp/src/packet/unknown.rs2
-rw-r--r--openpgp/src/packet_pile.rs73
-rw-r--r--openpgp/src/parse.rs54
-rw-r--r--openpgp/src/parse/packet_pile_parser.rs10
-rw-r--r--openpgp/src/serialize/mod.rs139
13 files changed, 466 insertions, 299 deletions
diff --git a/openpgp/src/message/mod.rs b/openpgp/src/message/mod.rs
index 1e3f3e69..dce7f11a 100644
--- a/openpgp/src/message/mod.rs
+++ b/openpgp/src/message/mod.rs
@@ -386,9 +386,7 @@ impl Message {
// we treat the content as an opaque message.
path.push(0);
- if packet.children().next().is_none()
- && packet.body().is_some()
- {
+ if packet.children().is_none() {
v.push_token(Token::OpaqueContent, &path);
}
}
@@ -1003,9 +1001,9 @@ mod tests {
// 1: MDC
// => good.
let mut seip = SEIP1::new();
- seip.children_mut().push(
+ seip.children_mut().unwrap().push(
lit.clone().into());
- seip.children_mut().push(
+ seip.children_mut().unwrap().push(
MDC::from([0u8; 20]).into());
packets[1] = seip.into();
@@ -1086,7 +1084,7 @@ mod tests {
// => bad.
packets.remove(3);
packets[2].container_mut().unwrap()
- .children_mut().push(lit.clone().into());
+ .children_mut().unwrap().push(lit.clone().into());
assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>()
== [ Tag::SKESK, Tag::SKESK, Tag::SEIP ]);
@@ -1100,7 +1098,8 @@ mod tests {
// 2: SEIP
// 0: Literal
// => good.
- packets[2].container_mut().unwrap().children_mut().pop().unwrap();
+ packets[2].container_mut().unwrap()
+ .children_mut().unwrap().pop().unwrap();
#[allow(deprecated)]
packets.insert(
diff --git a/openpgp/src/packet/aed.rs b/openpgp/src/packet/aed.rs
index 60388036..1fd68274 100644
--- a/openpgp/src/packet/aed.rs
+++ b/openpgp/src/packet/aed.rs
@@ -34,6 +34,19 @@ pub struct AED1 {
container: packet::Container,
}
+impl std::ops::Deref for AED1 {
+ type Target = packet::Container;
+ fn deref(&self) -> &Self::Target {
+ &self.container
+ }
+}
+
+impl std::ops::DerefMut for AED1 {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.container
+ }
+}
+
impl PartialEq for AED1 {
fn eq(&self, other: &AED1) -> bool {
self.sym_algo == other.sym_algo
@@ -144,8 +157,6 @@ impl AED1 {
}
}
-impl_container_forwards!(AED1);
-
impl From<AED1> for Packet {
fn from(p: AED1) -> Self {
super::AED::from(p).into()
diff --git a/openpgp/src/packet/compressed_data.rs b/openpgp/src/packet/compressed_data.rs
index bf89956f..9cecf5a6 100644
--- a/openpgp/src/packet/compressed_data.rs
+++ b/openpgp/src/packet/compressed_data.rs
@@ -25,6 +25,19 @@ pub struct CompressedData {
container: packet::Container,
}
+impl std::ops::Deref for CompressedData {
+ type Target = packet::Container;
+ fn deref(&self) -> &Self::Target {
+ &self.container
+ }
+}
+
+impl std::ops::DerefMut for CompressedData {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.container
+ }
+}
+
impl PartialEq for CompressedData {
fn eq(&self, other: &CompressedData) -> bool {
self.algo == other.algo
@@ -45,10 +58,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()
}
}
@@ -76,7 +86,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,13 +96,11 @@ 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)
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(),
+ }
+ }
+
/// Returns a reference to this Packet's children.
- pub fn children_ref(&self) -> &[Packet] {
- &self.packets
+ ///
+ /// Returns `None` if the body is not structured.
+ pub fn children_ref(&self) -> Option<&[Packet]> {
+ if let Body::Structured(packets) = &self.body {
+ Some(&packets[..])
+ } else {
+ None
+ }
}
/// Returns a mutable reference to this Packet's children.
- pub fn children_mut(&mut self) -> &mut Vec<Packet> {
- &mut self.packets
+ ///
+ /// Returns `None` if the body is not structured.
+ pub fn children_mut(&mut self) -> Option<&mut Vec<Packet>> {
+ if let Body::Structured(packets) = &mut self.body {
+ Some(packets)
+ } else {
+ None
+ }
}
/// Returns an iterator over the packet's descendants. The
/// descendants are visited in depth-first order.
- pub fn descendants(&self) -> Iter {
- return Iter {
+ ///
+ /// Returns `None` if the body is not structured.
+ pub fn descendants(&self) -> Option<Iter> {
+ Some(Iter {
// Iterate over each packet in the message.
- children: self.children(),
+ children: self.children()?,
child: None,
grandchildren: None,
depth: 0,
- };
+ })
}
/// Returns an iterator over the packet's immediate children.
- pub fn children<'a>(&'a self) -> slice::Iter<'a, Packet> {
- self.packets.iter()
+ ///
+ /// Returns `None` if the body is not structured.
+ pub fn children<'a>(&'a self) -> Option<slice::Iter<'a, Packet>> {
+ Some(self.children_ref()?.iter())
}
/// Returns an `IntoIter` over the packet's immediate children.
- pub fn into_children(self) -> vec::IntoIter<Packet> {
- self.packets.into_iter()
+ ///
+ /// Returns `None` if the body is not structured.
+ pub fn into_children(self) -> Option<vec::IntoIter<Packet>> {
+ if let Body::Structured(packets) = self.body {
+ Some(packets.into_iter())
+ } else {
+ None
+ }
}
- /// Retrieves the packet's body.
- ///
- /// Packets can store a sequence of bytes as body, e.g. if the
- /// maximum recursion level is reached while parsing a sequence of
- /// packets, the container's body is stored as is.
- pub fn body(&self) -> &[u8] {
+ /// Gets the packet's body.
+ pub fn body(&self) -> &Body {
&self.body
}
/// Sets the packet's body.
- ///
- /// Setting the body clears the old body, or any of the packet's
- /// descendants.
- pub fn set_body(&mut self, data: Vec<u8>) -> Vec<u8> {
- self.packets.clear();
+ pub fn set_body(&mut self, body: Body) -> Body {
+ use Body::*;
let mut h = Self::make_body_hash();
- h.update(&data);
+ match &body {
+ Unprocessed(bytes) => h.update(bytes),
+ Processed(bytes) => h.update(bytes),
+ Structured(_) => (),
+ }
self.set_body_hash(h);
- std::mem::replace(&mut self.body, data)
+ std::mem::replace(&mut self.body, body)
}
/// Returns the hash for the empty body.
@@ -250,40 +327,18 @@ impl Container {
//
// `indent` is the number of spaces to indent the output.
pub(crate) fn pretty_print(&self, indent: usize) {
- for (i, p) in self.packets.iter().enumerate() {
+ for (i, p) in self.children_ref().iter().enumerate() {
eprintln!("{}{}: {:?}",
Self::indent(indent), i + 1, p);
- if let Some(ref children) = self.packets[i].container_ref() {
+ if let Some(ref children) = self.children_ref()
+ .and_then(|c| c.get(i)).and_then(|p| p.container_ref())
+ {
children.pretty_print(indent + 1);
}
}
}
}
-macro_rules! the_common_container_forwards {
- () => {
- /// Returns a reference to the container.
- pub(crate) fn container_ref(&self) -> &packet::Container {
- &self.container
- }
-
- /// Returns a mutable reference to the container.
- pub(crate) fn container_mut(&mut self) -> &mut packet::Container {
- &mut self.container
- }
-
- /// Gets a reference to the this packet's body.
- pub fn body(&self) -> &[u8] {
- self.container.body()
- }
-
- /// Sets the this packet's body.
- pub fn set_body(&mut self, data: Vec<u8>) -> Vec<u8> {
- self.container.set_body(data)
- }
- };
-}
-
macro_rules! impl_body_forwards {
($typ:ident) => {
/// This packet implements the unprocessed container
@@ -292,39 +347,38 @@ macro_rules! impl_body_forwards {
/// Container packets like this one can contain unprocessed
/// data.
impl $typ {
- the_common_container_forwards!();
- }
- };
-}
-
-macro_rules! impl_container_forwards {
- ($typ:ident) => {
- /// This packet implements the container interface.
- ///
- /// Container packets can contain other packets, unprocessed
- /// data, or both.
- impl $typ {
- the_common_container_forwards!();
-
- /// Returns a reference to this Packet's children.
- pub fn children_ref(&self) -> &[Packet] {
- self.container.children_ref()
+ /// Returns a reference to the container.
+ pub(crate) fn container_ref(&self) -> &packet::Container {
+ &self.container
}
- /// Returns a mutable reference to this Packet's children.
- pub fn children_mut(&mut self) -> &mut Vec<Packet> {
- self.container.children_mut()
+ /// Returns a mutable reference to the container.
+ pub(crate) fn container_mut(&mut self) -> &mut packet::Container {
+ &mut self.container
}
- /// Returns an iterator over the packet's immediate children.
- pub fn children<'a>(&'a self) -> impl Iterator<Item = &'a Packet> {
- self.container.children()
+ /// Gets a reference to the this packet's body.
+ pub fn body(&self) -> &[u8] {
+ use crate::packet::Body::*;
+ match self.container.body() {
+ Unprocessed(bytes) => bytes,
+ Processed(_) => unreachable!(
+ "Unprocessed container has processed body"),
+ Structured(_) => unreachable!(
+ "Unprocessed container has structured body"),
+ }
}
- /// Returns an iterator over all of the packet's descendants, in
- /// depth-first order.
- pub fn descendants(&self) -> super::Iter {
- self.container.descendants()
+ /// Sets the this packet's body.
+ pub fn set_body(&mut self, data: Vec<u8>) -> Vec<u8> {
+ use crate::packet::{Body, Body::*};
+ match self.container.set_body(Body::Unprocessed(data)) {
+ Unprocessed(bytes) => bytes,
+ Processed(_) => unreachable!(
+ "Unprocessed container has processed body"),
+ Structured(_) => unreachable!(
+ "Unprocessed container has structured body"),
+ }
}
}
};
@@ -333,22 +387,24 @@ macro_rules! impl_container_forwards {
impl Packet {
pub(crate) // for packet_pile.rs
fn container_ref(&self) -> Option<&Container> {
+ use std::ops::Deref;
match self {
- Packet::CompressedData(p) => Some(p.container_ref()),
- Packet::SEIP(p) => Some(p.container_ref()),
- Packet::AED(p) => Some(p.container_ref()),
+ Packet::CompressedData(p) => Some(p.deref()),
+ Packet::SEIP(p) => Some(p.deref()),
+ Packet::AED(p) => Some(p.deref()),
Packet::Literal(p) => Some(p.container_ref()),
Packet::Unknown(p) => Some(p.container_ref()),
_ => None,
}
}
- pub(crate) // for packet_pile.rs
+ pub(crate) // for packet_pile.rs, packet_pile_parser.rs, parse.rs
fn container_mut(&mut self) -> Option<&mut Container> {
+ use std::ops::DerefMut;
match self {
- Packet::CompressedData(p) => Some(p.container_mut()),
- Packet::SEIP(p) => Some(p.container_mut()),
- Packet::AED(p) => Some(p.container_mut()),
+ Packet::CompressedData(p) => Some(p.deref_mut()),
+ Packet::SEIP(p) => Some(p.deref_mut()),
+ Packet::AED(p) => Some(p.deref_mut()),
Packet::Literal(p) => Some(p.container_mut()),
Packet::Unknown(p) => Some(p.container_mut()),
_ => None,
@@ -356,22 +412,32 @@ impl Packet {
}
/// Returns an iterator over the packet's immediate children.
- pub(crate) fn children<'a>(&'a self) -> impl Iterator<Item = &'a Packet> {
- self.container_ref().map(|c| c.children()).unwrap_or_else(|| [].iter())
+ pub(crate) fn children<'a>(&'a self)
+ -> Option<impl Iterator<Item = &'a Packet>> {
+ self.container_ref().and_then(|c| c.children())
}
/// Returns an iterator over all of the packet's descendants, in
/// depth-first order.
- pub(crate) fn descendants(&self) -> Iter {
- self.container_ref().map(|c| c.descendants()).unwrap_or_default()
+ pub(crate) fn descendants(&self) -> Option<Iter> {
+ self.container_ref().and_then(|c| c.descendants())
}
- /// Re