1use std::fmt;
4
5use super::blob::append_blob;
6use super::plex::append_plex;
7use super::seal::finish_seal;
8use super::util::{
9 MAX_BLOB_SIZE, MAX_PLEX_FIELDS, RECORD_GLYPH_LINE_LEN, SEAL_OVERHEAD, blob_record_len,
10 is_reserved_application_field_name, validate_field_name_value,
11};
12use super::{BySecret, Record};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum RecordCreateError {
17 Inference(String),
19 ReservedField(String),
21 Field(String),
23 Data(String),
25 Marking(String),
27}
28
29impl fmt::Display for RecordCreateError {
30 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::Inference(message) => write!(formatter, "record inference error: {message}"),
33 Self::ReservedField(name) => write!(formatter, "reserved field name: {name}"),
34 Self::Field(message) => write!(formatter, "field error: {message}"),
35 Self::Data(message) => write!(formatter, "data error: {message}"),
36 Self::Marking(message) => write!(formatter, "marking error: {message}"),
37 }
38 }
39}
40
41impl std::error::Error for RecordCreateError {}
42
43pub trait IntoRecordData {
45 fn into_record_data(self) -> Vec<u8>;
46}
47
48impl IntoRecordData for Vec<u8> {
49 fn into_record_data(self) -> Vec<u8> {
50 self
51 }
52}
53impl IntoRecordData for &Vec<u8> {
54 fn into_record_data(self) -> Vec<u8> {
55 self.clone()
56 }
57}
58impl IntoRecordData for &[u8] {
59 fn into_record_data(self) -> Vec<u8> {
60 self.to_vec()
61 }
62}
63impl<const N: usize> IntoRecordData for [u8; N] {
64 fn into_record_data(self) -> Vec<u8> {
65 self.to_vec()
66 }
67}
68impl<const N: usize> IntoRecordData for &[u8; N] {
69 fn into_record_data(self) -> Vec<u8> {
70 self.to_vec()
71 }
72}
73impl IntoRecordData for String {
74 fn into_record_data(self) -> Vec<u8> {
75 self.into_bytes()
76 }
77}
78impl IntoRecordData for &String {
79 fn into_record_data(self) -> Vec<u8> {
80 self.as_bytes().to_vec()
81 }
82}
83impl IntoRecordData for &str {
84 fn into_record_data(self) -> Vec<u8> {
85 self.as_bytes().to_vec()
86 }
87}
88
89#[derive(Clone, Default, PartialEq, Eq)]
91pub struct RecordSpec {
92 fields: Vec<(String, String)>,
93 data: Vec<u8>,
94 by_secret: Option<BySecret>,
95}
96
97impl fmt::Debug for RecordSpec {
98 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
99 formatter
100 .debug_struct("RecordSpec")
101 .field("fields", &self.fields)
102 .field("data_len", &self.data.len())
103 .field("by_secret", &self.by_secret.as_ref().map(|_| "<redacted>"))
104 .finish()
105 }
106}
107
108impl RecordSpec {
109 pub fn new() -> Self {
111 Self::default()
112 }
113
114 pub fn from_data(data: impl IntoRecordData) -> Self {
116 Self::new().data(data)
117 }
118
119 pub fn by_secret(mut self, by_secret: impl Into<BySecret>) -> Self {
120 self.by_secret = Some(by_secret.into());
121 self
122 }
123
124 pub fn field(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
125 self.fields.push((name.into(), value.into()));
126 self
127 }
128
129 pub fn fields<I, N, V>(mut self, fields: I) -> Self
130 where
131 I: IntoIterator<Item = (N, V)>,
132 N: Into<String>,
133 V: Into<String>,
134 {
135 self.fields.extend(
136 fields
137 .into_iter()
138 .map(|(name, value)| (name.into(), value.into())),
139 );
140 self
141 }
142
143 pub fn data(mut self, data: impl IntoRecordData) -> Self {
144 self.data = data.into_record_data();
145 self
146 }
147}
148
149pub fn new_record(spec: RecordSpec) -> Result<Record, RecordCreateError> {
151 new_record_impl(&spec.data, &spec.fields, spec.by_secret.as_ref(), None)
152}
153
154#[cfg(feature = "internal")]
156#[doc(hidden)]
157pub fn new_record_with_aux_rand32(
158 spec: RecordSpec,
159 aux_rand32: [u8; 32],
160) -> Result<Record, RecordCreateError> {
161 new_record_impl(
162 &spec.data,
163 &spec.fields,
164 spec.by_secret.as_ref(),
165 Some(aux_rand32),
166 )
167}
168
169pub(crate) fn new_record_impl(
170 data: &[u8],
171 fields: &[(String, String)],
172 by_secret: Option<&BySecret>,
173 aux_rand32: Option<[u8; 32]>,
174) -> Result<Record, RecordCreateError> {
175 CanonicalRecord::build(data, fields, by_secret, aux_rand32)
176 .map(|record| Record::from_constructed(record.0, record.1))
177}
178
179struct CanonicalRecord(Vec<u8>, usize);
184
185impl CanonicalRecord {
186 fn build(
187 data: &[u8],
188 fields: &[(String, String)],
189 by_secret: Option<&BySecret>,
190 aux_rand32: Option<[u8; 32]>,
191 ) -> Result<Self, RecordCreateError> {
192 if data.len() > MAX_BLOB_SIZE {
193 return Err(RecordCreateError::Data(format!(
194 "Blob data exceeds max size: {} bytes",
195 data.len()
196 )));
197 }
198 if aux_rand32.is_some() && by_secret.is_none() {
199 return Err(RecordCreateError::Marking(
200 "exact auxiliary input requires a By-secret value".into(),
201 ));
202 }
203 if fields.is_empty() && by_secret.is_some() {
204 return Err(RecordCreateError::Inference(
205 "a By-secret requires at least one application field".into(),
206 ));
207 }
208 if fields.len() > MAX_PLEX_FIELDS {
209 return Err(RecordCreateError::Field(
210 "too many application fields".into(),
211 ));
212 }
213 let mut field_bytes = 0;
214 for (name, value) in fields {
215 if is_reserved_application_field_name(name) {
216 return Err(RecordCreateError::ReservedField(name.clone()));
217 }
218 validate_field_name_value(name, value).map_err(RecordCreateError::Field)?;
219 field_bytes += name.len() + 2 + value.len() + 1;
220 }
221
222 let seal_bytes = if by_secret.is_some() {
225 SEAL_OVERHEAD
226 } else {
227 0
228 };
229 let plex_bytes = if fields.is_empty() {
230 0
231 } else {
232 RECORD_GLYPH_LINE_LEN + field_bytes
233 };
234 let mut bytes = Vec::with_capacity(seal_bytes + plex_bytes + blob_record_len(data.len()));
235 bytes.resize(seal_bytes, 0);
236 if fields.is_empty() {
237 append_blob(&mut bytes, data);
238 } else {
239 let plex_digest = append_plex(&mut bytes, fields, data);
240 if let Some(secret) = by_secret {
241 finish_seal(&mut bytes, secret, plex_digest, aux_rand32)
242 .map_err(RecordCreateError::Marking)?;
243 }
244 }
245 let data_offset = bytes.len() - data.len();
246 Ok(Self(bytes, data_offset))
247 }
248}