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 gets record (typ, sub)
57 /// from record type constant.
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 gets record type constant
71 /// from record (typ, sub).
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 gets record name string
85 /// from record type constant.
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 gets the record type constant
99 /// from record name string.
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/// Element yielded by [`RawDataIter`](crate::stdf_file::RawDataIter). It owns unprocessed STDF record data,
118/// and can be converted to [`StdfRecord`], or borrowed as [`StdfRecordView`].
119///
120/// ```
121/// use rust_stdf::{RawDataElement, ByteOrder, StdfRecord, RecordHeader, stdf_record_type::REC_FAR};
122///
123/// let rde = RawDataElement {
124/// offset: 0,
125/// header: RecordHeader {typ: 0, sub: 10, len: 2},
126/// raw_data: vec![0u8; 0],
127/// byte_order: ByteOrder::LittleEndian
128/// };
129/// let rec: StdfRecord = (&rde).into(); // not consume
130/// let rec: StdfRecord = rde.into(); // consume
131/// println!("{:?}", rec);
132/// assert!(rec.is_type(REC_FAR));
133/// ```
134#[derive(Debug, Clone, PartialEq, Eq)]
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/// Element yielded by [`RawDataViewIter`](crate::stdf_file::RawDataViewIter). Unlike [`RawDataElement`],
157/// it borrows the unprocessed record bytes from the iterator's reused buffer, so it's only valid
158/// until the next call to [`RawDataViewIter::next`](crate::stdf_file::RawDataViewIter::next).
159///
160/// In return, it offers better performance and lower heap footprints.
161///
162/// It can be converted to [`StdfRecord`], borrowed as [`StdfRecordView`], or copied into an owned
163/// [`RawDataElement`] when it needs to outlive the current iteration scope.
164///
165/// ```
166/// use rust_stdf::{RawDataElementView, ByteOrder, StdfRecord, RecordHeader, stdf_record_type::REC_FAR};
167///
168/// let raw = [0u8; 0];
169/// let rdv = RawDataElementView {
170/// offset: 0,
171/// header: RecordHeader {typ: 0, sub: 10, len: 2},
172/// raw_data: &raw,
173/// byte_order: ByteOrder::LittleEndian
174/// };
175/// let rec: StdfRecord = (&rdv).into();
176/// println!("{:?}", rec);
177/// assert!(rec.is_type(REC_FAR));
178/// ```
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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 unknown/reserved 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 match code {
295 stdf_record_type::REC_RESERVE => {
296 let mut rec = ReservedRec::new();
297 rec.typ = header.typ;
298 rec.sub = header.sub;
299 StdfRecord::ReservedRec(rec)
300 }
301 stdf_record_type::REC_UNKNOWN => {
302 let mut rec = ReservedRec::new();
303 rec.typ = header.typ;
304 rec.sub = header.sub;
305 StdfRecord::UnknownRec(rec)
306 }
307 _ => StdfRecord::new(code),
308 }
309 }
310
311 /// Returns the record type cdoe of the given StdfRecord,
312 /// which is defined in `rust_stdf::stdf_record_type::*` module.
313 ///
314 /// ```
315 /// use rust_stdf::{StdfRecord, stdf_record_type::*};
316 ///
317 /// // `REC_PTR` type code can be used for creating a new StdfRecord
318 /// let new_rec = StdfRecord::new(REC_PTR);
319 /// let returned_code = new_rec.get_type();
320 ///
321 /// assert_eq!(REC_PTR, returned_code);
322 ///
323 /// // type code can be used in variety of functions
324 /// // get record (typ, sub)
325 /// assert_eq!((15, 10), get_typ_sub_from_code(returned_code).unwrap());
326 /// // get record name
327 /// assert_eq!("PTR", get_rec_name_from_code(returned_code));
328 /// ```
329 #[inline(always)]
330 pub fn get_type(&self) -> u64 {
331 stdf_match_expr!(record_type)
332 }
333
334 /// Check the StdfRecord belongs the given type code(s),
335 /// it is useful for filtering the records during the parsing iteration.
336 /// ```
337 /// use rust_stdf::{StdfRecord, stdf_record_type::*};
338 ///
339 /// let new_rec = StdfRecord::new(REC_PTR);
340 ///
341 /// assert!(new_rec.is_type(REC_PTR));
342 /// assert!(new_rec.is_type(REC_PTR | REC_FTR | REC_MPR));
343 /// assert!(!new_rec.is_type(REC_FTR | REC_MPR));
344 /// ```
345 #[inline(always)]
346 pub fn is_type(&self, rec_type: u64) -> bool {
347 (self.get_type() & rec_type) != 0
348 }
349
350 /// Parse StdfRecord from byte data which **DOES NOT**
351 /// contain the record header (len, typ, sub),
352 ///
353 /// requires a mutable StdfRecord to store the parsed data
354 ///
355 /// ```
356 /// use rust_stdf::{StdfRecord, ByteOrder, stdf_record_type::*};
357 ///
358 /// let raw_with_no_header: [u8; 2] = [1, 4];
359 /// let mut new_rec = StdfRecord::new(REC_FAR);
360 /// new_rec.read_from_bytes(&raw_with_no_header, &ByteOrder::LittleEndian);
361 ///
362 /// if let StdfRecord::FAR(ref far_rec) = new_rec {
363 /// assert_eq!(4, far_rec.stdf_ver);
364 /// }
365 /// ```
366 #[inline(always)]
367 pub fn read_from_bytes(&mut self, raw_data: &[u8], order: &ByteOrder) {
368 stdf_match_expr!(record_read)
369 }
370
371 /// Parse StdfRecord from byte data which
372 /// **contains** the record header (len, typ, sub).
373 ///
374 /// ## Error
375 /// if the input data is not a valid (wrong typ, sub),
376 /// incomplete data or incorrect byte order, [`StdfError`] will be
377 /// returned instead.
378 ///
379 /// ```
380 /// use rust_stdf::{StdfRecord, ByteOrder, stdf_record_type::*};
381 ///
382 /// let raw_with_header: [u8; 6] = [0, 2, 0, 10, 1, 4];
383 /// let new_rec = StdfRecord::read_from_bytes_with_header(&raw_with_header, &ByteOrder::BigEndian).unwrap();
384 ///
385 /// if let StdfRecord::FAR(far_rec) = new_rec {
386 /// assert_eq!(4, far_rec.stdf_ver);
387 /// }
388 /// ```
389 #[inline(always)]
390 pub fn read_from_bytes_with_header(
391 raw_data: &[u8],
392 order: &ByteOrder,
393 ) -> Result<StdfRecord, StdfError> {
394 let header = RecordHeader::new().read_from_bytes(raw_data, order)?;
395
396 let expected_end_pos = 4 + header.len as usize;
397 if raw_data.len() < expected_end_pos {
398 return Err(StdfError::new(
399 StdfErrorKind::UnexpectedEof,
400 format!(
401 "Length of stdf field data ({} - 4 = {}) is less than what header specified ({})",
402 raw_data.len(),
403 raw_data.len() - 4,
404 header.len
405 ),
406 ));
407 }
408
409 let data_slice = &raw_data[4..expected_end_pos];
410 let mut rec = StdfRecord::new_from_header(&header);
411 rec.read_from_bytes(data_slice, order);
412 Ok(rec)
413 }
414}
415
416impl<'a> StdfRecordView<'a> {
417 /// Build a view from `RecordHeader`, raw field data and byte order.
418 ///
419 /// ```
420 /// use rust_stdf::{StdfRecordView, ByteOrder, RecordHeader, stdf_record_type::REC_PTR};
421 ///
422 /// let header = RecordHeader { len: 0, typ: 15, sub: 10 }; // PTR
423 /// let raw: [u8; 0] = []; // empty field data; getters fall back to defaults
424 /// let view = StdfRecordView::read_from_bytes(header, &raw, &ByteOrder::LittleEndian);
425 ///
426 /// if let StdfRecordView::PTR(ptr_view) = view {
427 /// assert_eq!(0, ptr_view.test_num());
428 /// }
429 /// assert!(view.is_type(REC_PTR));
430 /// ```
431 #[inline]
432 pub fn read_from_bytes(
433 header: RecordHeader,
434 raw_data: &'a [u8],
435 byte_order: &ByteOrder,
436 ) -> Self {
437 stdf_match_expr!(view_read)
438 }
439
440 /// Parse a `StdfRecordView` from byte data that **contains** the record
441 /// header (len, typ, sub).
442 ///
443 /// ## Error
444 /// Returns [`StdfError`] when the input is not a valid header or is
445 /// incomplete (the buffer is shorter than the header declares).
446 ///
447 /// ```
448 /// use rust_stdf::{StdfRecordView, ByteOrder};
449 ///
450 /// let raw_with_header: [u8; 6] = [0, 2, 0, 10, 1, 4];
451 /// let view = StdfRecordView::read_from_bytes_with_header(
452 /// &raw_with_header,
453 /// &ByteOrder::BigEndian,
454 /// ).unwrap();
455 ///
456 /// if let StdfRecordView::FAR(far_view) = view {
457 /// assert_eq!(4, far_view.stdf_ver());
458 /// }
459 /// ```
460 #[inline(always)]
461 pub fn read_from_bytes_with_header(
462 raw_data: &'a [u8],
463 order: &ByteOrder,
464 ) -> Result<Self, StdfError> {
465 let header = RecordHeader::new().read_from_bytes(raw_data, order)?;
466
467 let expected_end_pos = 4 + header.len as usize;
468 if raw_data.len() < expected_end_pos {
469 return Err(StdfError::new(
470 StdfErrorKind::UnexpectedEof,
471 format!(
472 "Length of stdf field data ({} - 4 = {}) is less than what header specified ({})",
473 raw_data.len(),
474 raw_data.len() - 4,
475 header.len
476 ),
477 ));
478 }
479
480 let data_slice = &raw_data[4..expected_end_pos];
481 Ok(Self::read_from_bytes(header, data_slice, order))
482 }
483
484 /// Return the record type code (see `stdf_record_type::*`).
485 #[inline(always)]
486 pub fn get_type(&self) -> u64 {
487 stdf_match_expr!(view_type)
488 }
489
490 /// Check whether this view belongs to the given record type(s).
491 #[inline(always)]
492 pub fn is_type(&self, rec_type: u64) -> bool {
493 (self.get_type() & rec_type) != 0
494 }
495
496 /// Parse this borrowed view into an owned [`StdfRecord`], escaping the
497 /// borrow of the underlying buffer.
498 #[inline]
499 pub fn to_owned(&self) -> StdfRecord {
500 stdf_match_expr!(view_to_owned)
501 }
502}
503
504impl RawDataElement {
505 #[inline(always)]
506 pub fn is_type(&self, rec_type: u64) -> bool {
507 (self.header.get_type() & rec_type) != 0
508 }
509}
510
511impl From<&RawDataElement> for StdfRecord {
512 /// It will NOT consume the input RawDataElement
513 #[inline(always)]
514 fn from(raw_element: &RawDataElement) -> Self {
515 let mut rec = StdfRecord::new_from_header(&raw_element.header);
516 rec.read_from_bytes(&raw_element.raw_data, &raw_element.byte_order);
517 rec
518 }
519}
520
521impl<'a> From<&'a RawDataElement> for StdfRecordView<'a> {
522 /// Build a zero-copy view; does not consume the input.
523 #[inline]
524 fn from(raw_element: &'a RawDataElement) -> Self {
525 Self::read_from_bytes(
526 raw_element.header,
527 &raw_element.raw_data,
528 &raw_element.byte_order,
529 )
530 }
531}
532
533impl From<RawDataElement> for StdfRecord {
534 /// It will consume the input RawDataElement
535 #[inline(always)]
536 fn from(raw_element: RawDataElement) -> Self {
537 let mut rec = StdfRecord::new_from_header(&raw_element.header);
538 rec.read_from_bytes(&raw_element.raw_data, &raw_element.byte_order);
539 rec
540 }
541}
542
543impl RawDataElementView<'_> {
544 #[inline(always)]
545 pub fn is_type(&self, rec_type: u64) -> bool {
546 (self.header.get_type() & rec_type) != 0
547 }
548}
549
550impl From<&RawDataElementView<'_>> for StdfRecord {
551 /// Parse the borrowed bytes into an owned record; does not consume the input.
552 #[inline(always)]
553 fn from(raw_view: &RawDataElementView<'_>) -> Self {
554 let mut rec = StdfRecord::new_from_header(&raw_view.header);
555 rec.read_from_bytes(raw_view.raw_data, &raw_view.byte_order);
556 rec
557 }
558}
559
560impl<'a> From<&RawDataElementView<'a>> for StdfRecordView<'a> {
561 /// Build a zero-copy view; does not consume the input.
562 #[inline]
563 fn from(raw_view: &RawDataElementView<'a>) -> Self {
564 Self::read_from_bytes(raw_view.header, raw_view.raw_data, &raw_view.byte_order)
565 }
566}
567
568impl From<&RawDataElementView<'_>> for RawDataElement {
569 /// Copy the borrowed bytes into an owned [`RawDataElement`]; does not consume the input.
570 #[inline(always)]
571 fn from(raw_view: &RawDataElementView<'_>) -> Self {
572 RawDataElement {
573 offset: raw_view.offset,
574 header: raw_view.header,
575 raw_data: raw_view.raw_data.to_vec(),
576 byte_order: raw_view.byte_order,
577 }
578 }
579}
580
581impl<'a> From<&StdfRecordView<'a>> for StdfRecord {
582 /// Parse the borrowed view into an owned record; does not consume the view.
583 #[inline(always)]
584 fn from(view: &StdfRecordView<'a>) -> Self {
585 view.to_owned()
586 }
587}