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
16pub struct TdmsFile {
21 pub(crate) inner: Arc<TdmsFileInner>,
22}
23
24pub struct TdmsGroup<'a> {
26 pub(crate) file: &'a TdmsFile,
27 pub(crate) data: &'a TdmsGroupData,
28}
29
30pub struct TdmsChannel<'a> {
32 pub(crate) file: &'a TdmsFile,
33 pub(crate) data: &'a TdmsChannelData,
34}
35
36impl TdmsFile {
37 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 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 pub fn property(&self, name: &str) -> Option<&PropertyValue> {
123 self.inner.properties.get(name)
124 }
125
126 pub fn properties(&self) -> impl Iterator<Item = (&str, &PropertyValue)> {
128 self.inner.properties.iter().map(|(k, v)| (k.as_str(), v))
129 }
130
131 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 pub fn name(&self) -> &str {
143 &self.data.name
144 }
145
146 pub fn property(&self, name: &str) -> Option<&PropertyValue> {
148 self.data.properties.get(name)
149 }
150
151 pub fn properties(&self) -> impl Iterator<Item = (&str, &PropertyValue)> {
153 self.data.properties.iter().map(|(k, v)| (k.as_str(), v))
154 }
155
156 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 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 pub fn name(&self) -> &str {
178 &self.data.name
179 }
180
181 pub fn dtype(&self) -> DataType {
183 self.data.dtype.clone()
184 }
185
186 pub fn len(&self) -> usize {
188 self.data.len
189 }
190
191 pub fn is_empty(&self) -> bool {
193 self.data.len == 0
194 }
195
196 pub fn property(&self, name: &str) -> Option<&PropertyValue> {
198 self.data.properties.get(name)
199 }
200
201 pub fn properties(&self) -> impl Iterator<Item = (&str, &PropertyValue)> {
203 self.data.properties.iter().map(|(k, v)| (k.as_str(), v))
204 }
205
206 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 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
320pub 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
342pub 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}