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