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}