sequoia_openpgp/packet/header/
ctb.rs

1//! Cipher Type Byte (CTB).
2//!
3//! The CTB encodes the packet's type and some length information.  It
4//! has two variants: the so-called old format and the so-called new
5//! format.  See [Section 4.2 of RFC 9580] for more details.
6//!
7//!   [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2
8
9use std::convert::TryFrom;
10
11use crate::{
12    packet::Tag,
13    Error,
14    Result
15};
16use crate::packet::header::BodyLength;
17
18/// Data common to all CTB formats.
19///
20/// OpenPGP defines two packet formats: an old format and a new
21/// format.  They both include the packet's so-called tag.
22///
23/// See [Section 4.2 of RFC 9580] for more details.
24///
25///   [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2
26#[derive(Clone, Debug)]
27struct CTBCommon {
28    /// RFC4880 Packet tag
29    tag: Tag,
30}
31
32/// A CTB using the new format encoding.
33///
34/// See [Section 4.2 of RFC 9580] for more details.
35///
36///   [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2
37#[derive(Clone, Debug)]
38pub struct CTBNew {
39    /// Packet CTB fields
40    common: CTBCommon,
41}
42assert_send_and_sync!(CTBNew);
43
44impl CTBNew {
45    /// Constructs a new-style CTB.
46    pub fn new(tag: Tag) -> Self {
47        CTBNew {
48            common: CTBCommon {
49                tag,
50            },
51        }
52    }
53
54    /// Returns the packet's tag.
55    pub fn tag(&self) -> Tag {
56        self.common.tag
57    }
58}
59
60/// The length encoded for an old style CTB.
61///
62/// The `PacketLengthType` is only part of the [old CTB], and is
63/// partially used to determine the packet's size.
64///
65/// See [Section 4.2.2 of RFC 9580] for more details.
66///
67///   [old CTB]: CTBOld
68///   [Section 4.2.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.2
69#[derive(Debug)]
70#[derive(Clone, Copy, PartialEq)]
71pub enum PacketLengthType {
72    /// A one-octet Body Length header encodes a length of 0 to 191 octets.
73    ///
74    /// The header is 2 octets long.  It contains the one byte CTB
75    /// followed by the one octet length.
76    OneOctet,
77    /// A two-octet Body Length header encodes a length of 192 to 8383 octets.
78    ///
79    /// The header is 3 octets long.  It contains the one byte CTB
80    /// followed by the two octet length.
81    TwoOctets,
82    /// A four-octet Body Length.
83    ///
84    /// The header is 5 octets long.  It contains the one byte CTB
85    /// followed by the four octet length.
86    FourOctets,
87    /// The packet is of indeterminate length.
88    ///
89    /// Neither the packet header nor the packet itself contain any
90    /// information about the length.  The end of the packet is clear
91    /// from the context, e.g., EOF.
92    Indeterminate,
93}
94assert_send_and_sync!(PacketLengthType);
95
96impl TryFrom<u8> for PacketLengthType {
97    type Error = anyhow::Error;
98
99    fn try_from(u: u8) -> Result<Self> {
100        match u {
101            0 => Ok(PacketLengthType::OneOctet),
102            1 => Ok(PacketLengthType::TwoOctets),
103            2 => Ok(PacketLengthType::FourOctets),
104            3 => Ok(PacketLengthType::Indeterminate),
105            _ => Err(Error::InvalidArgument(
106                format!("Invalid packet length: {}", u)).into()),
107        }
108    }
109}
110
111impl From<PacketLengthType> for u8 {
112    fn from(l: PacketLengthType) -> Self {
113        match l {
114            PacketLengthType::OneOctet => 0,
115            PacketLengthType::TwoOctets => 1,
116            PacketLengthType::FourOctets => 2,
117            PacketLengthType::Indeterminate => 3,
118        }
119    }
120}
121
122/// A CTB using the old format encoding.
123///
124/// See [Section 4.2 of RFC 9580] for more details.
125///
126///   [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2
127#[derive(Clone, Debug)]
128pub struct CTBOld {
129    /// Common CTB fields.
130    common: CTBCommon,
131    /// Type of length specifier.
132    length_type: PacketLengthType,
133}
134assert_send_and_sync!(CTBOld);
135
136impl CTBOld {
137    /// Constructs an old-style CTB.
138    ///
139    /// # Errors
140    ///
141    /// Returns [`crate::Error::InvalidArgument`] if the tag or the
142    /// body length cannot be expressed using an old-style CTB.
143    pub fn new(tag: Tag, length: BodyLength) -> Result<Self> {
144        let n: u8 = tag.into();
145
146        // Only tags 0-15 are supported.
147        if n > 15 {
148            return Err(Error::InvalidArgument(
149                format!("Only tags 0-15 are supported, got: {:?} ({})",
150                        tag, n)).into());
151        }
152
153        let length_type = match length {
154            // Assume an optimal encoding.
155            BodyLength::Full(l) => {
156                match l {
157                    // One octet length.
158                    0 ..= 0xFF => PacketLengthType::OneOctet,
159                    // Two octet length.
160                    0x1_00 ..= 0xFF_FF => PacketLengthType::TwoOctets,
161                    // Four octet length,
162                    _ => PacketLengthType::FourOctets,
163                }
164            },
165            BodyLength::Partial(_) =>
166                return Err(Error::InvalidArgument(
167                    "Partial body lengths are not support for old format packets".
168                        into()).into()),
169            BodyLength::Indeterminate =>
170                PacketLengthType::Indeterminate,
171        };
172
173        Ok(CTBOld {
174            common: CTBCommon {
175                tag,
176            },
177            length_type,
178        })
179    }
180
181    /// Returns the packet's tag.
182    pub fn tag(&self) -> Tag {
183        self.common.tag
184    }
185
186    /// Returns the packet's length type.
187    pub fn length_type(&self) -> PacketLengthType {
188        self.length_type
189    }
190}
191
192/// The CTB variants.
193///
194/// There are two CTB variants: the [old CTB format] and the [new CTB
195/// format].
196///
197///   [old CTB format]: CTBOld
198///   [new CTB format]: CTBNew
199///
200/// Note: CTB stands for Cipher Type Byte.
201#[derive(Clone, Debug)]
202pub enum CTB {
203    /// New (current) packet header format.
204    New(CTBNew),
205    /// Old PGP 2.6 header format.
206    Old(CTBOld),
207}
208assert_send_and_sync!(CTB);
209
210impl CTB {
211    /// Constructs a new-style CTB.
212    pub fn new(tag: Tag) -> Self {
213        CTB::New(CTBNew::new(tag))
214    }
215
216    /// Returns the packet's tag.
217    pub fn tag(&self) -> Tag {
218        match self {
219            CTB::New(c) => c.tag(),
220            CTB::Old(c) => c.tag(),
221        }
222    }
223}
224
225impl TryFrom<u8> for CTB {
226    type Error = anyhow::Error;
227
228    /// Parses a CTB as described in [Section 4.2 of RFC 9580].  This
229    /// function parses both new and old format CTBs.
230    ///
231    ///   [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2
232    fn try_from(ptag: u8) -> Result<CTB> {
233        // The top bit of the ptag must be set.
234        if ptag & 0b1000_0000 == 0 {
235            return Err(
236                Error::MalformedPacket(
237                    format!("Malformed CTB: MSB of ptag ({:#010b}) not set{}.",
238                            ptag,
239                            if ptag == b'-' {
240                                " (ptag is a dash, perhaps this is an \
241                                 ASCII-armor encoded message)"
242                            } else {
243                                ""
244                            })).into());
245        }
246
247        let new_format = ptag & 0b0100_0000 != 0;
248        let ctb = if new_format {
249            let tag = ptag & 0b0011_1111;
250            CTB::New(CTBNew {
251                common: CTBCommon {
252                    tag: tag.into()
253                }})
254        } else {
255            let tag = (ptag & 0b0011_1100) >> 2;
256            let length_type = ptag & 0b0000_0011;
257
258            CTB::Old(CTBOld {
259                common: CTBCommon {
260                    tag: tag.into(),
261                },
262                length_type: PacketLengthType::try_from(length_type)?,
263            })
264        };
265
266        Ok(ctb)
267    }
268}
269
270#[test]
271fn ctb() {
272    // 0x99 = public key packet
273    if let CTB::Old(ctb) = CTB::try_from(0x99).unwrap() {
274        assert_eq!(ctb.tag(), Tag::PublicKey);
275        assert_eq!(ctb.length_type, PacketLengthType::TwoOctets);
276    } else {
277        panic!("Expected an old format packet.");
278    }
279
280    // 0xa3 = old compressed packet
281    if let CTB::Old(ctb) = CTB::try_from(0xa3).unwrap() {
282        assert_eq!(ctb.tag(), Tag::CompressedData);
283        assert_eq!(ctb.length_type, PacketLengthType::Indeterminate);
284    } else {
285        panic!("Expected an old format packet.");
286    }
287
288    // 0xcb: new literal
289    if let CTB::New(ctb) = CTB::try_from(0xcb).unwrap() {
290        assert_eq!(ctb.tag(), Tag::Literal);
291    } else {
292        panic!("Expected a new format packet.");
293    }
294}