blob: 909728f8dd7ead6f2bcf19296c2afa5a3e1cfc0c [file] [log] [blame]
use super::*;
use ciborium_io::Write;
/// An encoder for serializing CBOR items
///
/// This structure wraps a writer and provides convenience functions for
/// writing `Header` objects to the wire.
pub struct Encoder<W: Write>(W);
impl<W: Write> From<W> for Encoder<W> {
#[inline]
fn from(value: W) -> Self {
Self(value)
}
}
impl<W: Write> Write for Encoder<W> {
type Error = W::Error;
fn write_all(&mut self, data: &[u8]) -> Result<(), Self::Error> {
self.0.write_all(data)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.0.flush()
}
}
impl<W: Write> Encoder<W> {
/// Push a `Header` to the wire
#[inline]
pub fn push(&mut self, header: Header) -> Result<(), W::Error> {
let title = Title::from(header);
let major = match title.0 {
Major::Positive => 0,
Major::Negative => 1,
Major::Bytes => 2,
Major::Text => 3,
Major::Array => 4,
Major::Map => 5,
Major::Tag => 6,
Major::Other => 7,
};
let minor = match title.1 {
Minor::This(x) => x,
Minor::Next1(..) => 24,
Minor::Next2(..) => 25,
Minor::Next4(..) => 26,
Minor::Next8(..) => 27,
Minor::More => 31,
};
self.0.write_all(&[major << 5 | minor])?;
self.0.write_all(title.1.as_ref())
}
/// Serialize a byte slice as CBOR
///
/// Optionally, segment the output into `segment` size segments. Note that
/// if `segment == Some(0)` it will be silently upgraded to `Some(1)`. This
/// minimum value is highly inefficient and should not be relied upon.
#[inline]
pub fn bytes(
&mut self,
value: &[u8],
segment: impl Into<Option<usize>>,
) -> Result<(), W::Error> {
let max = segment.into().unwrap_or_else(|| value.len());
let max = core::cmp::max(max, 1);
if max >= value.len() {
self.push(Header::Bytes(Some(value.len())))?;
self.write_all(value)?;
} else {
self.push(Header::Bytes(None))?;
for chunk in value.chunks(max) {
self.push(Header::Bytes(Some(chunk.len())))?;
self.write_all(chunk)?;
}
self.push(Header::Break)?;
}
Ok(())
}
/// Serialize a string slice as CBOR
///
/// Optionally, segment the output into `segment` size segments. Note that
/// since care is taken to ensure that each segment is itself a valid UTF-8
/// string, if `segment` contains a value of less than 4, it will be
/// silently upgraded to 4. This minimum value is highly inefficient and
/// should not be relied upon.
#[inline]
pub fn text(&mut self, value: &str, segment: impl Into<Option<usize>>) -> Result<(), W::Error> {
let max = segment.into().unwrap_or_else(|| value.len());
let max = core::cmp::max(max, 4);
if max >= value.len() {
self.push(Header::Text(Some(value.len())))?;
self.write_all(value.as_bytes())?;
} else {
self.push(Header::Text(None))?;
let mut bytes = value.as_bytes();
while !bytes.is_empty() {
let mut len = core::cmp::min(bytes.len(), max);
while len > 0 && core::str::from_utf8(&bytes[..len]).is_err() {
len -= 1
}
let (prefix, suffix) = bytes.split_at(len);
self.push(Header::Text(Some(prefix.len())))?;
self.write_all(prefix)?;
bytes = suffix;
}
self.push(Header::Break)?;
}
Ok(())
}
}