| //! Slice writer. |
| |
| use crate::{ |
| asn1::*, Encode, EncodeValue, ErrorKind, Header, Length, Result, Tag, TagMode, TagNumber, |
| Tagged, Writer, |
| }; |
| |
| /// [`Writer`] which encodes DER into a mutable output byte slice. |
| #[derive(Debug)] |
| pub struct SliceWriter<'a> { |
| /// Buffer into which DER-encoded message is written |
| bytes: &'a mut [u8], |
| |
| /// Has the encoding operation failed? |
| failed: bool, |
| |
| /// Total number of bytes written to buffer so far |
| position: Length, |
| } |
| |
| impl<'a> SliceWriter<'a> { |
| /// Create a new encoder with the given byte slice as a backing buffer. |
| pub fn new(bytes: &'a mut [u8]) -> Self { |
| Self { |
| bytes, |
| failed: false, |
| position: Length::ZERO, |
| } |
| } |
| |
| /// Encode a value which impls the [`Encode`] trait. |
| pub fn encode<T: Encode>(&mut self, encodable: &T) -> Result<()> { |
| if self.is_failed() { |
| self.error(ErrorKind::Failed)?; |
| } |
| |
| encodable.encode(self).map_err(|e| { |
| self.failed = true; |
| e.nested(self.position) |
| }) |
| } |
| |
| /// Return an error with the given [`ErrorKind`], annotating it with |
| /// context about where the error occurred. |
| pub fn error<T>(&mut self, kind: ErrorKind) -> Result<T> { |
| self.failed = true; |
| Err(kind.at(self.position)) |
| } |
| |
| /// Did the decoding operation fail due to an error? |
| pub fn is_failed(&self) -> bool { |
| self.failed |
| } |
| |
| /// Finish encoding to the buffer, returning a slice containing the data |
| /// written to the buffer. |
| pub fn finish(self) -> Result<&'a [u8]> { |
| let position = self.position; |
| |
| if self.is_failed() { |
| return Err(ErrorKind::Failed.at(position)); |
| } |
| |
| self.bytes |
| .get(..usize::try_from(position)?) |
| .ok_or_else(|| ErrorKind::Overlength.at(position)) |
| } |
| |
| /// Encode a `CONTEXT-SPECIFIC` field with the provided tag number and mode. |
| pub fn context_specific<T>( |
| &mut self, |
| tag_number: TagNumber, |
| tag_mode: TagMode, |
| value: &T, |
| ) -> Result<()> |
| where |
| T: EncodeValue + Tagged, |
| { |
| ContextSpecificRef { |
| tag_number, |
| tag_mode, |
| value, |
| } |
| .encode(self) |
| } |
| |
| /// Encode an ASN.1 `SEQUENCE` of the given length. |
| /// |
| /// Spawns a nested slice writer which is expected to be exactly the |
| /// specified length upon completion. |
| pub fn sequence<F>(&mut self, length: Length, f: F) -> Result<()> |
| where |
| F: FnOnce(&mut SliceWriter<'_>) -> Result<()>, |
| { |
| Header::new(Tag::Sequence, length).and_then(|header| header.encode(self))?; |
| |
| let mut nested_encoder = SliceWriter::new(self.reserve(length)?); |
| f(&mut nested_encoder)?; |
| |
| if nested_encoder.finish()?.len() == usize::try_from(length)? { |
| Ok(()) |
| } else { |
| self.error(ErrorKind::Length { tag: Tag::Sequence }) |
| } |
| } |
| |
| /// Reserve a portion of the internal buffer, updating the internal cursor |
| /// position and returning a mutable slice. |
| fn reserve(&mut self, len: impl TryInto<Length>) -> Result<&mut [u8]> { |
| if self.is_failed() { |
| return Err(ErrorKind::Failed.at(self.position)); |
| } |
| |
| let len = len |
| .try_into() |
| .or_else(|_| self.error(ErrorKind::Overflow))?; |
| |
| let end = (self.position + len).or_else(|e| self.error(e.kind()))?; |
| let slice = self |
| .bytes |
| .get_mut(self.position.try_into()?..end.try_into()?) |
| .ok_or_else(|| ErrorKind::Overlength.at(end))?; |
| |
| self.position = end; |
| Ok(slice) |
| } |
| } |
| |
| impl<'a> Writer for SliceWriter<'a> { |
| fn write(&mut self, slice: &[u8]) -> Result<()> { |
| self.reserve(slice.len())?.copy_from_slice(slice); |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::SliceWriter; |
| use crate::{Encode, ErrorKind, Length}; |
| |
| #[test] |
| fn overlength_message() { |
| let mut buffer = []; |
| let mut writer = SliceWriter::new(&mut buffer); |
| let err = false.encode(&mut writer).err().unwrap(); |
| assert_eq!(err.kind(), ErrorKind::Overlength); |
| assert_eq!(err.position(), Some(Length::ONE)); |
| } |
| } |