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}