tdms/
segment.rs

1use crate::data_type::{TDMSValue, TdmsDataType};
2use crate::{to_i32, to_u32, to_u64};
3use crate::{
4    Big, General, InvalidDAQmxDataIndex, InvalidSegment, Little, StringConversionError, TdmsError,
5};
6use indexmap::{indexmap, IndexMap};
7use std::io::{Read, Seek};
8
9/// These are bitmasks for the Table of Contents byte.
10const K_TOC_META_DATA: u32 = 1 << 1;
11// this flag represents a segment who's channel list/order has been changed from the previous segments
12// and therefore a new order for processing the raw data must be followed
13const K_TOC_NEW_OBJ_LIST: u32 = 1 << 2;
14const K_TOC_RAW_DATA: u32 = 1 << 3;
15const K_TOC_INTERLEAVED_DATA: u32 = 1 << 5;
16const K_TOC_BIG_ENDIAN: u32 = 1 << 6;
17const K_TOC_DAQMX_RAW_DATA: u32 = 1 << 7;
18
19/// Ease of use enum for determining how to read numerical values.
20#[derive(Clone, Copy, Debug)]
21pub enum Endianness {
22    Little,
23    Big,
24}
25
26#[derive(Debug, Clone)]
27/// `Segment` represents an entire TDMS File Segment and potentially its raw data.
28pub struct Segment {
29    pub lead_in: LeadIn,
30    pub metadata: Option<Metadata>,
31    pub start_pos: u64,
32    pub end_pos: u64,
33    pub groups: IndexMap<GroupPath, Option<IndexMap<ChannelPath, Channel>>>,
34    pub chunk_size: u64,
35}
36
37/// GroupPath is a simple alias to allow our function signatures to be more telling
38pub type GroupPath = String;
39/// ChannelPath is a simple alias to allow our function signatures to be more telling
40pub type ChannelPath = String;
41
42#[derive(Clone, Debug)]
43pub struct Channel {
44    pub full_path: String,
45    pub group_path: String,
46    pub path: String,
47    pub data_type: TdmsDataType,
48    pub raw_data_index: Option<RawDataIndex>,
49    pub daqmx_data_index: Option<DAQmxDataIndex>,
50    pub properties: Vec<MetadataProperty>,
51    pub chunk_positions: Vec<ChannelPositions>,
52    pub string_offset_pos: Option<ChannelPositions>,
53    pub interleaved_offset: u64,
54}
55
56#[derive(Clone, Debug, Copy)]
57pub struct ChannelPositions(pub u64, pub u64);
58
59impl Segment {
60    /// `new` expects a reader who's cursor position is at the start of a new TDMS segment.
61    /// You will see an InvalidSegment error return if the reader position isn't correct as the first
62    /// byte read will not be the correct tag for a segment. The segment will hold on to the original
63    /// reader in order to be able to return data not read into memory
64    pub fn new<R: Read + Seek>(
65        r: &mut R,
66        previous_segment: Option<&Segment>,
67    ) -> Result<Self, TdmsError> {
68        let segment_start_pos = r.stream_position()?;
69        let mut lead_in = [0; 28];
70
71        r.read(&mut lead_in[..])?;
72
73        let lead_in = LeadIn::from_bytes(&lead_in)?;
74
75        // calculate the end position by taking the start and adding the offset plus lead in bytes
76        let segment_end_pos = lead_in.next_segment_offset + 28 + segment_start_pos;
77
78        let endianness = if lead_in.table_of_contents & K_TOC_BIG_ENDIAN != 0 {
79            Big
80        } else {
81            Little
82        };
83
84        let mut metadata: Option<Metadata> = None;
85        if lead_in.table_of_contents & K_TOC_META_DATA != 0 {
86            metadata = Some(Metadata::from_reader(endianness, r)?);
87        }
88
89        // if we have have metadata, load up group and channel list for the segment - I debated
90        // somehow building this list dynamically as we read the file but honestly the performance
91        // hit according to benches was minimal and this makes a cleaner set of function boundaries
92        // and lets us get away from passing in mutable state all over the place
93        let mut groups: IndexMap<GroupPath, Option<IndexMap<ChannelPath, Channel>>> =
94            IndexMap::<GroupPath, Option<IndexMap<ChannelPath, Channel>>>::new();
95
96        // this variable will tell us where we're at in the raw data of the channel, allowing us to
97        // set start and end thresholds of the channel's data itself
98        let mut data_pos: u64 = segment_start_pos + lead_in.raw_data_offset;
99        let mut interleaved_total_size: u64 = 0;
100        let mut chunk_size: u64 = 0;
101
102        match &mut metadata {
103            Some(metadata) => {
104                for obj in &mut metadata.objects {
105                    let path = obj.object_path.clone();
106                    let paths: Vec<&str> = path.split("/").collect();
107
108                    if obj.object_path == "/" || paths.len() < 3 {
109                        continue;
110                    }
111                    let mut data_type: TdmsDataType = TdmsDataType::Void;
112
113                    if previous_segment.is_some()
114                        && obj.raw_data_index.is_none()
115                        && obj.daqmx_data_index.is_none()
116                    {
117                        match previous_segment
118                            .unwrap()
119                            .get_channel(rem_quotes(paths[1]), rem_quotes(paths[2]))
120                        {
121                            None => {}
122                            Some(c) => {
123                                obj.raw_data_index = match &c.raw_data_index {
124                                    None => None,
125                                    Some(r) => Some(r.clone()),
126                                };
127
128                                obj.daqmx_data_index = match &c.daqmx_data_index {
129                                    None => None,
130                                    Some(d) => Some(d.clone()),
131                                }
132                            }
133                        }
134                    }
135
136                    match &obj.raw_data_index {
137                        None => {}
138                        Some(index) => data_type = index.data_type,
139                    }
140
141                    match &obj.daqmx_data_index {
142                        None => {}
143                        Some(index) => data_type = index.data_type,
144                    }
145
146                    // add to the total interleaved size so we can calculate the offset later if needed
147                    interleaved_total_size += TdmsDataType::get_size(data_type) as u64;
148
149                    if paths.len() >= 2 && paths[1] != "" {
150                        if !groups.contains_key(rem_quotes(paths[1])) {
151                            let _ = groups.insert(rem_quotes(paths[1]).to_string(), None);
152                        }
153                    }
154
155                    if paths.len() >= 3 && paths[2] != "" {
156                        let map = groups.get_mut(rem_quotes(paths[1]));
157                        let mut start_pos = data_pos.clone();
158                        let mut end_pos = 0;
159
160                        let mut string_offset_pos: Option<ChannelPositions> = None;
161                        let raw_data_index = match &obj.raw_data_index {
162                            Some(index) => {
163                                let type_size = TdmsDataType::get_size(index.data_type);
164
165                                // if not interleaved, the end threshold comes at chunk end
166                                if lead_in.table_of_contents & K_TOC_INTERLEAVED_DATA == 0 {
167                                    if index.data_type == TdmsDataType::String
168                                        && index.number_of_bytes.is_some()
169                                    {
170                                        start_pos = start_pos + index.number_of_values * 4;
171                                        string_offset_pos = Some(ChannelPositions(data_pos, data_pos + index.number_of_values * 4));
172
173                                        data_pos = data_pos + index.number_of_bytes.unwrap();
174                                        end_pos = data_pos.clone();
175                                        chunk_size += index.number_of_bytes.unwrap();
176                                    } else {
177                                        data_pos = data_pos
178                                            + (type_size as u64
179                                                * index.array_dimension as u64
180                                                * index.number_of_values);
181
182                                        end_pos = data_pos.clone();
183                                        chunk_size += type_size as u64
184                                            * index.array_dimension as u64
185                                            * index.number_of_values
186                                    }
187                                }
188
189                                Some(index.clone())
190                            }
191                            None => None,
192                        };
193
194                        let daqmx_data_index = match &obj.daqmx_data_index {
195                            Some(index) => Some(index.clone()),
196                            None => None,
197                        };
198
199                        let channel = Channel {
200                            full_path: obj.object_path.clone(),
201                            group_path: rem_quotes(paths[1]).to_string(),
202                            path: rem_quotes(paths[2]).to_string(),
203                            data_type,
204                            raw_data_index,
205                            daqmx_data_index,
206                            properties: obj.properties.clone(),
207                            chunk_positions: vec![ChannelPositions(start_pos, end_pos)],
208                            // this will be calculated later as we need all the channels information
209                            // prior to calculating this offset
210                            interleaved_offset: 0,
211                            string_offset_pos
212                        };
213
214                        match map {
215                            Some(map) => {
216                                match map {
217                                    Some(map) => {
218                                        map.insert(rem_quotes(paths[2]).to_string(), channel);
219                                    }
220                                    None => {
221                                        groups.insert(
222                                    rem_quotes(paths[1]).to_string(),
223                                    Some(indexmap! {rem_quotes(paths[2]).to_string() => channel}),
224                                );
225                                    }
226                                }
227                            }
228                            None => (),
229                        }
230                    }
231                }
232            }
233            _ => (),
234        }
235
236        if lead_in.table_of_contents & K_TOC_INTERLEAVED_DATA != 0 {
237            for (_, channels) in groups.iter_mut() {
238                match channels {
239                    None => continue,
240                    Some(channels) => {
241                        for (_, channel) in channels.iter_mut() {
242                            let size = TdmsDataType::get_size(channel.data_type);
243
244                            // offset tells the iterator how many bytes to move to the next value
245                            channel.interleaved_offset = interleaved_total_size - size as u64;
246                            // update the end_pos_threshold  now that we have an idea of setup
247                            match channel.chunk_positions.get_mut(0) {
248                                None => (),
249                                Some(positions) => {
250                                    positions.1 = chunk_size - channel.interleaved_offset
251                                        + interleaved_total_size
252                                }
253                            }
254
255                            interleaved_total_size += size as u64;
256                        }
257                    }
258                }
259            }
260        }
261
262        // now we need to iterate yet again in order to build the start/end positions of each chunk
263        for (_, channels) in groups.iter_mut() {
264            match channels {
265                None => continue,
266                Some(channels) => {
267                    for (_, channel) in channels.iter_mut() {
268                        if channel.data_type == TdmsDataType::DAQmxRawData {
269                            continue;
270                        }
271
272                        let mut i = 0;
273                        loop {
274                            let ChannelPositions(prev_start, prev_end) =
275                                match channel.chunk_positions.get(i) {
276                                    None => {
277                                        return Err(General(String::from(
278                                            "unable to fetch previous chunk positions",
279                                        )))
280                                    }
281                                    Some(p) => p,
282                                };
283
284                            let new_start = prev_start + chunk_size;
285                            let mut new_end = prev_end + chunk_size;
286
287                            if new_start > segment_end_pos {
288                                break;
289                            }
290
291                            if new_end > segment_end_pos {
292                                new_end = segment_end_pos;
293                            }
294
295                            channel
296                                .chunk_positions
297                                .push(ChannelPositions(new_start, new_end));
298                            i += 1;
299                        }
300                    }
301                }
302            }
303        }
304
305        //
306
307        return Ok(Segment {
308            lead_in,
309            metadata,
310            start_pos: segment_start_pos,
311            // lead in plus offset
312            end_pos: segment_end_pos,
313            groups,
314            chunk_size,
315        });
316    }
317
318    /// this function is not accurate unless the lead in portion of the segment has been read
319    pub fn endianess(&self) -> Endianness {
320        return if self.lead_in.table_of_contents & K_TOC_BIG_ENDIAN != 0 {
321            Big
322        } else {
323            Little
324        };
325    }
326
327    /// this function is not accurate unless the lead in portion of the segment has been read
328    pub fn has_interleaved_data(&self) -> bool {
329        return self.lead_in.table_of_contents & K_TOC_INTERLEAVED_DATA != 0;
330    }
331
332    /// this function is not accurate unless the lead in portion of the segment has been read
333    pub fn has_daqmx_raw_data(&self) -> bool {
334        return self.lead_in.table_of_contents & K_TOC_DAQMX_RAW_DATA != 0;
335    }
336
337    /// this function is not accurate unless the lead in portion of the segment has been read
338    pub fn has_raw_data(&self) -> bool {
339        return self.lead_in.table_of_contents & K_TOC_RAW_DATA != 0;
340    }
341
342    /// this function is not accurate unless the lead in portion of the segment has been read
343    pub fn has_new_obj_list(&self) -> bool {
344        return self.lead_in.table_of_contents & K_TOC_NEW_OBJ_LIST != 0;
345    }
346
347    pub fn get_channel_mut(&mut self, group_path: &str, path: &str) -> Option<&mut Channel> {
348        let group = match self.groups.get_mut(group_path) {
349            None => return None,
350            Some(g) => g,
351        };
352
353        let channels = match group {
354            None => return None,
355            Some(c) => c,
356        };
357
358        return channels.get_mut(path);
359    }
360
361    pub fn get_channel(&self, group_path: &str, path: &str) -> Option<&Channel> {
362        let group = match self.groups.get(group_path) {
363            None => return None,
364            Some(g) => g,
365        };
366
367        let channels = match group {
368            None => return None,
369            Some(c) => c,
370        };
371
372        return channels.get(path);
373    }
374}
375
376#[derive(Debug, Clone)]
377/// `LeadIn` represents the 28 bytes representing the lead in to a TDMS Segment.
378pub struct LeadIn {
379    pub tag: [u8; 4],
380    pub table_of_contents: u32,
381    pub version_number: u32,
382    pub next_segment_offset: u64,
383    pub raw_data_offset: u64,
384}
385
386impl LeadIn {
387    /// `from_bytes` accepts a 28 byte array which represents the lead-in to a segment. This is hardcoded
388    /// as there are no dynamic lengths in this portion of a segment
389    pub fn from_bytes(lead_in: &[u8; 28]) -> Result<Self, TdmsError> {
390        let mut tag: [u8; 4] = [0; 4];
391        tag.clone_from_slice(&lead_in[0..4]);
392
393        if hex::encode(tag) != String::from("5444536d") {
394            return Err(InvalidSegment());
395        }
396
397        let mut toc: [u8; 4] = [0; 4];
398        toc.clone_from_slice(&lead_in[4..8]);
399
400        // the Table of Contents is always in little endian format regardless if the rest of the segment
401        // is in big endian
402        let table_of_contents = u32::from_le_bytes(toc);
403
404        let mut version: [u8; 4] = [0; 4];
405        version.clone_from_slice(&lead_in[8..12]);
406
407        let version_number = if table_of_contents & K_TOC_BIG_ENDIAN != 0 {
408            u32::from_be_bytes(version)
409        } else {
410            u32::from_le_bytes(version)
411        };
412
413        let mut offset: [u8; 8] = [0; 8];
414        offset.clone_from_slice(&lead_in[12..20]);
415
416        let next_segment_offset = if table_of_contents & K_TOC_BIG_ENDIAN != 0 {
417            u64::from_be_bytes(offset)
418        } else {
419            u64::from_le_bytes(offset)
420        };
421
422        let mut raw_offset: [u8; 8] = [0; 8];
423        raw_offset.clone_from_slice(&lead_in[20..28]);
424
425        let raw_data_offset = if table_of_contents & K_TOC_BIG_ENDIAN != 0 {
426            u64::from_be_bytes(raw_offset) + 28
427        } else {
428            u64::from_le_bytes(raw_offset) + 28
429        };
430
431        return Ok(LeadIn {
432            tag,
433            table_of_contents,
434            version_number,
435            next_segment_offset,
436            raw_data_offset,
437        });
438    }
439}
440
441#[derive(Debug, Clone)]
442/// `Metadata` represents the collection of metadata objects for a segment in the order in which they
443/// were read
444pub struct Metadata {
445    pub number_of_objects: u32,
446    pub objects: Vec<MetadataObject>,
447}
448
449#[derive(Debug, Clone)]
450/// `MetadataObject` represents information that is not raw data associated with the segment. May
451/// contain DAQmx raw data index, a standard index, or nothing at all.
452pub struct MetadataObject {
453    pub object_path: String,
454    pub raw_data_index: Option<RawDataIndex>,
455    pub daqmx_data_index: Option<DAQmxDataIndex>,
456    pub properties: Vec<MetadataProperty>,
457}
458
459impl Metadata {
460    /// from_reader accepts an open reader and attempts to read metadata from the currently selected
461    /// segment. Note that you must have read the segment's lead in information completely before
462    /// attempting to use this function
463    pub fn from_reader<R: Read + Seek>(
464        endianness: Endianness,
465        r: &mut R,
466    ) -> Result<Self, TdmsError> {
467        let mut buf: [u8; 4] = [0; 4];
468        r.read(&mut buf)?;
469
470        let number_of_objects = match endianness {
471            Little => u32::from_le_bytes(buf),
472            Big => u32::from_be_bytes(buf),
473        };
474
475        let mut objects: Vec<MetadataObject> = vec![];
476
477        for _ in 0..number_of_objects {
478            let mut buf: [u8; 4] = [0; 4];
479            r.read(&mut buf)?;
480
481            let length: u32 = to_u32!(buf, endianness);
482
483            // must be a vec due to variable length
484            let length = match usize::try_from(length) {
485                Ok(l) => l,
486                Err(_) => {
487                    return Err(General(String::from(
488                        "error converting strength length to system size",
489                    )))
490                }
491            };
492
493            let mut path = vec![0; length];
494            r.read_exact(&mut path)?;
495
496            // all strings are UTF8 encoded in TDMS files, most prefixed by the length attribute
497            // like above
498            let object_path = match String::from_utf8(path) {
499                Ok(n) => n,
500                Err(_) => {
501                    return Err(StringConversionError(String::from(
502                        "unable to convert object path",
503                    )))
504                }
505            };
506
507            let mut buf: [u8; 4] = [0; 4];
508            r.read_exact(&mut buf)?;
509            let mut raw_data_index: Option<RawDataIndex> = None;
510            let mut daqmx_data_index: Option<DAQmxDataIndex> = None;
511
512            let first_byte: u32 = to_u32!(buf, endianness);
513
514            // indicates format changing scaler
515            if first_byte == 0x69120000 || first_byte == 0x00001269 {
516                let index = DAQmxDataIndex::from_reader(endianness, r, true)?;
517                daqmx_data_index = Some(index);
518                // indicates digital line scaler
519            } else if first_byte == 0x69130000
520                || first_byte == 0x0000126A
521                || first_byte == 0x00001369
522            {
523                let index = DAQmxDataIndex::from_reader(endianness, r, false)?;
524                daqmx_data_index = Some(index);
525            } else {
526                if first_byte != 0xFFFFFFFF && first_byte != 0x0000000 {
527                    raw_data_index = Some(RawDataIndex::from_reader(endianness, r)?)
528                }
529            }
530
531            r.read_exact(&mut buf)?;
532            let num_of_properties: u32 = to_u32!(buf, endianness);
533
534            // now we iterate through all the properties for the object
535            let mut properties: Vec<MetadataProperty> = vec![];
536            for _ in 0..num_of_properties {
537                match MetadataProperty::from_reader(endianness, r) {
538                    Ok(p) => properties.push(p),
539                    Err(e) => return Err(e),
540                };
541            }
542
543            objects.push(MetadataObject {
544                object_path,
545                raw_data_index,
546                daqmx_data_index,
547                properties,
548            });
549        }
550
551        return Ok(Metadata {
552            number_of_objects,
553            objects,
554        });
555    }
556}
557
558#[derive(Debug, Clone)]
559pub struct RawDataIndex {
560    pub data_type: TdmsDataType,
561    pub array_dimension: u32, // should only ever be 1
562    pub number_of_values: u64,
563    pub number_of_bytes: Option<u64>, // only valid if data type is TDMS String
564}
565
566impl RawDataIndex {
567    pub fn from_reader<R: Read + Seek>(
568        endianness: Endianness,
569        r: &mut R,
570    ) -> Result<Self, TdmsError> {
571        let mut buf: [u8; 4] = [0; 4];
572
573        // now we check the data type
574        r.read_exact(&mut buf)?;
575        let data_type = to_i32!(buf, endianness);
576
577        let data_type = TdmsDataType::try_from(data_type)?;
578
579        r.read_exact(&mut buf)?;
580        let array_dimension: u32 = to_u32!(buf, endianness);
581
582        let mut buf: [u8; 8] = [0; 8];
583        r.read_exact(&mut buf)?;
584        let number_of_values = to_u64!(buf, endianness);
585
586        let number_of_bytes: Option<u64> = match data_type {
587            TdmsDataType::String => {
588                r.read_exact(&mut buf)?;
589                let num = to_u64!(buf, endianness);
590
591                Some(num)
592            }
593            _ => None,
594        };
595
596        return Ok(RawDataIndex {
597            data_type,
598            array_dimension,
599            number_of_values,
600            number_of_bytes,
601        });
602    }
603}
604
605#[derive(Debug, Clone)]
606pub struct DAQmxDataIndex {
607    pub data_type: TdmsDataType,
608    pub array_dimension: u32, // should only ever be 1
609    pub number_of_values: u64,
610    pub format_changing_size: Option<u32>,
611    pub format_changing_vec: Option<Vec<FormatChangingScaler>>,
612    pub buffer_vec_size: u32,
613    pub buffers: Vec<u32>,
614}
615
616impl DAQmxDataIndex {
617    pub fn from_reader<R: Read + Seek>(
618        endianness: Endianness,
619        r: &mut R,
620        is_format_changing: bool,
621    ) -> Result<Self, TdmsError> {
622        let mut buf: [u8; 4] = [0; 4];
623        r.read_exact(&mut buf)?;
624
625        let data_type = to_u32!(buf, endianness);
626
627        if data_type != 0xFFFFFFFF {
628            return Err(InvalidDAQmxDataIndex());
629        }
630
631        r.read_exact(&mut buf)?;
632        let array_dimension = to_u32!(buf, endianness);
633
634        let mut buf: [u8; 8] = [0; 8];
635        r.read_exact(&mut buf)?;
636        let number_of_values = to_u64!(buf, endianness);
637
638        let mut buf: [u8; 4] = [0; 4];
639
640        let format_changing_size: Option<u32> = None;
641        let format_changing_vec: Option<Vec<FormatChangingScaler>> = None;
642        if is_format_changing {
643            r.read_exact(&mut buf)?;
644            let changing_vec_size = to_u32!(buf, endianness);
645
646            let mut vec: Vec<FormatChangingScaler> = vec![];
647            for _ in 0..changing_vec_size {
648                vec.push(FormatChangingScaler::from_reader(endianness, r)?)
649            }
650        }
651
652        r.read_exact(&mut buf)?;
653        let buffer_vec_size = to_u32!(buf, endianness);
654
655        let mut buffers: Vec<u32> = vec![];
656
657        for _ in 0..buffer_vec_size {
658            r.read_exact(&mut buf)?;
659            let elements = to_u32!(buf, endianness);
660
661            buffers.push(elements);
662        }
663
664        return Ok(DAQmxDataIndex {
665            data_type: TdmsDataType::DAQmxRawData,
666            array_dimension,
667            number_of_values,
668            format_changing_size,
669            format_changing_vec,
670            buffer_vec_size,
671            buffers,
672        });
673    }
674}
675
676#[derive(Debug, Clone)]
677pub struct FormatChangingScaler {
678    pub data_type: TdmsDataType,
679    pub raw_buffer_index: u32,
680    pub raw_byte_offset: u32,
681    pub sample_format_bitmap: u32,
682    pub scale_id: u32,
683}
684
685impl FormatChangingScaler {
686    pub fn from_reader<R: Read + Seek>(
687        endianness: Endianness,
688        r: &mut R,
689    ) -> Result<Self, TdmsError> {
690        let mut buf: [u8; 4] = [0; 4];
691        r.read_exact(&mut buf)?;
692
693        let data_type = to_i32!(buf, endianness);
694
695        let data_type = TdmsDataType::try_from(data_type)?;
696
697        r.read_exact(&mut buf)?;
698        let raw_buffer_index = to_u32!(buf, endianness);
699
700        r.read_exact(&mut buf)?;
701        let raw_byte_offset = to_u32!(buf, endianness);
702
703        r.read_exact(&mut buf)?;
704        let sample_format_bitmap = to_u32!(buf, endianness);
705
706        r.read_exact(&mut buf)?;
707        let scale_id = to_u32!(buf, endianness);
708
709        return Ok(FormatChangingScaler {
710            data_type,
711            raw_buffer_index,
712            raw_byte_offset,
713            sample_format_bitmap,
714            scale_id,
715        });
716    }
717}
718
719#[derive(Debug, Clone)]
720/// `MetadataProperty` is a key/value pair associated with a `MetadataObject`
721pub struct MetadataProperty {
722    pub name: String,
723    pub data_type: TdmsDataType,
724    pub value: TDMSValue,
725}
726
727impl MetadataProperty {
728    /// from_reader accepts an open reader and attempts to read metadata properties from the currently
729    /// selected segment and metadata object. Note that you must have read the metadata object's lead
730    /// in information prior to using this function
731    pub fn from_reader<R: Read + Seek>(
732        endianness: Endianness,
733        r: &mut R,
734    ) -> Result<Self, TdmsError> {
735        let mut buf: [u8; 4] = [0; 4];
736        r.read_exact(&mut buf)?;
737
738        let length: u32 = to_u32!(buf, endianness);
739
740        // must be a vec due to variable length
741        let length = match usize::try_from(length) {
742            Ok(l) => l,
743            Err(_) => {
744                return Err(General(String::from(
745                    "error converting strength length to system size",
746                )))
747            }
748        };
749
750        let mut name = vec![0; length];
751        r.read_exact(&mut name)?;
752
753        // all strings are UTF8 encoded in TDMS files, most prefixed by the length attribute
754        // like above
755        let name = match String::from_utf8(name) {
756            Ok(n) => n,
757            Err(_) => {
758                return Err(StringConversionError(String::from(
759                    "unable to convert metadata property name",
760                )))
761            }
762        };
763
764        // now we check the data type
765        r.read_exact(&mut buf)?;
766        let data_type = to_i32!(buf, endianness);
767
768        let data_type = TdmsDataType::try_from(data_type)?;
769        let value = TDMSValue::from_reader(endianness, data_type, r)?;
770
771        return Ok(MetadataProperty {
772            name,
773            data_type,
774            value,
775        });
776    }
777}
778
779fn rem_quotes(value: &str) -> &str {
780    let mut original = value.chars();
781    let mut chars = value.clone().chars().peekable();
782
783    match chars.peek() {
784        None => (),
785        Some(first) => {
786            if first.to_string() == "'" {
787                original.next();
788            }
789        }
790    }
791
792    let mut reversed = chars.rev().peekable();
793    match reversed.peek() {
794        None => (),
795        Some(last) => {
796            if last.to_string() == "'" {
797                original.next_back();
798            }
799        }
800    }
801
802    original.as_str()
803}
804
805#[macro_export]
806macro_rules! to_u32 {
807    ( $x:ident, $t:ident ) => {
808        match $t {
809            Little => u32::from_le_bytes($x),
810            Big => u32::from_be_bytes($x),
811        }
812    };
813}
814
815#[macro_export]
816macro_rules! to_i32 {
817    ( $x:ident, $t:ident ) => {
818        match $t {
819            Little => i32::from_le_bytes($x),
820            Big => i32::from_be_bytes($x),
821        }
822    };
823}
824
825#[macro_export]
826macro_rules! to_u64 {
827    ( $x:ident, $t:ident ) => {
828        match $t {
829            Little => u64::from_le_bytes($x),
830            Big => u64::from_be_bytes($x),
831        }
832    };
833}
834
835#[macro_export]
836macro_rules! to_f64 {
837    ( $x:ident, $t:ident ) => {
838        match $t {
839            Little => f64::from_le_bytes($x),
840            Big => f64::from_be_bytes($x),
841        }
842    };
843}