Skip to content

Commit

Permalink
der: PemReader simplifications
Browse files Browse the repository at this point in the history
Removes the internal buffer added in #839 in favor of just cloning the
`PemReader`'s state.

This is a partial step towards #1475 and would greatly simplify the
implementation of #1474.
  • Loading branch information
tarcieri committed Aug 17, 2024
1 parent 7a95bb0 commit 953a879
Showing 1 changed file with 25 additions and 138 deletions.
163 changes: 25 additions & 138 deletions der/src/reader/pem.rs
Original file line number Diff line number Diff line change
@@ -1,132 +1,16 @@
//! Streaming PEM reader.
use super::Reader;
use crate::{Decode, EncodingRules, Error, ErrorKind, Header, Length, Result};
use core::cell::RefCell;

#[allow(clippy::arithmetic_side_effects)]
mod utils {
use crate::{Error, Length, Result};
use pem_rfc7468::Decoder;

#[derive(Clone)]
pub(super) struct BufReader<'i> {
/// Inner PEM decoder.
decoder: Decoder<'i>,

/// Remaining after base64 decoding
remaining: usize,

/// Read buffer
buf: [u8; BufReader::CAPACITY],

/// Position of the head in the buffer,
pos: usize,

/// Position of the tail in the buffer,
cap: usize,
}

impl<'i> BufReader<'i> {
const CAPACITY: usize = 256;

pub fn new(pem: &'i [u8]) -> Result<Self> {
let decoder = Decoder::new(pem)?;
let remaining = decoder.remaining_len();

Ok(Self {
decoder,
remaining,
buf: [0u8; 256],
pos: 0,
cap: 0,
})
}

pub fn remaining_len(&self) -> usize {
self.decoder.remaining_len() + self.cap - self.pos
}

fn fill_buffer(&mut self) -> Result<()> {
debug_assert!(self.pos <= self.cap);

if self.is_empty() {
self.pos = 0;
self.cap = 0;
}

let end = (self.cap + self.remaining).min(Self::CAPACITY);
let writable_slice = &mut self.buf[self.cap..end];
if writable_slice.is_empty() {
return Ok(());
}

let wrote = self.decoder.decode(writable_slice)?.len();
if wrote == 0 {
return Err(Error::incomplete(Length::try_from(self.pos)?));
}

self.cap += wrote;
self.remaining -= wrote;
debug_assert!(self.cap <= Self::CAPACITY);

Ok(())
}

/// Get the PEM label which will be used in the encapsulation boundaries
/// for this document.
pub fn type_label(&self) -> &'i str {
self.decoder.type_label()
}

fn is_empty(&self) -> bool {
self.pos == self.cap
}

fn as_slice(&self) -> &[u8] {
&self.buf[self.pos..self.cap]
}

pub fn peek_byte(&self) -> Option<u8> {
let s = self.as_slice();
s.first().copied()
}

pub fn copy_to_slice<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o [u8]> {
let mut output_pos = 0;

while output_pos < buf.len() {
if self.is_empty() {
self.fill_buffer()?;
}

let available = &self.buf[self.pos..self.cap];
let window_len = (buf.len() - output_pos).min(available.len());
let window = &mut buf[output_pos..output_pos + window_len];

window.copy_from_slice(&available[..window_len]);
self.pos += window_len;
output_pos += window_len;
}

// Don't leave the read buffer empty for peek_byte()
if self.is_empty() && self.decoder.remaining_len() != 0 {
self.fill_buffer()?
}

debug_assert_eq!(output_pos, buf.len());

Ok(buf)
}
}
}
use crate::{Decode, EncodingRules, ErrorKind, Header, Length, Result};
use pem_rfc7468::Decoder;

/// `Reader` type which decodes PEM on-the-fly.
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[derive(Clone)]
pub struct PemReader<'i> {
/// Inner PEM decoder wrapped in a BufReader.
reader: RefCell<utils::BufReader<'i>>,
/// Inner PEM decoder.
decoder: Decoder<'i>,

/// Encoding rules to apply when decoding the input.
encoding_rules: EncodingRules,
Expand All @@ -139,16 +23,17 @@ pub struct PemReader<'i> {
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl<'i> PemReader<'i> {
/// Create a new PEM reader which decodes data on-the-fly.
///
/// Uses the default 64-character line wrapping.
pub fn new(pem: &'i [u8]) -> Result<Self> {
let reader = utils::BufReader::new(pem)?;
let input_len = Length::try_from(reader.remaining_len())?;
let decoder = Decoder::new(pem)?;
let input_len = Length::try_from(decoder.remaining_len())?;

Ok(Self {
reader: RefCell::new(reader),
decoder,
encoding_rules: EncodingRules::default(),
input_len,
position: Length::ZERO,
Expand All @@ -158,11 +43,21 @@ impl<'i> PemReader<'i> {
/// Get the PEM label which will be used in the encapsulation boundaries
/// for this document.
pub fn type_label(&self) -> &'i str {
self.reader.borrow().type_label()
self.decoder.type_label()
}

/// Peek at the decoded PEM without updating the internal state, writing into the provided
/// output buffer.
///
/// Attempts to fill the entire buffer, returning an error if there is not enough data.
pub fn peek_into(&self, buf: &mut [u8]) -> Result<()> {
self.decoder.clone().decode(buf)?;
Ok(())
}
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl<'i> Reader<'i> for PemReader<'i> {
fn encoding_rules(&self) -> EncodingRules {
self.encoding_rules
Expand All @@ -173,19 +68,12 @@ impl<'i> Reader<'i> for PemReader<'i> {
}

fn peek_byte(&self) -> Option<u8> {
if self.is_finished() {
None
} else {
self.reader.borrow().peek_byte()
}
let mut byte = [0];
self.peek_into(&mut byte).ok().map(|_| byte[0])
}

fn peek_header(&self) -> Result<Header> {
if self.is_finished() {
Err(Error::incomplete(self.offset()))
} else {
Header::decode(&mut self.clone())
}
Header::decode(&mut self.clone())
}

fn position(&self) -> Length {
Expand All @@ -198,13 +86,12 @@ impl<'i> Reader<'i> for PemReader<'i> {
}

fn read_into<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o [u8]> {
let bytes = self.reader.borrow_mut().copy_to_slice(buf)?;

let bytes = self.decoder.decode(buf)?;
self.position = (self.position + bytes.len())?;

debug_assert_eq!(
self.position,
(self.input_len - Length::try_from(self.reader.borrow().remaining_len())?)?
(self.input_len - Length::try_from(self.decoder.remaining_len())?)?
);

Ok(bytes)
Expand Down

0 comments on commit 953a879

Please sign in to comment.