paysec_keyblock/tr31_2018/opt_block.rs
1//! Module for TR-31 Optional Blocks.
2//!
3//! This module defines the [`OptBlock`] type representing an optional block
4//! within a TR-31 key block.
5//!
6//! Optional blocks contain supplementary information associated with a key
7//! block and can be linked together to form a sequence.
8//!
9//! # Format
10//!
11//! An optional block consists of:
12//!
13//! - a two-character identifier,
14//! - a length field,
15//! - ASCII data,
16//! - optionally, another optional block.
17//!
18//! Blocks shorter than 256 bytes use the normal two-character hexadecimal
19//! length field. Larger blocks use the TR-31 extended-length representation.
20//!
21//! # Example
22//!
23//! ```
24//! use paysec_keyblock::OptBlock;
25//!
26//! let opt_block =
27//! OptBlock::new("CT", "ExampleData", None).unwrap();
28//!
29//! assert_eq!(opt_block.id(), "CT");
30//! assert_eq!(opt_block.data(), "ExampleData");
31//!
32//! let next_block =
33//! OptBlock::new("PB", "PaddingData", None).unwrap();
34//!
35//! let mut chain = opt_block;
36//! chain.append(next_block);
37//!
38//! let exported = chain.export_str().unwrap();
39//!
40//! assert!(!exported.is_empty());
41//! ```
42//!
43//! # References
44//!
45//! TR-31: 2018, p. 17-18, 27-33.
46
47use super::error::OptBlockError;
48use super::header_constants::ALLOWED_OPT_BLOCK_IDS;
49
50/// Represent an optional block as defined by TR-31.
51///
52/// Each block contains:
53///
54/// - a two-character identifier,
55/// - ASCII data,
56/// - the encoded total block length,
57/// - an optional link to another block.
58#[derive(Debug, PartialEq, Clone)]
59pub struct OptBlock {
60 id: String,
61 data: String,
62 length: usize,
63 next: Option<Box<OptBlock>>,
64}
65
66impl OptBlock {
67 /// Create a new optional block.
68 ///
69 /// # Parameters
70 ///
71 /// * `id` - TR-31 optional-block identifier.
72 /// * `data` - ASCII optional-block data.
73 /// * `next` - Optional next block in the chain.
74 ///
75 /// # Errors
76 ///
77 /// Returns [`OptBlockError::InvalidId`] if the identifier is unsupported.
78 ///
79 /// Returns [`OptBlockError::NonAsciiData`] if the data contains non-ASCII
80 /// characters.
81 ///
82 /// Returns [`OptBlockError::BlockTooLong`] if the encoded block exceeds
83 /// 65535 bytes.
84 pub fn new(id: &str, data: &str, next: Option<OptBlock>) -> Result<Self, OptBlockError> {
85 let mut opt_block = Self::new_empty();
86
87 opt_block.set_id(id)?;
88 opt_block.set_data(data)?;
89 opt_block.set_next(next);
90
91 Ok(opt_block)
92 }
93
94 /// Create an empty optional block.
95 pub fn new_empty() -> Self {
96 Self {
97 id: String::new(),
98 data: String::new(),
99 length: 0,
100 next: None,
101 }
102 }
103
104 /// Parse one or more linked optional blocks from their string
105 /// representation.
106 ///
107 /// # Parameters
108 ///
109 /// * `s` - Encoded optional-block data.
110 /// * `num_opt_blocks` - Number of linked optional blocks expected.
111 ///
112 /// # Errors
113 ///
114 /// Returns an [`OptBlockError`] if the input is malformed, truncated,
115 /// contains invalid length information, uses an unsupported identifier,
116 /// or contains non-ASCII data.
117 pub fn new_from_str(s: &str, num_opt_blocks: usize) -> Result<Self, OptBlockError> {
118 if !s.is_ascii() {
119 return Err(OptBlockError::NonAsciiInput);
120 }
121
122 if s.len() < 4 {
123 return Err(OptBlockError::StringTooShort {
124 minimum: 4,
125 actual: s.len(),
126 });
127 }
128
129 let mut opt_block = Self::new_empty();
130
131 opt_block.set_id(&s[..2])?;
132
133 let data_start_offset;
134
135 if &s[2..4] == "00" {
136 // Extended-length blocks necessarily have a total encoded length
137 // greater than 255 bytes.
138 if s.len() < 256 {
139 return Err(OptBlockError::ExtendedLengthStringTooShort {
140 minimum: 256,
141 actual: s.len(),
142 });
143 }
144
145 let ext_block_len = &s[4..10];
146
147 opt_block.length = Self::ext_len_from_str(ext_block_len)?;
148
149 data_start_offset = 10;
150 } else {
151 opt_block.length = Self::len_from_str(&s[2..4])?;
152
153 data_start_offset = 4;
154 }
155
156 if s.len() < opt_block.length {
157 return Err(OptBlockError::StringTooShortForLength {
158 required: opt_block.length,
159 actual: s.len(),
160 });
161 }
162
163 opt_block.set_data(&s[data_start_offset..opt_block.length])?;
164
165 if num_opt_blocks > 1 {
166 let next_block_str = &s[opt_block.length..];
167
168 let next_block = Self::new_from_str(next_block_str, num_opt_blocks - 1)?;
169
170 opt_block.set_next(Some(next_block));
171 }
172
173 Ok(opt_block)
174 }
175
176 /// Serialize this optional block and all following linked blocks.
177 ///
178 /// # Errors
179 ///
180 /// Returns [`OptBlockError::Uninitialized`] if this block does not contain
181 /// a valid initialized length.
182 ///
183 /// Errors from subsequent linked blocks are propagated unchanged.
184 pub fn export_str(&self) -> Result<String, OptBlockError> {
185 if self.length < 4 {
186 return Err(OptBlockError::Uninitialized {
187 length: self.length,
188 });
189 }
190
191 let mut result = String::new();
192
193 result.push_str(&self.id);
194
195 if self.length < 256 {
196 result.push_str(&format!("{:02X}", self.length,));
197 } else {
198 result.push_str(&format!("0002{:04X}", self.length,));
199 }
200
201 result.push_str(&self.data);
202
203 if let Some(next) = &self.next {
204 result.push_str(&next.export_str()?);
205 }
206
207 Ok(result)
208 }
209
210 /// Set the optional-block identifier.
211 ///
212 /// # Errors
213 ///
214 /// Returns [`OptBlockError::InvalidId`] if `id` is not a supported TR-31
215 /// optional-block identifier.
216 pub fn set_id(&mut self, id: &str) -> Result<(), OptBlockError> {
217 if Self::is_allowed_id(id) {
218 self.id = id.to_string();
219
220 Ok(())
221 } else {
222 Err(OptBlockError::InvalidId(id.to_string()))
223 }
224 }
225
226 /// Return the optional-block identifier.
227 pub fn id(&self) -> &str {
228 &self.id
229 }
230
231 /// Set the optional-block data and recalculate the encoded block length.
232 ///
233 /// # Errors
234 ///
235 /// Returns [`OptBlockError::IdNotSet`] if the identifier has not first
236 /// been configured.
237 ///
238 /// Returns [`OptBlockError::NonAsciiData`] if `data` contains non-ASCII
239 /// characters.
240 ///
241 /// Returns [`OptBlockError::BlockTooLong`] if the resulting encoded block
242 /// exceeds 65535 bytes.
243 pub fn set_data(&mut self, data: &str) -> Result<(), OptBlockError> {
244 if self.id.len() != 2 {
245 return Err(OptBlockError::IdNotSet);
246 }
247
248 if !data.is_ascii() {
249 return Err(OptBlockError::NonAsciiData(data.to_string()));
250 }
251
252 self.data = data.to_string();
253
254 self.set_length()?;
255
256 Ok(())
257 }
258
259 /// Return the optional-block data.
260 pub fn data(&self) -> &str {
261 &self.data
262 }
263
264 /// Calculate and store this block's encoded length.
265 ///
266 /// Blocks shorter than 256 bytes use the normal two-character length
267 /// field. Larger blocks require the six additional characters used by the
268 /// extended-length representation.
269 fn set_length(&mut self) -> Result<(), OptBlockError> {
270 const MAX_OPT_BLOCK_LENGTH: usize = 65535;
271
272 let minimum_length = self.id.len() + 2 + self.data.len();
273
274 self.length = if minimum_length < 256 {
275 minimum_length
276 } else {
277 minimum_length + 6
278 };
279
280 if self.length > MAX_OPT_BLOCK_LENGTH {
281 let actual = self.length;
282
283 self.length = 0;
284
285 return Err(OptBlockError::BlockTooLong {
286 maximum: MAX_OPT_BLOCK_LENGTH,
287 actual,
288 });
289 }
290
291 Ok(())
292 }
293
294 /// Return this optional block's encoded length.
295 pub fn length(&self) -> &usize {
296 &self.length
297 }
298
299 /// Set the next optional block.
300 pub fn set_next(&mut self, next_block: Option<OptBlock>) {
301 self.next = next_block.map(Box::new);
302 }
303
304 /// Return the next optional block, if one exists.
305 pub fn next(&self) -> Option<&OptBlock> {
306 self.next.as_deref()
307 }
308
309 /// Append an optional block to the end of this block chain.
310 pub fn append(&mut self, opt_block_to_append: OptBlock) {
311 match &mut self.next {
312 Some(next_block) => {
313 next_block.append(opt_block_to_append);
314 }
315
316 None => {
317 self.set_next(Some(opt_block_to_append));
318 }
319 }
320 }
321
322 /// Return whether an optional-block identifier is supported.
323 pub fn is_allowed_id(id: &str) -> bool {
324 ALLOWED_OPT_BLOCK_IDS.contains(&id)
325 }
326
327 /// Return the total encoded length of this block and all linked blocks.
328 pub fn total_length(&self) -> usize {
329 let mut total = self.length;
330
331 if let Some(next) = &self.next {
332 total += next.total_length();
333 }
334
335 total
336 }
337
338 /// Parse a normal two-character hexadecimal optional-block length.
339 fn len_from_str(s: &str) -> Result<usize, OptBlockError> {
340 if s.len() != 2 {
341 return Err(OptBlockError::InvalidLengthFieldWidth {
342 value: s.to_string(),
343 expected: 2,
344 });
345 }
346
347 let length = usize::from_str_radix(s, 16).map_err(|source| {
348 OptBlockError::InvalidLengthFieldHex {
349 value: s.to_string(),
350 source,
351 }
352 })?;
353
354 if length < 4 {
355 return Err(OptBlockError::LengthFieldTooSmall {
356 minimum: 4,
357 actual: length,
358 });
359 }
360
361 Ok(length)
362 }
363
364 /// Parse the six-character extended optional-block length field.
365 fn ext_len_from_str(s: &str) -> Result<usize, OptBlockError> {
366 if s.len() != 6 {
367 return Err(OptBlockError::InvalidExtendedLengthField(s.to_string()));
368 }
369
370 let length_of_length = &s[0..2];
371
372 if length_of_length != "02" {
373 return Err(OptBlockError::InvalidLengthOfLengthField(
374 length_of_length.to_string(),
375 ));
376 }
377
378 let encoded_length = &s[2..6];
379
380 let length = usize::from_str_radix(encoded_length, 16).map_err(|source| {
381 OptBlockError::InvalidExtendedLengthHex {
382 value: encoded_length.to_string(),
383 source,
384 }
385 })?;
386
387 if length <= 255 {
388 return Err(OptBlockError::ExtendedLengthTooSmall {
389 value: encoded_length.to_string(),
390 });
391 }
392
393 Ok(length)
394 }
395}