Skip to main content

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}