Skip to main content

rust_stdf/records/
mod.rs

1use crate::stdf_codec::*;
2use crate::stdf_error::{StdfError, StdfErrorKind};
3use rust_stdf_derive::{stdf_match_expr, stdf_records};
4use smart_default::SmartDefault;
5
6mod type_0_file_info;
7mod type_10_test_synopsis;
8mod type_15_test_info;
9mod type_180_reserved;
10mod type_1_lot_info;
11mod type_20_generic_data;
12mod type_2_wafer_info;
13mod type_50_program_segment;
14mod type_5_part_info;
15
16pub use type_0_file_info::*;
17pub use type_10_test_synopsis::*;
18pub use type_15_test_info::*;
19pub use type_180_reserved::*;
20pub use type_1_lot_info::*;
21pub use type_20_generic_data::*;
22pub use type_2_wafer_info::*;
23pub use type_50_program_segment::*;
24pub use type_5_part_info::*;
25
26#[derive(SmartDefault, Debug, Clone, Copy, PartialEq, Eq)]
27pub struct RecordHeader {
28    pub len: u16,
29    pub typ: u8,
30    pub sub: u8,
31}
32
33// Record Types
34
35/// This module contains constants
36/// for STDF Record type check and
37/// some help functions
38///
39/// ```
40/// use rust_stdf::{StdfRecord, stdf_record_type::*};
41///
42/// // use constant for record initializing
43/// let mut rec = StdfRecord::new(REC_MIR);
44///
45/// // for type check
46/// let t = REC_MIR | REC_MRR | REC_PTR;
47/// let is_t = rec.is_type(t);      // true
48/// ```
49pub mod stdf_record_type {
50    use crate::stdf_error::{StdfError, StdfErrorKind};
51    use rust_stdf_derive::{stdf_match_expr, stdf_records};
52
53    // Generates the `REC_*` record-type code constants.
54    stdf_records!(rec_codes);
55
56    /// This function convert record type constant to
57    /// STDF record (typ, sub)
58    ///
59    /// ```
60    /// use rust_stdf::stdf_record_type::*;
61    ///
62    /// let ptr_typ_sub = get_typ_sub_from_code(REC_PTR).unwrap();
63    /// assert_eq!((15, 10), ptr_typ_sub);
64    /// ```
65    #[inline(always)]
66    pub fn get_typ_sub_from_code(code: u64) -> Result<(u8, u8), StdfError> {
67        stdf_match_expr!(typ_sub_from_code)
68    }
69
70    /// This function convert (typ, sub) to
71    /// STDF record type constant
72    ///
73    /// ```
74    /// use rust_stdf::stdf_record_type::*;
75    ///
76    /// let type_code = get_code_from_typ_sub(15, 10);
77    /// assert_eq!(REC_PTR, type_code);
78    /// ```
79    #[inline(always)]
80    pub fn get_code_from_typ_sub(typ: u8, sub: u8) -> u64 {
81        stdf_match_expr!(code_from_typ_sub)
82    }
83
84    /// This function convert record type constant to
85    /// STDF record name string
86    ///
87    /// ```
88    /// use rust_stdf::stdf_record_type::*;
89    ///
90    /// let rec_name = get_rec_name_from_code(REC_PTR);
91    /// assert_eq!("PTR", rec_name);
92    /// ```
93    #[inline(always)]
94    pub fn get_rec_name_from_code(rec_type: u64) -> &'static str {
95        stdf_match_expr!(name_from_code)
96    }
97
98    /// This function convert record name string to
99    /// STDF record type constant
100    ///
101    /// ```
102    /// use rust_stdf::stdf_record_type::*;
103    ///
104    /// let type_code = get_code_from_rec_name("PTR");
105    /// assert_eq!(REC_PTR, type_code);
106    /// ```
107    ///
108    #[inline(always)]
109    pub fn get_code_from_rec_name(rec_name: &str) -> u64 {
110        stdf_match_expr!(code_from_name)
111    }
112}
113
114// Generates the `StdfRecord` and `StdfRecordView` enums.
115stdf_records!(rec_enums);
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118/// Element yielded by [`RawDataIter`](crate::stdf_file::RawDataIter). It owns unprocessed STDF record data,
119/// and can be converted to [`StdfRecord`], or borrowed as [`StdfRecordView`].
120///
121/// ```
122/// use rust_stdf::{RawDataElement, ByteOrder, StdfRecord, RecordHeader, stdf_record_type::REC_FAR};
123///
124/// let rde = RawDataElement {
125///     offset: 0,
126///     header: RecordHeader {typ: 0, sub: 10, len: 2},
127///     raw_data: vec![0u8; 0],
128///     byte_order: ByteOrder::LittleEndian
129/// };
130/// let rec: StdfRecord = (&rde).into();    // not consume
131/// let rec: StdfRecord = rde.into();       // consume
132/// println!("{:?}", rec);
133/// assert!(rec.is_type(REC_FAR));
134/// ```
135pub struct RawDataElement {
136    /// file offset of `raw_data` in file,
137    /// after header.len and before raw_data
138    ///
139    /// |-typ-|-sub-|--len--⬇️--raw..data--|
140    ///
141    /// note that the offset is relative to the
142    /// file position that runs `get_rawdata_iter`,
143    ///
144    /// it can be treated as file position **only if**
145    /// the iteration starts from beginning of the file.
146    pub offset: u64,
147
148    /// used for identifying StdfRecord types
149    pub header: RecordHeader,
150
151    /// field data of current STDF Record
152    pub raw_data: Vec<u8>,
153    pub byte_order: ByteOrder,
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157/// Element yielded by [`RawDataViewIter`](crate::stdf_file::RawDataViewIter). Unlike [`RawDataElement`],
158/// it borrows the unprocessed record bytes from the iterator's reused buffer, so it's only valid
159/// until the next call to [`RawDataViewIter::next`](crate::stdf_file::RawDataViewIter::next).
160///
161/// In return, it offers better performance and a lower heap footprint.
162///
163/// It can be converted to [`StdfRecord`], borrowed as [`StdfRecordView`], or copied into an owned
164/// [`RawDataElement`] when it needs to outlive the current iteration scope.
165///
166/// ```
167/// use rust_stdf::{RawDataElementView, ByteOrder, StdfRecord, RecordHeader, stdf_record_type::REC_FAR};
168///
169/// let raw = [0u8; 0];
170/// let rdv = RawDataElementView {
171///     offset: 0,
172///     header: RecordHeader {typ: 0, sub: 10, len: 2},
173///     raw_data: &raw,
174///     byte_order: ByteOrder::LittleEndian
175/// };
176/// let rec: StdfRecord = (&rdv).into();
177/// println!("{:?}", rec);
178/// assert!(rec.is_type(REC_FAR));
179/// ```
180pub struct RawDataElementView<'a> {
181    /// file offset of `raw_data` in file,
182    /// after header.len and before raw_data
183    ///
184    /// |-typ-|-sub-|--len--⬇️--raw..data--|
185    ///
186    /// note that the offset is relative to the
187    /// file position that runs `get_rawdata_view_iter`,
188    ///
189    /// it can be treated as file position **only if**
190    /// the iteration starts from beginning of the file.
191    pub offset: u64,
192
193    /// used for identifying StdfRecord types
194    pub header: RecordHeader,
195
196    /// field data of current STDF Record, borrowed from the iterator's reused buffer
197    pub raw_data: &'a [u8],
198    pub byte_order: ByteOrder,
199}
200
201// implementation
202
203impl RecordHeader {
204    #[inline(always)]
205    pub fn new() -> Self {
206        RecordHeader::default()
207    }
208
209    /// Construct a STDF record header from first 4 elements of given byte array,
210    /// no error would occur even if the header is invalid.
211    ///
212    /// Unless the array size is less than 4, StdfError of
213    /// `EOF` or `Unexpected EOF` will be returned
214    #[inline(always)]
215    pub fn read_from_bytes(
216        mut self,
217        raw_data: &[u8],
218        order: &ByteOrder,
219    ) -> Result<Self, StdfError> {
220        match raw_data.len() {
221            0 => Err(StdfError::new(
222                StdfErrorKind::Eof,
223                String::from("No bytes to read"),
224            )),
225            1..=3 => Err(StdfError::new(
226                StdfErrorKind::UnexpectedEof,
227                String::from("Not enough data to construct record header"),
228            )),
229            _ => {
230                let len_bytes = [raw_data[0], raw_data[1]];
231                self.len = match order {
232                    ByteOrder::LittleEndian => u16::from_le_bytes(len_bytes),
233                    ByteOrder::BigEndian => u16::from_be_bytes(len_bytes),
234                };
235                self.typ = raw_data[2];
236                self.sub = raw_data[3];
237                // return even if we have a invalid record type, let other code to handle it
238                Ok(self)
239            }
240        }
241    }
242
243    /// return the type_code of current header
244    pub fn get_type(&self) -> u64 {
245        stdf_record_type::get_code_from_typ_sub(self.typ, self.sub)
246    }
247}
248
249impl StdfRecord {
250    /// Create a StdfRecord of a given type with default data
251    ///
252    /// ```
253    /// use rust_stdf::{StdfRecord, stdf_record_type::REC_PMR};
254    ///
255    /// // create StdfRecord with a nested PMR
256    /// let new_rec = StdfRecord::new(REC_PMR);
257    ///
258    /// if let StdfRecord::PMR(pmr_rec) = new_rec {
259    ///     assert_eq!(pmr_rec.head_num, 1);
260    ///     assert_eq!(pmr_rec.site_num, 1);
261    /// } else {
262    ///     // this case will not be hit
263    /// }
264    /// ```
265    #[inline(always)]
266    pub fn new(rec_type: u64) -> Self {
267        stdf_match_expr!(record_new)
268    }
269
270    /// Create a `StdfRecord` from a `RecordHeader` with default data
271    ///
272    /// The difference between `new()` is that this method can save
273    /// the info of an invalid record header, help the caller to
274    /// debug
275    ///
276    /// ```
277    /// use rust_stdf::{StdfRecord, RecordHeader, stdf_record_type::REC_PMR};
278    ///
279    /// // create a PMR StdfRecord from header
280    /// // (1, 60)
281    /// let pmr_header = RecordHeader {typ: 1, sub: 60, len: 0 };
282    /// let new_rec = StdfRecord::new_from_header(pmr_header);
283    ///
284    /// if let StdfRecord::PMR(pmr_rec) = new_rec {
285    ///     assert_eq!(pmr_rec.head_num, 1);
286    ///     assert_eq!(pmr_rec.site_num, 1);
287    /// } else {
288    ///     // this case will not be hit
289    /// }
290    /// ```
291    #[inline(always)]
292    pub fn new_from_header(header: RecordHeader) -> Self {
293        let code = stdf_record_type::get_code_from_typ_sub(header.typ, header.sub);
294        if code == stdf_record_type::REC_INVALID {
295            // Preserve the invalid header so callers can inspect it for debugging.
296            StdfRecord::InvalidRec(header)
297        } else {
298            StdfRecord::new(code)
299        }
300    }
301
302    /// Returns the record type cdoe of the given StdfRecord,
303    /// which is defined in `rust_stdf::stdf_record_type::*` module.
304    ///
305    /// ```
306    /// use rust_stdf::{StdfRecord, stdf_record_type::*};
307    ///
308    /// // `REC_PTR` type code can be used for creating a new StdfRecord
309    /// let new_rec = StdfRecord::new(REC_PTR);
310    /// let returned_code = new_rec.get_type();
311    ///
312    /// assert_eq!(REC_PTR, returned_code);
313    ///
314    /// // type code can be used in variety of functions
315    /// // get record (typ, sub)
316    /// assert_eq!((15, 10), get_typ_sub_from_code(returned_code).unwrap());
317    /// // get record name
318    /// assert_eq!("PTR", get_rec_name_from_code(returned_code));
319    /// ```
320    #[inline(always)]
321    pub fn get_type(&self) -> u64 {
322        stdf_match_expr!(record_type)
323    }
324
325    /// Check the StdfRecord belongs the given type code(s),
326    /// it is useful for filtering the records during the parsing iteration.
327    /// ```
328    /// use rust_stdf::{StdfRecord, stdf_record_type::*};
329    ///
330    /// let new_rec = StdfRecord::new(REC_PTR);
331    ///
332    /// assert!(new_rec.is_type(REC_PTR));
333    /// assert!(new_rec.is_type(REC_PTR | REC_FTR | REC_MPR));
334    /// assert!(!new_rec.is_type(REC_FTR | REC_MPR));
335    /// ```
336    #[inline(always)]
337    pub fn is_type(&self, rec_type: u64) -> bool {
338        (self.get_type() & rec_type) != 0
339    }
340
341    /// Parse StdfRecord from byte data which **DOES NOT**
342    /// contain the record header (len, typ, sub),
343    ///
344    /// requires a mutable StdfRecord to store the parsed data
345    ///
346    /// ```
347    /// use rust_stdf::{StdfRecord, ByteOrder, stdf_record_type::*};
348    ///
349    /// let raw_with_no_header: [u8; 2] = [1, 4];
350    /// let mut new_rec = StdfRecord::new(REC_FAR);
351    /// new_rec.read_from_bytes(&raw_with_no_header, &ByteOrder::LittleEndian);
352    ///
353    /// if let StdfRecord::FAR(ref far_rec) = new_rec {
354    ///     assert_eq!(4, far_rec.stdf_ver);
355    /// }
356    /// ```
357    #[inline(always)]
358    pub fn read_from_bytes(&mut self, raw_data: &[u8], order: &ByteOrder) {
359        stdf_match_expr!(record_read)
360    }
361
362    /// Parse StdfRecord from byte data which
363    /// **contains** the record header (len, typ, sub).
364    ///
365    /// ## Error
366    /// if the input data is not a valid (wrong typ, sub),
367    /// incomplete data or incorrect byte order, [`StdfError`] will be
368    /// returned instead.
369    ///
370    /// ```
371    /// use rust_stdf::{StdfRecord, ByteOrder, stdf_record_type::*};
372    ///
373    /// let raw_with_header: [u8; 6] = [0, 2, 0, 10, 1, 4];
374    /// let new_rec = StdfRecord::read_from_bytes_with_header(&raw_with_header, &ByteOrder::BigEndian).unwrap();
375    ///
376    /// if let StdfRecord::FAR(far_rec) = new_rec {
377    ///     assert_eq!(4, far_rec.stdf_ver);
378    /// }
379    /// ```
380    #[inline(always)]
381    pub fn read_from_bytes_with_header(
382        raw_data: &[u8],
383        order: &ByteOrder,
384    ) -> Result<StdfRecord, StdfError> {
385        let header = RecordHeader::new().read_from_bytes(raw_data, order)?;
386
387        let expected_end_pos = 4 + header.len as usize;
388        if raw_data.len() < expected_end_pos {
389            return Err(StdfError::new(
390                StdfErrorKind::UnexpectedEof,
391                format!(
392                    "Length of stdf field data ({} - 4 = {}) is less than what header specified ({})",
393                    raw_data.len(),
394                    raw_data.len() - 4,
395                    header.len
396                ),
397            ));
398        }
399
400        let data_slice = &raw_data[4..expected_end_pos];
401        let mut rec = StdfRecord::new(header.get_type());
402        rec.read_from_bytes(data_slice, order);
403        Ok(rec)
404    }
405}
406
407impl<'a> StdfRecordView<'a> {
408    /// Build a view from `RecordHeader`, raw field data and byte order.
409    ///
410    /// ```
411    /// use rust_stdf::{StdfRecordView, ByteOrder, RecordHeader, stdf_record_type::REC_PTR};
412    ///
413    /// let header = RecordHeader { len: 0, typ: 15, sub: 10 }; // PTR
414    /// let raw: [u8; 0] = []; // empty field data; getters fall back to defaults
415    /// let view = StdfRecordView::read_from_bytes(header, &raw, &ByteOrder::LittleEndian);
416    ///
417    /// if let StdfRecordView::PTR(ptr_view) = view {
418    ///     assert_eq!(0, ptr_view.test_num());
419    /// }
420    /// assert!(view.is_type(REC_PTR));
421    /// ```
422    #[inline]
423    pub fn read_from_bytes(
424        header: RecordHeader,
425        raw_data: &'a [u8],
426        byte_order: &ByteOrder,
427    ) -> Self {
428        stdf_match_expr!(view_read)
429    }
430
431    /// Parse a `StdfRecordView` from byte data that **contains** the record
432    /// header (len, typ, sub).
433    ///
434    /// ## Error
435    /// Returns [`StdfError`] when the input is not a valid header or is
436    /// incomplete (the buffer is shorter than the header declares).
437    ///
438    /// ```
439    /// use rust_stdf::{StdfRecordView, ByteOrder};
440    ///
441    /// let raw_with_header: [u8; 6] = [0, 2, 0, 10, 1, 4];
442    /// let view = StdfRecordView::read_from_bytes_with_header(
443    ///     &raw_with_header,
444    ///     &ByteOrder::BigEndian,
445    /// ).unwrap();
446    ///
447    /// if let StdfRecordView::FAR(far_view) = view {
448    ///     assert_eq!(4, far_view.stdf_ver());
449    /// }
450    /// ```
451    #[inline(always)]
452    pub fn read_from_bytes_with_header(
453        raw_data: &'a [u8],
454        order: &ByteOrder,
455    ) -> Result<Self, StdfError> {
456        let header = RecordHeader::new().read_from_bytes(raw_data, order)?;
457
458        let expected_end_pos = 4 + header.len as usize;
459        if raw_data.len() < expected_end_pos {
460            return Err(StdfError::new(
461                StdfErrorKind::UnexpectedEof,
462                format!(
463                    "Length of stdf field data ({} - 4 = {}) is less than what header specified ({})",
464                    raw_data.len(),
465                    raw_data.len() - 4,
466                    header.len
467                ),
468            ));
469        }
470
471        let data_slice = &raw_data[4..expected_end_pos];
472        Ok(Self::read_from_bytes(header, data_slice, order))
473    }
474
475    /// Return the record type code (see `stdf_record_type::*`).
476    #[inline(always)]
477    pub fn get_type(&self) -> u64 {
478        stdf_match_expr!(view_type)
479    }
480
481    /// Check whether this view belongs to the given record type(s).
482    #[inline(always)]
483    pub fn is_type(&self, rec_type: u64) -> bool {
484        (self.get_type() & rec_type) != 0
485    }
486
487    /// Parse this borrowed view into an owned [`StdfRecord`], escaping the
488    /// borrow of the underlying buffer.
489    #[inline]
490    pub fn to_owned(&self) -> StdfRecord {
491        stdf_match_expr!(view_to_owned)
492    }
493}
494
495impl RawDataElement {
496    #[inline(always)]
497    pub fn is_type(&self, rec_type: u64) -> bool {
498        (self.header.get_type() & rec_type) != 0
499    }
500}
501
502impl From<&RawDataElement> for StdfRecord {
503    /// it will NOT consume the input RawDataElement
504    #[inline(always)]
505    fn from(raw_element: &RawDataElement) -> Self {
506        let mut rec = StdfRecord::new_from_header(raw_element.header);
507        rec.read_from_bytes(&raw_element.raw_data, &raw_element.byte_order);
508        rec
509    }
510}
511
512impl<'a> From<&'a RawDataElement> for StdfRecordView<'a> {
513    /// Build a zero-copy view; does not consume the input.
514    #[inline]
515    fn from(raw_element: &'a RawDataElement) -> Self {
516        Self::read_from_bytes(
517            raw_element.header,
518            &raw_element.raw_data,
519            &raw_element.byte_order,
520        )
521    }
522}
523
524impl From<RawDataElement> for StdfRecord {
525    /// it will consume the input RawDataElement
526    #[inline(always)]
527    fn from(raw_element: RawDataElement) -> Self {
528        let mut rec = StdfRecord::new_from_header(raw_element.header);
529        rec.read_from_bytes(&raw_element.raw_data, &raw_element.byte_order);
530        rec
531    }
532}
533
534impl RawDataElementView<'_> {
535    #[inline(always)]
536    pub fn is_type(&self, rec_type: u64) -> bool {
537        (self.header.get_type() & rec_type) != 0
538    }
539}
540
541impl From<&RawDataElementView<'_>> for StdfRecord {
542    /// Parse the borrowed bytes into an owned record; does not consume the input.
543    #[inline(always)]
544    fn from(raw_view: &RawDataElementView<'_>) -> Self {
545        let mut rec = StdfRecord::new_from_header(raw_view.header);
546        rec.read_from_bytes(raw_view.raw_data, &raw_view.byte_order);
547        rec
548    }
549}
550
551impl<'a> From<&RawDataElementView<'a>> for StdfRecordView<'a> {
552    /// Build a zero-copy view; does not consume the input.
553    #[inline]
554    fn from(raw_view: &RawDataElementView<'a>) -> Self {
555        Self::read_from_bytes(raw_view.header, raw_view.raw_data, &raw_view.byte_order)
556    }
557}
558
559impl From<&RawDataElementView<'_>> for RawDataElement {
560    /// Copy the borrowed bytes into an owned [`RawDataElement`]; does not consume the input.
561    #[inline(always)]
562    fn from(raw_view: &RawDataElementView<'_>) -> Self {
563        RawDataElement {
564            offset: raw_view.offset,
565            header: raw_view.header,
566            raw_data: raw_view.raw_data.to_vec(),
567            byte_order: raw_view.byte_order,
568        }
569    }
570}
571
572impl<'a> From<&StdfRecordView<'a>> for StdfRecord {
573    /// Parse the borrowed view into an owned record; does not consume the view.
574    #[inline(always)]
575    fn from(view: &StdfRecordView<'a>) -> Self {
576        view.to_owned()
577    }
578}