tdms_rs/api/
reader.rs

1use crate::error::{Result, TdmsError};
2use crate::format::metadata::ParsingMetadata;
3use crate::format::segment::Segment;
4use crate::io::ext::TdmsReadExt;
5use crate::model::channel::{DataLocation, TdmsChannelData};
6use crate::model::datatypes::{DataType, PropertyValue};
7use crate::model::file::TdmsFileInner;
8use crate::model::group::TdmsGroupData;
9use indexmap::IndexMap;
10use std::fs::File;
11use std::io::{BufReader, Read, Seek, SeekFrom};
12use std::ops::Range;
13use std::path::Path;
14use std::sync::Arc;
15
16/// A TDMS file handle for reading.
17///
18/// This struct indexes the file structure (groups, channels, properties) on open
19/// without loading raw data into memory.
20pub struct TdmsFile {
21    pub(crate) inner: Arc<TdmsFileInner>,
22}
23
24/// A group within a TDMS file.
25pub struct TdmsGroup<'a> {
26    pub(crate) file: &'a TdmsFile,
27    pub(crate) data: &'a TdmsGroupData,
28}
29
30/// A channel within a TDMS group.
31pub struct TdmsChannel<'a> {
32    pub(crate) file: &'a TdmsFile,
33    pub(crate) data: &'a TdmsChannelData,
34}
35
36impl TdmsFile {
37    /// Open a TDMS file for reading.
38    ///
39    /// This parses and indexes all segment metadata eagerly, but does not read
40    /// raw channel data until it is requested.
41    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
42        let path = path.as_ref();
43        let file = File::open(path)?;
44        let mut reader = TdmsReaderInternal::new(BufReader::new(file));
45
46        let mut groups = IndexMap::new();
47        let mut file_properties = IndexMap::new();
48
49        loop {
50            let segment = match reader.read_segment() {
51                Ok(s) => s,
52                Err(TdmsError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
53                Err(TdmsError::Io(e))
54                    if e.kind() == std::io::ErrorKind::Other
55                        && e.to_string().contains("UnexpectedEof") =>
56                {
57                    break
58                }
59                Err(e) => return Err(e),
60            };
61
62            for obj in segment.objects {
63                if let Some(g_name) = obj.path.group_name() {
64                    let group = groups
65                        .entry(g_name.to_string())
66                        .or_insert_with(|| TdmsGroupData {
67                            name: g_name.to_string(),
68                            channels: IndexMap::new(),
69                            properties: IndexMap::new(),
70                        });
71
72                    if let Some(c_name) = obj.path.channel_name() {
73                        let channel =
74                            group.channels.entry(c_name.to_string()).or_insert_with(|| {
75                                TdmsChannelData {
76                                    name: c_name.to_string(),
77                                    dtype: DataType::Double,
78                                    len: 0,
79                                    data_locations: Vec::new(),
80                                    properties: IndexMap::new(),
81                                }
82                            });
83
84                        channel.properties.extend(obj.properties);
85
86                        if let Some(loc) = obj.data_location {
87                            channel.len += loc.number_of_values as usize;
88                            channel.data_locations.push(DataLocation {
89                                offset: loc.offset,
90                                number_of_values: loc.number_of_values,
91                            });
92                        }
93
94                        if let Some(meta) = obj.raw_data_meta {
95                            channel.dtype = meta.data_type;
96                        }
97                    } else {
98                        group.properties.extend(obj.properties);
99                    }
100                } else if obj.path.is_root() {
101                    file_properties.extend(obj.properties);
102                }
103            }
104        }
105
106        Ok(Self {
107            inner: Arc::new(TdmsFileInner {
108                path: path.to_path_buf(),
109                groups,
110                properties: file_properties,
111            }),
112        })
113    }
114
115    /// Look up a group by name.
116    pub fn group(&self, name: &str) -> Option<TdmsGroup<'_>> {
117        let data = self.inner.groups.get(name)?;
118        Some(TdmsGroup { file: self, data })
119    }
120
121    /// Look up a file-level property by name.
122    pub fn property(&self, name: &str) -> Option<&PropertyValue> {
123        self.inner.properties.get(name)
124    }
125
126    /// Iterate over all file-level properties.
127    pub fn properties(&self) -> impl Iterator<Item = (&str, &PropertyValue)> {
128        self.inner.properties.iter().map(|(k, v)| (k.as_str(), v))
129    }
130
131    /// Iterate over all groups in the file.
132    pub fn groups(&self) -> impl Iterator<Item = TdmsGroup<'_>> {
133        self.inner
134            .groups
135            .values()
136            .map(move |data| TdmsGroup { file: self, data })
137    }
138}
139
140impl<'a> TdmsGroup<'a> {
141    /// Return the group name.
142    pub fn name(&self) -> &str {
143        &self.data.name
144    }
145
146    /// Look up a group-level property by name.
147    pub fn property(&self, name: &str) -> Option<&PropertyValue> {
148        self.data.properties.get(name)
149    }
150
151    /// Iterate over all group-level properties.
152    pub fn properties(&self) -> impl Iterator<Item = (&str, &PropertyValue)> {
153        self.data.properties.iter().map(|(k, v)| (k.as_str(), v))
154    }
155
156    /// Look up a channel by name.
157    pub fn channel(&self, name: &str) -> Option<TdmsChannel<'a>> {
158        let data = self.data.channels.get(name)?;
159        Some(TdmsChannel {
160            file: self.file,
161            data,
162        })
163    }
164
165    /// Iterate over all channels in the group.
166    pub fn channels(&self) -> impl Iterator<Item = TdmsChannel<'a>> {
167        let file = self.file;
168        self.data
169            .channels
170            .values()
171            .map(move |data| TdmsChannel { file, data })
172    }
173}
174
175impl<'a> TdmsChannel<'a> {
176    /// Return the channel name.
177    pub fn name(&self) -> &str {
178        &self.data.name
179    }
180
181    /// Return the channel data type.
182    pub fn dtype(&self) -> DataType {
183        self.data.dtype.clone()
184    }
185
186    /// Return the number of values in the channel.
187    pub fn len(&self) -> usize {
188        self.data.len
189    }
190
191    /// Returns `true` if the channel contains no values.
192    pub fn is_empty(&self) -> bool {
193        self.data.len == 0
194    }
195
196    /// Look up a channel-level property by name.
197    pub fn property(&self, name: &str) -> Option<&PropertyValue> {
198        self.data.properties.get(name)
199    }
200
201    /// Iterate over all channel-level properties.
202    pub fn properties(&self) -> impl Iterator<Item = (&str, &PropertyValue)> {
203        self.data.properties.iter().map(|(k, v)| (k.as_str(), v))
204    }
205
206    /// Read a range of values into the provided output buffer.
207    ///
208    /// The element type `T` must match the TDMS channel element size.
209    pub fn read<T: Pod>(&self, range: Range<usize>, out: &mut [T]) -> Result<usize> {
210        if range.end > self.data.len {
211            return Err(TdmsError::InvalidRange(
212                range.start,
213                range.end,
214                self.data.len,
215            ));
216        }
217        if std::mem::size_of::<T>() != self.data.dtype.itemsize() {
218            return Err(TdmsError::TypeMismatch);
219        }
220        let requested = range.end - range.start;
221        if out.len() < requested {
222            return Err(TdmsError::InvalidFormat(
223                "output buffer too small for requested range".to_string(),
224            ));
225        }
226
227        let out_bytes = unsafe {
228            std::slice::from_raw_parts_mut(
229                out.as_mut_ptr() as *mut u8,
230                requested * std::mem::size_of::<T>(),
231            )
232        };
233
234        let file = File::open(&self.file.inner.path)?;
235        let mut reader = BufReader::new(file);
236        self.read_range_into_bytes(&range, out_bytes, &mut reader)?;
237        Ok(requested)
238    }
239
240    /// If the waveform timing properties are present, return an iterator of
241    /// timestamps (in seconds) corresponding to each sample.
242    pub fn timestamps(&self) -> Option<TimestampIterator> {
243        let start_time = self.data.properties.get("wf_start_time").and_then(|v| {
244            if let PropertyValue::Double(d) = v {
245                Some(*d)
246            } else {
247                None
248            }
249        })?;
250
251        let increment = self.data.properties.get("wf_increment").and_then(|v| {
252            if let PropertyValue::Double(d) = v {
253                Some(*d)
254            } else {
255                None
256            }
257        })?;
258
259        Some(TimestampIterator {
260            start: start_time,
261            increment,
262            index: 0,
263            len: self.data.len,
264        })
265    }
266
267    fn read_range_into_bytes<R: std::io::Read + std::io::Seek>(
268        &self,
269        range: &Range<usize>,
270        out: &mut [u8],
271        reader: &mut R,
272    ) -> Result<()> {
273        let itemsize = self.data.dtype.itemsize();
274        let total_bytes = (range.end - range.start) * itemsize;
275        if out.len() != total_bytes {
276            return Err(TdmsError::InvalidFormat(
277                "output buffer length must exactly match requested byte length".to_string(),
278            ));
279        }
280
281        let mut remaining = range.end - range.start;
282        let mut current_offset = range.start;
283        let mut out_cursor = 0;
284
285        for loc in &self.data.data_locations {
286            let loc_end = loc.number_of_values as usize;
287
288            if current_offset >= loc_end {
289                current_offset -= loc_end;
290                continue;
291            }
292
293            let read_start = current_offset;
294            let read_end = loc_end.min(current_offset + remaining);
295            let read_count = read_end - read_start;
296
297            if read_count == 0 {
298                break;
299            }
300
301            let byte_offset = loc.offset + (read_start * itemsize) as u64;
302            reader.seek(SeekFrom::Start(byte_offset))?;
303
304            let read_bytes = read_count * itemsize;
305            reader.read_exact(&mut out[out_cursor..out_cursor + read_bytes])?;
306            out_cursor += read_bytes;
307
308            remaining -= read_count;
309            current_offset = 0;
310
311            if remaining == 0 {
312                break;
313            }
314        }
315
316        Ok(())
317    }
318}
319
320/// Iterator returned by [`TdmsChannel::timestamps`].
321pub struct TimestampIterator {
322    start: f64,
323    increment: f64,
324    index: usize,
325    len: usize,
326}
327
328impl Iterator for TimestampIterator {
329    type Item = f64;
330
331    fn next(&mut self) -> Option<Self::Item> {
332        if self.index >= self.len {
333            return None;
334        }
335
336        let t = self.start + (self.index as f64) * self.increment;
337        self.index += 1;
338        Some(t)
339    }
340}
341
342/// Marker trait for plain-old-data types supported by [`TdmsChannel::read`].
343pub trait Pod: Copy {}
344impl Pod for i8 {}
345impl Pod for u8 {}
346impl Pod for i16 {}
347impl Pod for u16 {}
348impl Pod for i32 {}
349impl Pod for u32 {}
350impl Pod for i64 {}
351impl Pod for u64 {}
352impl Pod for f32 {}
353impl Pod for f64 {}
354impl Pod for bool {}
355
356struct TdmsReaderInternal<R: Read + Seek> {
357    reader: R,
358    active_meta: std::collections::HashMap<String, crate::format::metadata::RawDataMeta>,
359    object_order: Vec<String>,
360}
361
362impl<R: Read + Seek> TdmsReaderInternal<R> {
363    fn new(reader: R) -> Self {
364        Self {
365            reader,
366            active_meta: std::collections::HashMap::new(),
367            object_order: Vec::new(),
368        }
369    }
370
371    fn read_segment(&mut self) -> Result<Segment> {
372        let start_pos = self.reader.stream_position()?;
373        let mut lead_in = [0u8; 4];
374        self.reader.read_exact(&mut lead_in)?;
375
376        if &lead_in != b"TDSm" {
377            return Err(TdmsError::InvalidSignature);
378        }
379
380        let mask_val = self.reader.read_u32()?;
381        let version = self.reader.read_u32()?;
382        let next_segment_offset = self.reader.read_u64()?;
383        let raw_data_offset = self.reader.read_u64()?;
384
385        let mask = crate::format::segment::Mask::new(mask_val);
386        let mut objects = Vec::new();
387
388        if mask.has_new_obj_list() {
389            let count = self.reader.read_u32()?;
390            self.object_order.clear();
391
392            for _ in 0..count {
393                let path_len = self.reader.read_u32()?;
394                let mut path_bytes = vec![0u8; path_len as usize];
395                self.reader.read_exact(&mut path_bytes)?;
396                let path_str =
397                    String::from_utf8(path_bytes).map_err(|_| TdmsError::StringEncoding)?;
398                self.object_order.push(path_str.clone());
399
400                let raw_data_index = self.reader.read_u32()?;
401                let mut raw_data_meta = None;
402                let prop_count;
403
404                if raw_data_index != 0 && raw_data_index != 0xFFFFFFFF {
405                    let mut skipped = vec![0u8; raw_data_index as usize];
406                    self.reader.read_exact(&mut skipped)?;
407
408                    if raw_data_index >= 4 {
409                        let mut slice = &skipped[0..4];
410                        let type_code = slice.read_u32()?;
411                        let data_type = DataType::from_u32(type_code)?;
412
413                        let mut count = 0;
414                        let mut total_size = None;
415
416                        if data_type == DataType::String {
417                            if raw_data_index >= 16 {
418                                let mut count_slice = &skipped[8..16];
419                                count = count_slice.read_u64()?;
420                            }
421                            if raw_data_index >= 24 {
422                                let mut size_slice = &skipped[16..24];
423                                total_size = Some(size_slice.read_u64()?);
424                            }
425                            prop_count = self.reader.read_u32()?;
426                        } else {
427                            if raw_data_index >= 16 {
428                                let mut count_slice = &skipped[8..16];
429                                count = count_slice.read_u64()?;
430                            }
431                            let start = (raw_data_index - 4) as usize;
432                            let mut end_slice = &skipped[start..];
433                            prop_count = end_slice.read_u32()?;
434                        }
435
436                        raw_data_meta = Some(crate::format::metadata::RawDataMeta {
437                            data_type,
438                            number_of_values: count,
439                            total_size_bytes: total_size,
440                        });
441                    } else {
442                        prop_count = 0;
443                    }
444                } else {
445                    prop_count = self.reader.read_u32()?;
446                }
447
448                let mut properties = std::collections::HashMap::new();
449                for _ in 0..prop_count {
450                    let key_len = self.reader.read_u32()?;
451                    let mut key_bytes = vec![0u8; key_len as usize];
452                    self.reader.read_exact(&mut key_bytes)?;
453                    let key =
454                        String::from_utf8(key_bytes).map_err(|_| TdmsError::StringEncoding)?;
455                    let type_code = self.reader.read_u32()?;
456                    let val =
457                        crate::model::datatypes::DataType::from_u32(type_code).and_then(|dt| {
458                            match dt {
459                                DataType::I8 => Ok(PropertyValue::I8(self.reader.read_i8()?)),
460                                DataType::I16 => Ok(PropertyValue::I16(self.reader.read_i16()?)),
461                                DataType::I32 => Ok(PropertyValue::I32(self.reader.read_i32()?)),
462                                DataType::I64 => Ok(PropertyValue::I64(self.reader.read_i64()?)),
463                                DataType::U8 => Ok(PropertyValue::U8(self.reader.read_u8()?)),
464                                DataType::U16 => Ok(PropertyValue::U16(self.reader.read_u16()?)),
465                                DataType::U32 => Ok(PropertyValue::U32(self.reader.read_u32()?)),
466                                DataType::U64 => Ok(PropertyValue::U64(self.reader.read_u64()?)),
467                                DataType::Float => {
468                                    Ok(PropertyValue::Float(self.reader.read_f32()?))
469                                }
470                                DataType::Double => {
471                                    Ok(PropertyValue::Double(self.reader.read_f64()?))
472                                }
473                                DataType::Boolean => {
474                                    Ok(PropertyValue::Boolean(self.reader.read_u8()? != 0))
475                                }
476                                DataType::String => {
477                                    let len = self.reader.read_u32()?;
478                                    let mut buf = vec![0u8; len as usize];
479                                    self.reader.read_exact(&mut buf)?;
480                                    let s = String::from_utf8(buf)
481                                        .map_err(|_| TdmsError::StringEncoding)?;
482                                    Ok(PropertyValue::String(s))
483                                }
484                                DataType::TimeStamp => {
485                                    let fraction = self.reader.read_u64()?;
486                                    let seconds = self.reader.read_i64()?;
487                                    Ok(PropertyValue::TimeStamp((seconds, fraction)))
488                                }
489                            }
490                        })?;
491                    properties.insert(key, val);
492                }
493
494                objects.push(ParsingMetadata {
495                    path: crate::format::metadata::ObjectPath::new(path_str),
496                    raw_data_index,
497                    properties,
498                    raw_data_meta,
499                    data_location: None,
500                });
501            }
502        } else {
503            for path_str in &self.object_order {
504                objects.push(ParsingMetadata {
505                    path: crate::format::metadata::ObjectPath::new(path_str.clone()),
506                    raw_data_index: 0,
507                    properties: std::collections::HashMap::new(),
508                    raw_data_meta: None,
509                    data_location: None,
510                });
511            }
512        }
513
514        let mut current_raw_offset = start_pos + 28 + raw_data_offset;
515        for obj in &mut objects {
516            let path_str = obj.path.raw.clone();
517            if let Some(meta) = &obj.raw_data_meta {
518                self.active_meta.insert(path_str.clone(), meta.clone());
519                if meta.number_of_values > 0 {
520                    let size = (meta.data_type.itemsize() as u64) * meta.number_of_values;
521                    obj.data_location = Some(crate::format::metadata::DataLocation {
522                        offset: current_raw_offset,
523                        number_of_values: meta.number_of_values,
524                        _data_type: meta.data_type.clone(),
525                        _total_size_bytes: meta.total_size_bytes,
526                    });
527                    current_raw_offset += size;
528                }
529            } else if obj.raw_data_index == 0 {
530                if let Some(meta) = self.active_meta.get(&path_str) {
531                    if meta.number_of_values > 0 {
532                        let size = (meta.data_type.itemsize() as u64) * meta.number_of_values;
533                        obj.data_location = Some(crate::format::metadata::DataLocation {
534                            offset: current_raw_offset,
535                            number_of_values: meta.number_of_values,
536                            _data_type: meta.data_type.clone(),
537                            _total_size_bytes: meta.total_size_bytes,
538                        });
539                        current_raw_offset += size;
540                    }
541                }
542            }
543        }
544
545        let target_pos = if next_segment_offset != 0xFFFFFFFFFFFFFFFF {
546            start_pos + 28 + next_segment_offset
547        } else {
548            current_raw_offset
549        };
550
551        let current_pos = self.reader.stream_position()?;
552        if current_pos != target_pos {
553            self.reader.seek(SeekFrom::Start(target_pos))?;
554        }
555
556        Ok(Segment {
557            _version: version,
558            _next_segment_offset: next_segment_offset,
559            _raw_data_offset: raw_data_offset,
560            _toc_mask: mask.convert(),
561            objects,
562        })
563    }
564}