1use super::*;
2
3use ciborium_io::Write;
4
5/// An encoder for serializing CBOR items
6///
7/// This structure wraps a writer and provides convenience functions for
8/// writing `Header` objects to the wire.
9pub struct Encoder<W: Write>(W);
10
11impl<W: Write> From<W> for Encoder<W> {
12 #[inline]
13 fn from(value: W) -> Self {
14 Self(value)
15 }
16}
17
18impl<W: Write> Write for Encoder<W> {
19 type Error = W::Error;
20
21 fn write_all(&mut self, data: &[u8]) -> Result<(), Self::Error> {
22 self.0.write_all(data)
23 }
24
25 fn flush(&mut self) -> Result<(), Self::Error> {
26 self.0.flush()
27 }
28}
29
30impl<W: Write> Encoder<W> {
31 /// Push a `Header` to the wire
32 #[inline]
33 pub fn push(&mut self, header: Header) -> Result<(), W::Error> {
34 let title = Title::from(header);
35
36 let major = match title.0 {
37 Major::Positive => 0,
38 Major::Negative => 1,
39 Major::Bytes => 2,
40 Major::Text => 3,
41 Major::Array => 4,
42 Major::Map => 5,
43 Major::Tag => 6,
44 Major::Other => 7,
45 };
46
47 let minor = match title.1 {
48 Minor::This(x) => x,
49 Minor::Next1(..) => 24,
50 Minor::Next2(..) => 25,
51 Minor::Next4(..) => 26,
52 Minor::Next8(..) => 27,
53 Minor::More => 31,
54 };
55
56 self.0.write_all(&[major << 5 | minor])?;
57 self.0.write_all(title.1.as_ref())
58 }
59
60 /// Serialize a byte slice as CBOR
61 ///
62 /// Optionally, segment the output into `segment` size segments. Note that
63 /// if `segment == Some(0)` it will be silently upgraded to `Some(1)`. This
64 /// minimum value is highly inefficient and should not be relied upon.
65 #[inline]
66 pub fn bytes(
67 &mut self,
68 value: &[u8],
69 segment: impl Into<Option<usize>>,
70 ) -> Result<(), W::Error> {
71 let max = segment.into().unwrap_or(value.len());
72 let max = core::cmp::max(max, 1);
73
74 if max >= value.len() {
75 self.push(Header::Bytes(Some(value.len())))?;
76 self.write_all(value)?;
77 } else {
78 self.push(Header::Bytes(None))?;
79
80 for chunk in value.chunks(max) {
81 self.push(Header::Bytes(Some(chunk.len())))?;
82 self.write_all(chunk)?;
83 }
84
85 self.push(Header::Break)?;
86 }
87
88 Ok(())
89 }
90
91 /// Serialize a string slice as CBOR
92 ///
93 /// Optionally, segment the output into `segment` size segments. Note that
94 /// since care is taken to ensure that each segment is itself a valid UTF-8
95 /// string, if `segment` contains a value of less than 4, it will be
96 /// silently upgraded to 4. This minimum value is highly inefficient and
97 /// should not be relied upon.
98 #[inline]
99 pub fn text(&mut self, value: &str, segment: impl Into<Option<usize>>) -> Result<(), W::Error> {
100 let max = segment.into().unwrap_or(value.len());
101 let max = core::cmp::max(max, 4);
102
103 if max >= value.len() {
104 self.push(Header::Text(Some(value.len())))?;
105 self.write_all(value.as_bytes())?;
106 } else {
107 self.push(Header::Text(None))?;
108
109 let mut bytes = value.as_bytes();
110 while !bytes.is_empty() {
111 let mut len = core::cmp::min(bytes.len(), max);
112 while len > 0 && core::str::from_utf8(&bytes[..len]).is_err() {
113 len -= 1
114 }
115
116 let (prefix, suffix) = bytes.split_at(len);
117 self.push(Header::Text(Some(prefix.len())))?;
118 self.write_all(prefix)?;
119 bytes = suffix;
120 }
121
122 self.push(Header::Break)?;
123 }
124
125 Ok(())
126 }
127}
128