//! Cipher Type Byte. //! //! See [Section 4.2 of RFC 4880] for more details. //! //! [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2 use std::convert::TryFrom; use crate::{ packet::Tag, Error, Result }; use crate::packet::header::BodyLength; /// OpenPGP defines two packet formats: the old and the new format. /// They both include the packet's so-called tag. /// /// See [Section 4.2 of RFC 4880] for more details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2 #[derive(Clone, Debug)] struct CTBCommon { /// RFC4880 Packet tag tag: Tag, } /// The new CTB format. /// /// See [Section 4.2 of RFC 4880] for more details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2 #[derive(Clone, Debug)] pub struct CTBNew { /// Packet CTB fields common: CTBCommon, } impl CTBNew { /// Constructs a new-style CTB. pub fn new(tag: Tag) -> Self { CTBNew { common: CTBCommon { tag: tag, }, } } /// Returns the packet's tag. pub fn tag(&self) -> Tag { self.common.tag } } /// The PacketLengthType is used as part of the [old CTB], and is /// partially used to determine the packet's size. /// /// See [Section 4.2.1 of RFC 4880] for more details. /// /// [Section 4.2.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2.1 /// [old CTB]: struct.CTBOld.html #[derive(Debug)] #[derive(Clone, Copy, PartialEq)] pub enum PacketLengthType { /// A one-octet Body Length header encodes a length of 0 to 191 octets. OneOctet, /// A two-octet Body Length header encodes a length of 192 to 8383 octets. TwoOctets, /// A five-octet Body Length header consists of a single octet holding /// the value 255, followed by a four-octet scalar. FourOctets, /// A Partial Body Length header is one octet long and encodes the length /// of only part of the data packet. Indeterminate, } impl TryFrom for PacketLengthType { type Error = failure::Error; fn try_from(u: u8) -> Result { match u { 0 => Ok(PacketLengthType::OneOctet), 1 => Ok(PacketLengthType::TwoOctets), 2 => Ok(PacketLengthType::FourOctets), 3 => Ok(PacketLengthType::Indeterminate), _ => Err(Error::InvalidArgument( format!("Invalid packet length: {}", u)).into()), } } } impl From for u8 { fn from(l: PacketLengthType) -> Self { match l { PacketLengthType::OneOctet => 0, PacketLengthType::TwoOctets => 1, PacketLengthType::FourOctets => 2, PacketLengthType::Indeterminate => 3, } } } /// The old CTB format. /// /// See [Section 4.2 of RFC 4880] for more details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2 #[derive(Clone, Debug)] pub struct CTBOld { /// Common CTB fields. common: CTBCommon, /// Type of length specifier. pub length_type: PacketLengthType, } impl CTBOld { /// Constructs a old-style CTB. /// /// # Errors /// /// Returns [`Error::InvalidArgument`] if the tag or body length /// cannot be expressed using an old-style CTB. /// /// [`Error::InvalidArgument`]: ../../enum.Error.html#variant.InvalidArgument pub fn new(tag: Tag, length: BodyLength) -> Result { let n: u8 = tag.into(); // Only tags 0-15 are supported. if n > 15 { return Err(Error::InvalidArgument( format!("Only tags 0-15 are supported, got: {:?} ({})", tag, n)).into()); } let length_type = match length { // Assume an optimal encoding. BodyLength::Full(l) => { match l { // One octet length. 0 ..= 0xFF => PacketLengthType::OneOctet, // Two octet length. 0x1_00 ..= 0xFF_FF => PacketLengthType::TwoOctets, // Four octet length, _ => PacketLengthType::FourOctets, } }, BodyLength::Partial(_) => return Err(Error::InvalidArgument( "Partial body lengths are not support for old format packets". into()).into()), BodyLength::Indeterminate => PacketLengthType::Indeterminate, }; Ok(CTBOld { common: CTBCommon { tag: tag, }, length_type: length_type, }) } /// Returns the packet's tag. pub fn tag(&self) -> Tag { self.common.tag } } /// A sum type for the different CTB variants. /// /// There are two CTB variants: the [old CTB format] and the [new CTB /// format]. /// /// [old CTB format]: struct.CTBOld.html /// [new CTB format]: struct.CTBNew.html /// /// Note: CTB stands for Cipher Type Byte. #[derive(Clone, Debug)] pub enum CTB { /// New (current) packet header format. New(CTBNew), /// Old PGP 2.6 header format. Old(CTBOld), } impl CTB { /// Constructs a new-style CTB. pub fn new(tag: Tag) -> Self { CTB::New(CTBNew::new(tag)) } /// Parses a CTB as described in [Section 4.2 of RFC 4880]. This /// function parses both new and old format CTBs. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2 pub fn from_ptag(ptag: u8) -> Result { // The top bit of the ptag must be set. if ptag & 0b1000_0000 == 0 { // XXX: Use a proper error. return Err( Error::MalformedPacket( format!("Malformed CTB: MSB of ptag ({:#010b}) not set{}.", ptag, if ptag == '-' as u8 { " (ptag is a dash, perhaps this is an \ ASCII-armor encoded message)" } else { "" })).into()); } let new_format = ptag & 0b0100_0000 != 0; let ctb = if new_format { let tag = ptag & 0b0011_1111; CTB::New(CTBNew { common: CTBCommon { tag: tag.into() }}) } else { let tag = (ptag & 0b0011_1100) >> 2; let length_type = ptag & 0b0000_0011; CTB::Old(CTBOld { common: CTBCommon { tag: tag.into(), }, length_type: PacketLengthType::try_from(length_type)?, }) }; Ok(ctb) } /// Returns the packet's tag. pub fn tag(&self) -> Tag { match self { CTB::New(c) => c.tag(), CTB::Old(c) => c.tag(), } } } #[test] fn ctb() { // 0x99 = public key packet if let CTB::Old(ctb) = CTB::from_ptag(0x99).unwrap() { assert_eq!(ctb.tag(), Tag::PublicKey); assert_eq!(ctb.length_type, PacketLengthType::TwoOctets); } else { panic!("Expected an old format packet."); } // 0xa3 = old compressed packet if let CTB::Old(ctb) = CTB::from_ptag(0xa3).unwrap() { assert_eq!(ctb.tag(), Tag::CompressedData); assert_eq!(ctb.length_type, PacketLengthType::Indeterminate); } else { panic!("Expected an old format packet."); } // 0xcb: new literal if let CTB::New(ctb) = CTB::from_ptag(0xcb).unwrap() { assert_eq!(ctb.tag(), Tag::Literal); } else { panic!("Expected a new format packet."); } }