Skip to main content

mseed/
trace.rs

1use std::ffi::c_double;
2use std::fmt;
3use std::ptr;
4use std::slice::from_raw_parts;
5
6use crate::{
7    error::check, raw, util, MSControlFlags, MSError, MSRecord, MSResult, MSSampleType,
8    MSSubSeconds, MSTimeFormat,
9};
10use time::OffsetDateTime;
11
12use raw::{MS3TraceID, MS3TraceList, MS3TraceSeg};
13
14/// A container for a trace identifier composed by [`MSTraceSegment`]s.
15#[derive(Debug)]
16pub struct MSTraceId(*mut MS3TraceID);
17
18impl MSTraceId {
19    fn ptr(&self) -> MS3TraceID {
20        unsafe { *self.0 }
21    }
22
23    #[allow(dead_code)]
24    pub(crate) fn get_raw(&self) -> *const MS3TraceID {
25        self.0
26    }
27
28    #[allow(dead_code)]
29    pub(crate) unsafe fn get_raw_mut(&mut self) -> *mut MS3TraceID {
30        self.0
31    }
32
33    /// Returns the [FDSN source identifier](https://docs.fdsn.org/projects/source-identifiers/).
34    pub fn sid(&self) -> MSResult<String> {
35        let nslc = util::NetStaLocCha::from_sid(&self.ptr().sid)?;
36        Ok(nslc.to_string())
37    }
38
39    /// Returns the largest contributing publication version.
40    pub fn pub_version(&self) -> u8 {
41        self.ptr().pubversion
42    }
43
44    /// Returns the time of the the first sample.
45    pub fn start_time(&self) -> MSResult<OffsetDateTime> {
46        util::nstime_to_time(self.ptr().earliest)
47    }
48
49    /// Returns the time of the the last sample.
50    pub fn end_time(&self) -> MSResult<OffsetDateTime> {
51        util::nstime_to_time(self.ptr().latest)
52    }
53
54    /// Returns the number of [`MSTraceSegment`]s for this trace identifier.
55    pub fn len(&self) -> u32 {
56        self.ptr().numsegments
57    }
58
59    /// Returns whether the trace identifier is empty.
60    pub fn is_empty(&self) -> bool {
61        self.len() == 0
62    }
63
64    /// Returns an iterator over the trace identifiers' trace segments.
65    pub fn iter(&self) -> MSTraceSegmentIter {
66        MSTraceSegmentIter {
67            trace_id: self,
68
69            next: self.ptr().first,
70            prev: ptr::null_mut(),
71        }
72    }
73}
74
75/// An iterator for [`MSTraceId`].
76#[derive(Debug)]
77pub struct MSTraceIdIter {
78    next: *mut MS3TraceID,
79}
80
81impl Iterator for MSTraceIdIter {
82    type Item = MSTraceId;
83    fn next(&mut self) -> Option<Self::Item> {
84        if self.next.is_null() {
85            return None;
86        }
87
88        let rv = Some(MSTraceId(self.next));
89        self.next = unsafe { (*self.next).next[0] };
90        rv
91    }
92}
93
94/// A container for a continuous trace segment.
95#[derive(Debug)]
96pub struct MSTraceSegment<'id> {
97    _trace_id: &'id MSTraceId,
98
99    inner: *mut MS3TraceSeg,
100}
101
102impl<'id> MSTraceSegment<'id> {
103    fn ptr(&self) -> MS3TraceSeg {
104        unsafe { *self.inner }
105    }
106
107    /// Returns the time of the the first sample.
108    pub fn start_time(&self) -> MSResult<OffsetDateTime> {
109        util::nstime_to_time(self.ptr().starttime)
110    }
111
112    /// Returns the time of the the last sample.
113    pub fn end_time(&self) -> MSResult<OffsetDateTime> {
114        util::nstime_to_time(self.ptr().endtime)
115    }
116
117    /// Returns the nominal sample rate as samples per second (`Hz`)
118    pub fn sample_rate_hz(&self) -> c_double {
119        self.ptr().samprate
120    }
121
122    /// Returns the number of samples in trace coverage.
123    pub fn sample_cnt(&self) -> i64 {
124        self.ptr().samplecnt as _
125    }
126
127    /// Returns the data samples of the trace segment.
128    ///
129    /// Note that the data samples must have been unpacked, previously. Deferred unpacking of data
130    /// samples from the internal record list is currently not implemented.
131    pub fn data_samples<T: DataSampleType>(&mut self) -> MSResult<&[T]> {
132        if !self.is_data_unpacked() {
133            return Err(MSError::from_str("data samples must be unpacked"));
134        }
135
136        let rv = unsafe {
137            <T as DataSampleType>::convert_into(self.inner, false)?;
138            from_raw_parts(
139                self.ptr().datasamples as *mut T,
140                self.ptr().samplecnt as usize,
141            )
142        };
143
144        Ok(rv)
145    }
146
147    /// Returns the size of the buffer for (unpacked) data samples in bytes.
148    pub fn data_size(&self) -> u64 {
149        self.ptr().datasize
150    }
151
152    /// Returns the number of (unpacked) data samples.
153    pub fn num_samples(&self) -> i64 {
154        self.ptr().numsamples as _
155    }
156
157    /// Returns the trace segment sample type.
158    pub fn sample_type(&self) -> MSSampleType {
159        MSSampleType::from_char(self.ptr().sampletype as _)
160    }
161
162    /// Returns whether the data samples are unpacked.
163    pub fn is_data_unpacked(&self) -> bool {
164        self.sample_cnt() == self.num_samples()
165            && self.data_size() > 0
166            && !self.ptr().datasamples.is_null()
167    }
168
169    ///// Unpacks data samples of the trace segment and returns the number of unpacked samples.
170    /////
171    ///// If the data is already unpacked, the number of previously unpacked samples is returned.
172    //pub fn unpack_data(&mut self) -> MSResult<c_long> {
173    //    todo!();
174
175    //    if !self.ptr().datasamples.is_null() {
176    //        return Ok(self.num_samples());
177    //    }
178
179    //    unsafe {
180    //        check(raw::mstl3_unpack_recordlist(
181    //            self.trace_id.get_raw_mut(),
182    //            self.inner,
183    //            ptr::null_mut(),
184    //            0,
185    //            0,
186    //        ))
187    //    }
188    //}
189}
190
191pub trait DataSampleType {
192    /// Converts the trace segments' samples
193    ///
194    /// # Safety
195    ///
196    /// `seg` must not be a null pointer.
197    unsafe fn convert_into(seg: *mut MS3TraceSeg, truncate: bool) -> MSResult<()>;
198}
199
200impl DataSampleType for u8 {
201    unsafe fn convert_into(_seg: *mut MS3TraceSeg, _truncate: bool) -> MSResult<()> {
202        Ok(())
203    }
204}
205
206impl DataSampleType for i32 {
207    unsafe fn convert_into(seg: *mut MS3TraceSeg, truncate: bool) -> MSResult<()> {
208        let rv = unsafe {
209            check(raw::mstl3_convertsamples(
210                seg,
211                MSSampleType::Integer32 as _,
212                truncate as _,
213            ))
214        };
215
216        match rv {
217            Ok(_) => Ok(()),
218            Err(e) => Err(e),
219        }
220    }
221}
222
223impl DataSampleType for f32 {
224    unsafe fn convert_into(seg: *mut MS3TraceSeg, truncate: bool) -> MSResult<()> {
225        let rv = unsafe {
226            check(raw::mstl3_convertsamples(
227                seg,
228                MSSampleType::Float32 as _,
229                truncate as _,
230            ))
231        };
232
233        match rv {
234            Ok(_) => Ok(()),
235            Err(e) => Err(e),
236        }
237    }
238}
239
240impl DataSampleType for f64 {
241    unsafe fn convert_into(seg: *mut MS3TraceSeg, truncate: bool) -> MSResult<()> {
242        let rv = unsafe {
243            check(raw::mstl3_convertsamples(
244                seg,
245                MSSampleType::Float64 as _,
246                truncate as _,
247            ))
248        };
249
250        match rv {
251            Ok(_) => Ok(()),
252            Err(e) => Err(e),
253        }
254    }
255}
256
257/// An iterator for [`MSTraceSegment`].
258#[derive(Debug)]
259pub struct MSTraceSegmentIter<'id> {
260    trace_id: &'id MSTraceId,
261
262    next: *mut MS3TraceSeg,
263    prev: *mut MS3TraceSeg,
264}
265
266impl<'id> Iterator for MSTraceSegmentIter<'id> {
267    type Item = MSTraceSegment<'id>;
268
269    fn next(&mut self) -> Option<Self::Item> {
270        if self.next.is_null() {
271            return None;
272        }
273
274        let rv = Some(MSTraceSegment {
275            _trace_id: self.trace_id,
276            inner: self.next,
277        });
278        self.prev = self.next;
279        self.next = unsafe { (*self.next).next };
280        rv
281    }
282}
283
284impl<'id> DoubleEndedIterator for MSTraceSegmentIter<'id> {
285    fn next_back(&mut self) -> Option<Self::Item> {
286        if self.prev.is_null() {
287            return None;
288        }
289
290        let rv = Some(MSTraceSegment {
291            _trace_id: self.trace_id,
292            inner: self.prev,
293        });
294        self.next = self.prev;
295        self.prev = unsafe { (*self.prev).prev };
296        rv
297    }
298}
299
300/// A container for [`MSTraceId`]s.
301///
302/// # Examples
303///
304/// Creating a `MSTraceList` from a file may be implemented as follows:
305///
306/// ```no_run
307/// use std::fs::File;
308///
309/// use std::io::{Read, BufReader};
310///
311/// use mseed::{MSControlFlags, MSTraceList};
312///
313/// let file = File::open("path/to/data.mseed").unwrap();
314/// let mut reader = BufReader::new(file);
315///
316/// let mut buf = Vec::new();
317/// // read content of `data.mseed` into `buf`
318/// reader.read_to_end(&mut buf).unwrap();
319///
320/// let mstl = MSTraceList::from_buffer(&buf, MSControlFlags::MSF_UNPACKDATA).unwrap();
321/// ```
322///
323/// If controlling the records to be inserted is desired, using [`MSReader`] is required:
324///
325/// ```no_run
326/// use std::fs::File;
327///
328/// use mseed::{MSControlFlags, MSReader, MSTraceList};
329///
330/// let mut mstl = MSTraceList::new().unwrap();
331///
332/// let mut reader =
333///     MSReader::new_with_flags("path/to/data.mseed", MSControlFlags::MSF_UNPACKDATA).unwrap();
334///
335/// while let Some(res) = reader.next() {
336///     let msr = res.unwrap();
337///
338///     if msr.network().unwrap() == "NET" && msr.station().unwrap() == "STA" {
339///         mstl.insert(msr, true).unwrap();
340///     }
341/// }
342///
343/// // do something with `mstl`
344/// let mstl_iter = mstl.iter();
345/// for tid in mstl_iter {
346///     let tid_iter = tid.iter();
347///     for tseg in tid_iter {
348///         // do something with `tseg`
349///     }
350/// }
351/// ```
352/// [`MSReader`]: crate::MSReader
353#[derive(Debug)]
354pub struct MSTraceList {
355    inner: *mut MS3TraceList,
356}
357
358impl MSTraceList {
359    fn ptr(&self) -> MS3TraceList {
360        unsafe { *self.inner }
361    }
362
363    #[allow(dead_code)]
364    pub(crate) fn get_raw(&self) -> *const MS3TraceList {
365        self.inner
366    }
367
368    #[allow(dead_code)]
369    pub(crate) unsafe fn get_raw_mut(&mut self) -> *mut MS3TraceList {
370        self.inner
371    }
372
373    /// Creates a new [`MSTraceList`] container.
374    pub fn new() -> MSResult<Self> {
375        let mstl: *mut MS3TraceList = ptr::null_mut();
376        let mstl = unsafe { raw::mstl3_init(mstl) };
377        if mstl.is_null() {
378            return Err(MSError::from_str("failed to initialize trace list"));
379        }
380
381        Ok(Self { inner: mstl })
382    }
383
384    /// Creates a new [`MSTraceList`] from a buffer.
385    pub fn from_buffer(buf: &[u8], flags: MSControlFlags) -> MSResult<Self> {
386        let mut rv = Self::new()?;
387
388        unsafe {
389            let buf = &*(buf as *const [_] as *const [_]);
390            check(raw::mstl3_readbuffer(
391                (&mut rv.get_raw_mut()) as *mut *mut _,
392                buf.as_ptr(),
393                buf.len() as _,
394                0,
395                flags.bits(),
396                ptr::null_mut(),
397                0,
398            ))
399        }?;
400
401        Ok(rv)
402    }
403
404    /// Returns the length of the trace list.
405    pub fn len(&self) -> u32 {
406        self.ptr().numtraceids
407    }
408
409    /// Returns whether the trace list is empty.
410    pub fn is_empty(&self) -> bool {
411        self.len() == 0
412    }
413
414    /// Returns a forward iterator over the trace lists' trace identifiers.
415    pub fn iter(&self) -> MSTraceIdIter {
416        MSTraceIdIter {
417            next: self.ptr().traces.next[0],
418        }
419    }
420
421    /// Inserts `rec` into the trace list.
422    ///
423    /// Note that currently [`MSTraceList`] does not implement deferred unpacking of data samples.
424    /// Therefore, clients need to make sure that the `rec` inserted is unpacked, beforehand. If
425    /// not doing so, the trace list will merely be a list of channels.
426    pub fn insert(&mut self, rec: MSRecord, autoheal: bool) -> MSResult<()> {
427        let rv = unsafe {
428            raw::mstl3_addmsr_recordptr(
429                self.inner,
430                rec.into_raw(),
431                ptr::null_mut(),
432                0,
433                autoheal as _,
434                MSControlFlags::empty().bits(),
435                ptr::null_mut(),
436            )
437        };
438
439        if rv.is_null() {
440            return Err(MSError::from_str("failed to insert record"));
441        }
442
443        Ok(())
444    }
445
446    /// Returns an object that implements [`Display`] for printing a trace list summary.
447    ///
448    /// By default only prints the [FDSN source
449    /// identifier](https://docs.fdsn.org/projects/source-identifiers/), starttime and endtime for
450    /// each trace. If `detail` is greater than zero the sample rate, number of samples and a total
451    /// trace count is included.
452    /// If `gap` is greater than zero and the previous trace matches both the FDSN source identifier
453    /// and  the sample rate the gap between the endtime of the last trace and the starttime of the
454    /// current trace is included.
455    /// If `version` is greater than zero, the publication version is included.
456    ///
457    ///  [`Display`]: fmt::Display
458    pub fn display(
459        &self,
460        time_format: MSTimeFormat,
461        detail: i8,
462        gap: i8,
463        version: i8,
464    ) -> TraceListDisplay<'_> {
465        TraceListDisplay {
466            mstl: self,
467            time_format,
468            detail,
469            gap,
470            version,
471        }
472    }
473}
474
475impl Drop for MSTraceList {
476    fn drop(&mut self) {
477        unsafe { raw::mstl3_free((&mut self.inner) as *mut *mut MS3TraceList, 1) };
478    }
479}
480
481/// Helper struct for printing `MSTraceList` with [`format!`] and `{}`.
482pub struct TraceListDisplay<'a> {
483    mstl: &'a MSTraceList,
484    time_format: MSTimeFormat,
485    detail: i8,
486    gap: i8,
487    version: i8,
488}
489
490impl fmt::Debug for TraceListDisplay<'_> {
491    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492        fmt::Debug::fmt(&self.mstl, f)
493    }
494}
495
496impl fmt::Display for TraceListDisplay<'_> {
497    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498        // XXX(damb): reimplements `mstl3_printtracelist()`
499        if self.detail > 0 && self.gap > 0 {
500            writeln!(f, "       SourceID                      Start sample                End sample           Gap  Hz  Samples")?;
501        } else if self.detail <= 0 && self.gap > 0 {
502            writeln!(f, "       SourceID                      Start sample                End sample           Gap")?;
503        } else if self.detail > 0 && self.gap <= 0 {
504            writeln!(f, "       SourceID                      Start sample                End sample           Hz  Samples")?;
505        } else {
506            writeln!(
507                f,
508                "       SourceID                      Start sample                End sample"
509            )?;
510        }
511
512        let mut tid_cnt = 0;
513        let mut tseg_cnt = 0;
514
515        for tid in self.mstl.iter() {
516            let sid = tid.sid().map_err(|_| fmt::Error)?;
517            let sid = if self.version > 0 {
518                format!("{}#{}", sid, tid.pub_version())
519            } else {
520                sid
521            };
522            for tseg in tid.iter() {
523                let start_time = unsafe { (*tseg.inner).starttime };
524                let start_time_str =
525                    util::nstime_to_string(start_time, self.time_format, MSSubSeconds::NanoMicro)
526                        .map_err(|_| fmt::Error)?;
527
528                let end_time = unsafe { (*tseg.inner).endtime };
529                let end_time_str =
530                    util::nstime_to_string(end_time, self.time_format, MSSubSeconds::NanoMicro)
531                        .map_err(|_| fmt::Error)?;
532
533                if self.gap > 0 {
534                    let mut gap: f64 = 0.0;
535                    let mut no_gap = false;
536
537                    let prev_tseg_ptr = unsafe { (*tseg.inner).prev };
538                    if !prev_tseg_ptr.is_null() {
539                        gap = (start_time - unsafe { (*prev_tseg_ptr).endtime }) as f64
540                            / raw::NSTMODULUS as f64;
541                    } else {
542                        no_gap = true;
543                    }
544
545                    // Check that any overlap is not larger than the trace coverage
546                    if gap < 0.0 {
547                        let sample_rate = unsafe { (*tseg.inner).samprate };
548                        let delta = if sample_rate != 0.0 {
549                            1.0 / sample_rate
550                        } else {
551                            0.0
552                        };
553                        if gap * -1.0
554                            > ((end_time - start_time) as f64 / raw::NSTMODULUS as f64 + delta)
555                        {
556                            gap = -1.0 * (end_time - start_time) as f64 / raw::NSTMODULUS as f64
557                                + delta;
558                        }
559                    }
560
561                    let gap_str = if no_gap {
562                        " == ".to_string()
563                    } else if gap >= 86400.0 || gap <= -86400.0 {
564                        format!("{:<3.1}d", gap / 86400.0)
565                    } else if gap >= 3600.0 || gap <= -3600.0 {
566                        format!("{:<3.1}h", gap / 3600.0)
567                    } else if gap == 0.0 {
568                        "-0  ".to_string()
569                    } else {
570                        format!("{:<4.4}", gap)
571                    };
572
573                    if self.detail <= 0 {
574                        writeln!(
575                            f,
576                            "{:<27} {:<28} {:<28} {:<4}",
577                            sid, start_time_str, end_time_str, gap_str
578                        )?;
579                    } else {
580                        writeln!(
581                            f,
582                            "{:<27} {:<28} {:<28} {:<} {:<3.3} {:<}",
583                            sid,
584                            start_time_str,
585                            end_time_str,
586                            gap_str,
587                            tseg.sample_rate_hz(),
588                            tseg.sample_cnt()
589                        )?;
590                    }
591                } else if self.detail > 0 && self.gap <= 0 {
592                    writeln!(
593                        f,
594                        "{:<27} {:<28} {:<28} {:<3.3} {:<}",
595                        sid,
596                        start_time_str,
597                        end_time_str,
598                        tseg.sample_rate_hz(),
599                        tseg.sample_cnt()
600                    )?;
601                } else {
602                    writeln!(f, "{:<27} {:<28} {:<28}", sid, start_time_str, end_time_str)?;
603                }
604
605                tseg_cnt += 1;
606            }
607
608            tid_cnt += 1;
609        }
610
611        if self.detail > 0 {
612            writeln!(
613                f,
614                "Total: {} trace(s) with {} segment(s)",
615                tid_cnt, tseg_cnt
616            )?;
617        }
618
619        Ok(())
620    }
621}
622
623#[cfg(test)]
624mod tests {
625
626    use super::*;
627
628    use pretty_assertions::assert_eq;
629    use time::format_description::well_known::Iso8601;
630
631    use crate::{test, MSReader, MSSampleType};
632
633    #[test]
634    fn test_read_unpack_mstl_mseed3() {
635        let mut p = test::test_data_base_dir();
636        assert!(p.is_dir());
637
638        p.push("testdata-oneseries-mixedlengths-mixedorder.mseed3");
639
640        let mut mstl = MSTraceList::new().unwrap();
641
642        let flags = MSControlFlags::MSF_UNPACKDATA;
643        let mut reader = MSReader::new_with_flags(p, flags).unwrap();
644
645        while let Some(res) = reader.next() {
646            let msr = res.unwrap();
647            mstl.insert(msr, true).unwrap();
648        }
649
650        assert_eq!(mstl.len(), 1);
651        let mut mstl_iter = mstl.iter();
652        let trace_id = mstl_iter.next();
653        assert!(trace_id.is_some());
654        let trace_id = trace_id.unwrap();
655        assert_eq!(&trace_id.sid().unwrap(), "FDSN:XX_TEST_00_L_H_Z");
656        assert_eq!(trace_id.pub_version(), 1);
657        assert_eq!(trace_id.len(), 1);
658        assert_eq!(
659            trace_id
660                .start_time()
661                .unwrap()
662                .format(&Iso8601::DEFAULT)
663                .unwrap(),
664            "2010-02-27T06:50:00.069539000Z"
665        );
666        assert_eq!(
667            trace_id
668                .end_time()
669                .unwrap()
670                .format(&Iso8601::DEFAULT)
671                .unwrap(),
672            "2010-02-27T07:55:51.069539000Z"
673        );
674        let mut trace_id_iter = trace_id.iter();
675        let trace_seg = trace_id_iter.next();
676        assert!(trace_seg.is_some());
677        let mut trace_seg = trace_seg.unwrap();
678        assert_eq!(
679            trace_seg
680                .start_time()
681                .unwrap()
682                .format(&Iso8601::DEFAULT)
683                .unwrap(),
684            "2010-02-27T06:50:00.069539000Z"
685        );
686        assert_eq!(
687            trace_seg
688                .end_time()
689                .unwrap()
690                .format(&Iso8601::DEFAULT)
691                .unwrap(),
692            "2010-02-27T07:55:51.069539000Z"
693        );
694        assert_eq!(trace_seg.sample_cnt(), 3952);
695        assert_eq!(trace_seg.sample_type(), MSSampleType::Integer32);
696        assert_eq!(trace_seg.num_samples(), 3952);
697
698        let data_samples: &[i32] = trace_seg.data_samples().unwrap();
699        // Test last 4 decoded sample values
700        assert_eq!(data_samples[3948], 28067);
701        assert_eq!(data_samples[3949], -9565);
702        assert_eq!(data_samples[3950], -71961);
703        assert_eq!(data_samples[3951], -146622);
704
705        assert!(trace_id_iter.next().is_none());
706        assert!(mstl_iter.next().is_none());
707    }
708
709    #[test]
710    fn test_read_unpack_mstl_mseed2() {
711        let mut p = test::test_data_base_dir();
712        assert!(p.is_dir());
713
714        p.push("testdata-oneseries-mixedlengths-mixedorder.mseed2");
715
716        let mut mstl = MSTraceList::new().unwrap();
717
718        let flags = MSControlFlags::MSF_UNPACKDATA;
719        let mut reader = MSReader::new_with_flags(p, flags).unwrap();
720
721        while let Some(res) = reader.next() {
722            let msr = res.unwrap();
723            mstl.insert(msr, true).unwrap();
724        }
725
726        assert_eq!(mstl.len(), 1);
727        let mut mstl_iter = mstl.iter();
728        let trace_id = mstl_iter.next();
729        assert!(trace_id.is_some());
730        let trace_id = trace_id.unwrap();
731        assert_eq!(&trace_id.sid().unwrap(), "FDSN:XX_TEST_00_L_H_Z");
732        assert_eq!(trace_id.pub_version(), 1);
733        assert_eq!(trace_id.len(), 1);
734        assert_eq!(
735            trace_id
736                .start_time()
737                .unwrap()
738                .format(&Iso8601::DEFAULT)
739                .unwrap(),
740            "2010-02-27T06:50:00.069539000Z"
741        );
742        assert_eq!(
743            trace_id
744                .end_time()
745                .unwrap()
746                .format(&Iso8601::DEFAULT)
747                .unwrap(),
748            "2010-02-27T07:55:51.069539000Z"
749        );
750        let mut trace_id_iter = trace_id.iter();
751        let trace_seg = trace_id_iter.next();
752        assert!(trace_seg.is_some());
753        let mut trace_seg = trace_seg.unwrap();
754        assert_eq!(
755            trace_seg
756                .start_time()
757                .unwrap()
758                .format(&Iso8601::DEFAULT)
759                .unwrap(),
760            "2010-02-27T06:50:00.069539000Z"
761        );
762        assert_eq!(
763            trace_seg
764                .end_time()
765                .unwrap()
766                .format(&Iso8601::DEFAULT)
767                .unwrap(),
768            "2010-02-27T07:55:51.069539000Z"
769        );
770        assert_eq!(trace_seg.sample_cnt(), 3952);
771        assert_eq!(trace_seg.sample_type(), MSSampleType::Integer32);
772        assert_eq!(trace_seg.num_samples(), 3952);
773
774        let data_samples: &[i32] = trace_seg.data_samples().unwrap();
775        // Test last 4 decoded sample values
776        assert_eq!(data_samples[3948], 28067);
777        assert_eq!(data_samples[3949], -9565);
778        assert_eq!(data_samples[3950], -71961);
779        assert_eq!(data_samples[3951], -146622);
780
781        assert!(trace_id_iter.next().is_none());
782        assert!(mstl_iter.next().is_none());
783    }
784}