1use crate::label::Label;
14
15#[derive(Clone, PartialEq, Eq)]
17pub struct Transcript {
18 buf: Vec<u8>,
19}
20
21impl core::fmt::Debug for Transcript {
22 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23 write!(f, "Transcript({} байт)", self.buf.len())
24 }
25}
26
27impl Transcript {
28 #[must_use]
37 pub fn new(label: Label) -> Self {
38 let bytes = label.as_bytes();
39 let mut buf = Vec::with_capacity(bytes.len().saturating_add(64));
40 buf.extend_from_slice(bytes);
41 buf.push(0x00);
42 Self { buf }
43 }
44
45 pub fn u8(&mut self, value: u8) -> &mut Self {
47 self.buf.push(value);
48 self
49 }
50
51 pub fn u32le(&mut self, value: u32) -> &mut Self {
53 self.buf.extend_from_slice(&value.to_le_bytes());
54 self
55 }
56
57 pub fn u32be(&mut self, value: u32) -> &mut Self {
59 self.buf.extend_from_slice(&value.to_be_bytes());
60 self
61 }
62
63 pub fn u64be(&mut self, value: u64) -> &mut Self {
65 self.buf.extend_from_slice(&value.to_be_bytes());
66 self
67 }
68
69 pub fn fixed(&mut self, value: &[u8]) -> &mut Self {
75 self.buf.extend_from_slice(value);
76 self
77 }
78
79 pub fn field(&mut self, value: &[u8]) -> &mut Self {
81 let len = u32::try_from(value.len()).unwrap_or(u32::MAX);
82 self.buf.extend_from_slice(&len.to_le_bytes());
83 self.buf.extend_from_slice(value);
84 self
85 }
86
87 pub fn tail_after_declared_length(&mut self, value: &[u8]) -> &mut Self {
93 self.buf.extend_from_slice(value);
94 self
95 }
96
97 pub fn as_bytes(&self) -> &[u8] {
99 &self.buf
100 }
101
102 pub fn len(&self) -> usize {
104 self.buf.len()
105 }
106
107 pub fn is_empty(&self) -> bool {
109 self.buf.is_empty()
110 }
111}
112
113#[cfg(test)]
114#[allow(clippy::unwrap_used, clippy::panic)]
115mod tests {
116 use super::*;
117 use crate::label;
118
119 #[test]
120 fn every_transcript_starts_with_its_label() {
121 let t = Transcript::new(label::LEASE);
122 assert!(t.as_bytes().starts_with(label::LEASE.as_bytes()));
123 assert_eq!(t.as_bytes().get(label::LEASE.len()), Some(&0x00));
124 }
125
126 #[test]
127 fn prefix_labels_do_not_collide_even_though_the_set_is_prefix_free() {
128 let mut a = Transcript::new(label::LEASE);
132 a.fixed(b"-cache-and-more");
133 let mut b = Transcript::new(label::CACHED_LEASE);
134 b.fixed(b"-and-more");
135 assert_ne!(a.as_bytes(), b.as_bytes());
136 }
137
138 #[test]
139 fn different_labels_never_collide() {
140 let mut a = Transcript::new(label::LEASE);
143 a.fixed(&[1, 2, 3]);
144 let mut b = Transcript::new(label::REVOCATION);
145 b.fixed(&[1, 2, 3]);
146 assert_ne!(a.as_bytes(), b.as_bytes());
147 }
148
149 #[test]
150 fn variable_length_fields_are_unambiguous() {
151 let mut a = Transcript::new(label::GRANT);
153 a.field(b"ab").field(b"c");
154 let mut b = Transcript::new(label::GRANT);
155 b.field(b"a").field(b"bc");
156 assert_ne!(a.as_bytes(), b.as_bytes());
157 }
158
159 #[test]
160 fn fixed_length_fields_are_concatenated_verbatim() {
161 let mut t = Transcript::new(label::CHUNK);
162 t.fixed(&[0xaa; 16]).u32be(7).u8(1);
163 let expected_len = label::CHUNK.len() + 1 + 16 + 4 + 1;
164 assert_eq!(t.len(), expected_len);
165 assert_eq!(t.as_bytes().get(t.len() - 5..t.len()), Some(&[0, 0, 0, 7, 1][..]));
166 }
167
168 #[test]
169 fn endianness_is_explicit_and_distinct() {
170 let mut le = Transcript::new(label::CHUNK);
171 le.u32le(1);
172 let mut be = Transcript::new(label::CHUNK);
173 be.u32be(1);
174 assert_ne!(le.as_bytes(), be.as_bytes(), "порядок байтов обязан быть явным");
175 }
176}