Skip to main content

labstream_wire/
lib.rs

1//! The LSL sample codec for protocol 1.10.
2//!
3//! This crate holds no input and no output. It takes bytes and returns bytes.
4//! Every rule here comes from `SPEC.md`, which cites the pinned oracle.
5//!
6//! The codec is the one part of LSL that a test can compare byte for byte. A
7//! fixed sample with a fixed byte order always produces the same bytes.
8
9#![forbid(unsafe_code)]
10#![deny(missing_docs)]
11
12use core::fmt;
13
14/// Tag that starts a sample with no timestamp bytes. SPEC.md 7.1.
15pub const TAG_DEDUCED_TIMESTAMP: u8 = 1;
16/// Tag that starts a sample with an `f64` timestamp. SPEC.md 7.1.
17pub const TAG_TRANSMITTED_TIMESTAMP: u8 = 2;
18
19/// The timestamp value that means "deduce this on the reading side".
20///
21/// liblsl uses `-1.0` (`include/lsl/common.h:48`).
22pub const DEDUCED_TIMESTAMP: f64 = -1.0;
23
24/// The nominal rate that means "this stream has no fixed rate".
25///
26/// liblsl uses `0.0` (`include/lsl/common.h:39`).
27pub const IRREGULAR_RATE: f64 = 0.0;
28
29/// A channel format. The values match `include/lsl/common.h:64-84`.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31#[repr(u8)]
32pub enum Format {
33    /// No format. A stream never carries this.
34    Undefined = 0,
35    /// 32-bit float.
36    Float32 = 1,
37    /// 64-bit float.
38    Double64 = 2,
39    /// A length-prefixed byte string.
40    String = 3,
41    /// 32-bit signed integer.
42    Int32 = 4,
43    /// 16-bit signed integer.
44    Int16 = 5,
45    /// 8-bit signed integer.
46    Int8 = 6,
47    /// 64-bit signed integer.
48    Int64 = 7,
49}
50
51impl Format {
52    /// Build a format from its wire value.
53    pub fn from_u8(v: u8) -> Option<Self> {
54        Some(match v {
55            0 => Format::Undefined,
56            1 => Format::Float32,
57            2 => Format::Double64,
58            3 => Format::String,
59            4 => Format::Int32,
60            5 => Format::Int16,
61            6 => Format::Int8,
62            7 => Format::Int64,
63            _ => return None,
64        })
65    }
66
67    /// The width of one value in bytes. A string has no fixed width.
68    pub fn width(self) -> Option<usize> {
69        Some(match self {
70            Format::Undefined | Format::String => return None,
71            Format::Float32 | Format::Int32 => 4,
72            Format::Double64 | Format::Int64 => 8,
73            Format::Int16 => 2,
74            Format::Int8 => 1,
75        })
76    }
77
78    /// True when the format holds a floating point value.
79    ///
80    /// liblsl calls this table `format_float` (`src/sample.h:32`).
81    pub fn is_float(self) -> bool {
82        matches!(self, Format::Float32 | Format::Double64)
83    }
84}
85
86/// One channel value.
87#[derive(Debug, Clone, PartialEq)]
88pub enum Value {
89    /// A 32-bit float.
90    F32(f32),
91    /// A 64-bit float.
92    F64(f64),
93    /// A byte string. LSL does not require valid UTF-8.
94    Str(Vec<u8>),
95    /// A 32-bit signed integer.
96    I32(i32),
97    /// A 16-bit signed integer.
98    I16(i16),
99    /// An 8-bit signed integer.
100    I8(i8),
101    /// A 64-bit signed integer.
102    I64(i64),
103}
104
105/// One sample: a timestamp and one value per channel.
106#[derive(Debug, Clone, PartialEq)]
107pub struct Sample {
108    /// The timestamp. `DEDUCED_TIMESTAMP` means the reader derives it.
109    pub timestamp: f64,
110    /// One value per channel. Every value carries the same format.
111    pub values: Vec<Value>,
112}
113
114/// The byte order that a connection selected.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum ByteOrder {
117    /// Least significant byte first. liblsl writes `1234`.
118    Little,
119    /// Most significant byte first. liblsl writes `4321`.
120    Big,
121}
122
123impl ByteOrder {
124    /// The value that liblsl writes in the `Byte-Order` header. SPEC.md 4.6.
125    pub fn wire_value(self) -> i32 {
126        match self {
127            ByteOrder::Little => 1234,
128            ByteOrder::Big => 4321,
129        }
130    }
131
132    /// Read a byte order from a `Byte-Order` header value.
133    ///
134    /// A value of `0` means the native order of the reader. That case exists
135    /// for liblsl 1.13 and earlier (`src/data_receiver.cpp:231`).
136    pub fn from_wire(v: i32) -> Option<Self> {
137        match v {
138            1234 => Some(ByteOrder::Little),
139            4321 => Some(ByteOrder::Big),
140            0 => Some(ByteOrder::native()),
141            _ => None,
142        }
143    }
144
145    /// The byte order of this machine.
146    pub const fn native() -> Self {
147        if cfg!(target_endian = "little") {
148            ByteOrder::Little
149        } else {
150            ByteOrder::Big
151        }
152    }
153}
154
155/// Everything a codec call needs to know about the connection.
156#[derive(Debug, Clone, Copy)]
157pub struct Codec {
158    /// The format of every channel in the stream.
159    pub format: Format,
160    /// The number of channels in one sample.
161    pub channels: usize,
162    /// The byte order that the connection selected.
163    pub order: ByteOrder,
164    /// True when the reader clears subnormal values. SPEC.md 7.4.
165    pub suppress_subnormals: bool,
166}
167
168impl Codec {
169    /// Build a codec for a stream.
170    pub fn new(format: Format, channels: usize, order: ByteOrder) -> Self {
171        Codec {
172            format,
173            channels,
174            order,
175            suppress_subnormals: false,
176        }
177    }
178
179    /// Return a copy that clears subnormal values on read.
180    pub fn with_suppress_subnormals(mut self, on: bool) -> Self {
181        self.suppress_subnormals = on;
182        self
183    }
184
185    /// True when a value needs a byte swap.
186    ///
187    /// A value of one byte never needs one (`src/sample.cpp:219`).
188    fn swaps(&self) -> bool {
189        self.order != ByteOrder::native() && self.format.width().map_or(true, |w| w > 1)
190    }
191}
192
193/// A decode error.
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum Error {
196    /// The buffer ended before the value did.
197    Truncated,
198    /// The tag byte was neither 1 nor 2.
199    BadTag(u8),
200    /// The width byte of a string length was not 1, 2, 4, or 8.
201    ///
202    /// liblsl reports `Stream contents corrupted (invalid varlen int).`
203    /// (`src/sample.cpp:256`).
204    BadLengthWidth(u8),
205    /// A string length did not fit in this platform's `usize`.
206    LengthTooLarge,
207    /// The codec holds a format that a stream never carries.
208    UndefinedFormat,
209    /// A value did not match the format of the codec.
210    FormatMismatch,
211    /// The sample held a different number of values than the codec expects.
212    ChannelCountMismatch {
213        /// The count that the codec expects.
214        expected: usize,
215        /// The count that the sample holds.
216        actual: usize,
217    },
218}
219
220impl fmt::Display for Error {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        match self {
223            Error::Truncated => write!(f, "the buffer ended inside a value"),
224            Error::BadTag(t) => write!(f, "the tag byte {t} is not 1 or 2"),
225            Error::BadLengthWidth(w) => write!(f, "the length width {w} is not 1, 2, 4, or 8"),
226            Error::LengthTooLarge => write!(f, "the string length does not fit in usize"),
227            Error::UndefinedFormat => write!(f, "the format is undefined"),
228            Error::FormatMismatch => write!(f, "a value does not match the codec format"),
229            Error::ChannelCountMismatch { expected, actual } => {
230                write!(
231                    f,
232                    "the sample holds {actual} values and the codec expects {expected}"
233                )
234            }
235        }
236    }
237}
238
239impl std::error::Error for Error {}
240
241// ---------------------------------------------------------------------------
242// encode
243// ---------------------------------------------------------------------------
244
245macro_rules! put {
246    ($out:expr, $v:expr, $swap:expr) => {{
247        let b = if $swap {
248            $v.to_be_bytes()
249        } else {
250            $v.to_le_bytes()
251        };
252        $out.extend_from_slice(&b);
253    }};
254}
255
256impl Codec {
257    /// Write one sample to the end of `out`.
258    ///
259    /// The layout is the tag byte, then an optional timestamp, then one value
260    /// per channel. SPEC.md 7.1.
261    pub fn encode(&self, s: &Sample, out: &mut Vec<u8>) -> Result<(), Error> {
262        if self.format == Format::Undefined {
263            return Err(Error::UndefinedFormat);
264        }
265        if s.values.len() != self.channels {
266            return Err(Error::ChannelCountMismatch {
267                expected: self.channels,
268                actual: s.values.len(),
269            });
270        }
271
272        // A deduced timestamp writes the tag and nothing else.
273        if s.timestamp == DEDUCED_TIMESTAMP {
274            out.push(TAG_DEDUCED_TIMESTAMP);
275        } else {
276            out.push(TAG_TRANSMITTED_TIMESTAMP);
277            // The timestamp always swaps with the connection, because it is
278            // eight bytes wide. The format width does not apply to it.
279            let swap = self.order != ByteOrder::native();
280            put!(out, s.timestamp.to_bits(), swap);
281        }
282
283        let swap = self.swaps();
284        for v in &s.values {
285            match (self.format, v) {
286                (Format::Float32, Value::F32(x)) => put!(out, x.to_bits(), swap),
287                (Format::Double64, Value::F64(x)) => put!(out, x.to_bits(), swap),
288                (Format::Int32, Value::I32(x)) => put!(out, x, swap),
289                (Format::Int16, Value::I16(x)) => put!(out, x, swap),
290                (Format::Int8, Value::I8(x)) => out.push(*x as u8),
291                (Format::Int64, Value::I64(x)) => put!(out, x, swap),
292                (Format::String, Value::Str(b)) => {
293                    encode_string_len(b.len(), swap, out);
294                    out.extend_from_slice(b);
295                }
296                _ => return Err(Error::FormatMismatch),
297            }
298        }
299        Ok(())
300    }
301
302    /// Write one sample and return the bytes.
303    pub fn encode_to_vec(&self, s: &Sample) -> Result<Vec<u8>, Error> {
304        let mut v = Vec::new();
305        self.encode(s, &mut v)?;
306        Ok(v)
307    }
308}
309
310/// Write a string length as a width byte and then the length.
311///
312/// The encoder writes the widths 1, 4, and 8 only. It never writes 2. The
313/// decoder still accepts 2. SPEC.md 7.3 explains why that matters.
314fn encode_string_len(len: usize, swap: bool, out: &mut Vec<u8>) {
315    if len <= 0xFF {
316        out.push(1);
317        out.push(len as u8);
318    } else if len <= 0xFFFF_FFFF {
319        out.push(4);
320        put!(out, (len as u32), swap);
321    } else {
322        out.push(8);
323        put!(out, (len as u64), swap);
324    }
325}
326
327// ---------------------------------------------------------------------------
328// decode
329// ---------------------------------------------------------------------------
330
331/// A cursor over a byte slice.
332struct Reader<'a> {
333    buf: &'a [u8],
334    pos: usize,
335}
336
337impl<'a> Reader<'a> {
338    fn new(buf: &'a [u8]) -> Self {
339        Reader { buf, pos: 0 }
340    }
341
342    fn byte(&mut self) -> Result<u8, Error> {
343        let b = *self.buf.get(self.pos).ok_or(Error::Truncated)?;
344        self.pos += 1;
345        Ok(b)
346    }
347
348    fn take(&mut self, n: usize) -> Result<&'a [u8], Error> {
349        let end = self.pos.checked_add(n).ok_or(Error::Truncated)?;
350        let s = self.buf.get(self.pos..end).ok_or(Error::Truncated)?;
351        self.pos = end;
352        Ok(s)
353    }
354}
355
356macro_rules! get {
357    ($r:expr, $t:ty, $swap:expr) => {{
358        const N: usize = core::mem::size_of::<$t>();
359        let s = $r.take(N)?;
360        let mut a = [0u8; N];
361        a.copy_from_slice(s);
362        if $swap {
363            <$t>::from_be_bytes(a)
364        } else {
365            <$t>::from_le_bytes(a)
366        }
367    }};
368}
369
370impl Codec {
371    /// Read one sample from the start of `buf`.
372    ///
373    /// Returns the sample and the number of bytes that it used.
374    pub fn decode(&self, buf: &[u8]) -> Result<(Sample, usize), Error> {
375        if self.format == Format::Undefined {
376            return Err(Error::UndefinedFormat);
377        }
378        let mut r = Reader::new(buf);
379
380        let tag = r.byte()?;
381        let timestamp = match tag {
382            TAG_DEDUCED_TIMESTAMP => DEDUCED_TIMESTAMP,
383            TAG_TRANSMITTED_TIMESTAMP => {
384                let swap = self.order != ByteOrder::native();
385                f64::from_bits(get!(r, u64, swap))
386            }
387            other => return Err(Error::BadTag(other)),
388        };
389
390        let swap = self.swaps();
391        let mut values = Vec::with_capacity(self.channels);
392        for _ in 0..self.channels {
393            values.push(match self.format {
394                Format::Float32 => {
395                    let mut bits = get!(r, u32, swap);
396                    // Suppression runs after the byte order conversion.
397                    // SPEC.md 7.4, `src/sample.cpp:267-280`.
398                    if self.suppress_subnormals && bits != 0 && (bits & 0x7fff_ffff) <= 0x007f_ffff
399                    {
400                        bits &= 0x8000_0000;
401                    }
402                    Value::F32(f32::from_bits(bits))
403                }
404                Format::Double64 => {
405                    let mut bits = get!(r, u64, swap);
406                    if self.suppress_subnormals
407                        && bits != 0
408                        && (bits & 0x7fff_ffff_ffff_ffff) <= 0x000f_ffff_ffff_ffff
409                    {
410                        bits &= 0x8000_0000_0000_0000;
411                    }
412                    Value::F64(f64::from_bits(bits))
413                }
414                Format::Int32 => Value::I32(get!(r, i32, swap)),
415                Format::Int16 => Value::I16(get!(r, i16, swap)),
416                Format::Int8 => Value::I8(r.byte()? as i8),
417                Format::Int64 => Value::I64(get!(r, i64, swap)),
418                Format::String => {
419                    let len = decode_string_len(&mut r, swap)?;
420                    Value::Str(r.take(len)?.to_vec())
421                }
422                Format::Undefined => return Err(Error::UndefinedFormat),
423            });
424        }
425
426        Ok((Sample { timestamp, values }, r.pos))
427    }
428
429    /// Read as many whole samples as `buf` holds.
430    ///
431    /// Returns the samples and the number of bytes that they used. Trailing
432    /// bytes that do not form a whole sample stay unread.
433    pub fn decode_all(&self, buf: &[u8]) -> Result<(Vec<Sample>, usize), Error> {
434        let mut out = Vec::new();
435        let mut off = 0;
436        loop {
437            match self.decode(&buf[off..]) {
438                Ok((s, n)) => {
439                    out.push(s);
440                    off += n;
441                    if off >= buf.len() {
442                        break;
443                    }
444                }
445                Err(Error::Truncated) => break,
446                Err(e) => return Err(e),
447            }
448        }
449        Ok((out, off))
450    }
451}
452
453/// Read a string length: a width byte, then the length in that width.
454///
455/// The decoder accepts the width 2. No liblsl outlet writes it.
456/// SPEC.md 7.3, `src/sample.cpp:251`.
457fn decode_string_len(r: &mut Reader<'_>, swap: bool) -> Result<usize, Error> {
458    let width = r.byte()?;
459    let len: u64 = match width {
460        1 => r.byte()? as u64,
461        2 => get!(r, u16, swap) as u64,
462        4 => get!(r, u32, swap) as u64,
463        8 => get!(r, u64, swap),
464        other => return Err(Error::BadLengthWidth(other)),
465    };
466    usize::try_from(len).map_err(|_| Error::LengthTooLarge)
467}
468
469// ---------------------------------------------------------------------------
470// the test pattern
471// ---------------------------------------------------------------------------
472
473/// The timestamp that every test pattern sample carries.
474///
475/// `src/sample.cpp:366`.
476pub const TEST_PATTERN_TIMESTAMP: f64 = 123456.789;
477
478/// The two offsets that a feed sends, in order. SPEC.md 6.1.
479pub const TEST_PATTERN_OFFSETS: [i64; 2] = [4, 2];
480
481/// Build the test pattern sample that a feed sends.
482///
483/// A feed opens with two of these, at the offsets in `TEST_PATTERN_OFFSETS`.
484/// An implementation that skips them loses the whole stream. SPEC.md 6.
485pub fn test_pattern(format: Format, channels: usize, offset: i64) -> Sample {
486    // The caller offset adds to a base that depends on the format.
487    // `src/sample.cpp:368-400`.
488    let base: i64 = match format {
489        Format::Float32 => 0,
490        Format::Double64 => 16_777_217,
491        Format::Int32 => 65_537,
492        Format::Int16 => 257,
493        Format::Int8 => 1,
494        Format::Int64 => 2_147_483_649,
495        Format::String | Format::Undefined => 0,
496    };
497    let off = base + offset;
498
499    let values = (0..channels)
500        .map(|k| {
501            let k = k as i64;
502            // A string channel ignores the offset (`src/sample.cpp:375-380`).
503            if format == Format::String {
504                let v = (k + 10) * if k % 2 == 0 { 1 } else { -1 };
505                return Value::Str(v.to_string().into_bytes());
506            }
507            // liblsl computes `k + offset` in an unsigned type, takes it
508            // modulo the type maximum for an integer format, and then applies
509            // the sign (`src/sample.cpp:356-362`).
510            let raw = (k as u64).wrapping_add(off as u64);
511            let sign = k % 2 == 0;
512            match format {
513                Format::Float32 => {
514                    let v = raw as f32;
515                    Value::F32(if sign { v } else { -v })
516                }
517                Format::Double64 => {
518                    let v = raw as f64;
519                    Value::F64(if sign { v } else { -v })
520                }
521                Format::Int32 => {
522                    let v = (raw % i32::MAX as u64) as i32;
523                    Value::I32(if sign { v } else { -v })
524                }
525                Format::Int16 => {
526                    let v = (raw % i16::MAX as u64) as i16;
527                    Value::I16(if sign { v } else { -v })
528                }
529                Format::Int8 => {
530                    let v = (raw % i8::MAX as u64) as i8;
531                    Value::I8(if sign { v } else { -v })
532                }
533                Format::Int64 => {
534                    let v = (raw % i64::MAX as u64) as i64;
535                    Value::I64(if sign { v } else { -v })
536                }
537                Format::String | Format::Undefined => unreachable!(),
538            }
539        })
540        .collect();
541
542    Sample {
543        timestamp: TEST_PATTERN_TIMESTAMP,
544        values,
545    }
546}
547
548// ---------------------------------------------------------------------------
549// timestamp reconstruction
550// ---------------------------------------------------------------------------
551
552/// Rebuilds a deduced timestamp from the position in the stream.
553///
554/// The accumulator runs for the life of the connection. Every deduced
555/// timestamp depends on every sample before it. SPEC.md 8.1.
556#[derive(Debug, Clone)]
557pub struct TimestampDeducer {
558    last: f64,
559    srate: f64,
560}
561
562impl TimestampDeducer {
563    /// Build a deducer for a stream with the given nominal rate.
564    ///
565    /// liblsl starts the accumulator at `0.0` (`src/data_receiver.cpp:310`).
566    pub fn new(srate: f64) -> Self {
567        TimestampDeducer { last: 0.0, srate }
568    }
569
570    /// Replace a deduced timestamp with a real one.
571    pub fn apply(&mut self, timestamp: f64) -> f64 {
572        let mut t = timestamp;
573        if t == DEDUCED_TIMESTAMP {
574            t = self.last;
575            if self.srate != IRREGULAR_RATE {
576                t += 1.0 / self.srate;
577            }
578        }
579        self.last = t;
580        t
581    }
582}
583
584#[cfg(test)]
585mod tests {
586    use super::*;
587
588    fn rt(c: &Codec, s: &Sample) -> Sample {
589        let b = c.encode_to_vec(s).unwrap();
590        let (got, n) = c.decode(&b).unwrap();
591        assert_eq!(n, b.len(), "decode did not use every byte");
592        got
593    }
594
595    #[test]
596    fn deduced_tag_is_one_byte() {
597        let c = Codec::new(Format::Float32, 2, ByteOrder::Little);
598        let s = Sample {
599            timestamp: DEDUCED_TIMESTAMP,
600            values: vec![Value::F32(1.0), Value::F32(2.0)],
601        };
602        let b = c.encode_to_vec(&s).unwrap();
603        assert_eq!(b[0], TAG_DEDUCED_TIMESTAMP);
604        assert_eq!(b.len(), 1 + 8);
605        assert_eq!(rt(&c, &s), s);
606    }
607
608    #[test]
609    fn transmitted_tag_carries_eight_bytes() {
610        let c = Codec::new(Format::Int8, 1, ByteOrder::Little);
611        let s = Sample {
612            timestamp: 1.5,
613            values: vec![Value::I8(-3)],
614        };
615        let b = c.encode_to_vec(&s).unwrap();
616        assert_eq!(b[0], TAG_TRANSMITTED_TIMESTAMP);
617        assert_eq!(b.len(), 1 + 8 + 1);
618        assert_eq!(rt(&c, &s), s);
619    }
620
621    #[test]
622    fn string_width_boundaries() {
623        let c = Codec::new(Format::String, 1, ByteOrder::Little);
624        for (len, want_width) in [(0usize, 1u8), (255, 1), (256, 4), (257, 4)] {
625            let s = Sample {
626                timestamp: 0.0,
627                values: vec![Value::Str(vec![b'x'; len])],
628            };
629            let b = c.encode_to_vec(&s).unwrap();
630            assert_eq!(b[9], want_width, "length {len} picked the wrong width");
631            assert_eq!(rt(&c, &s), s);
632        }
633    }
634
635    #[test]
636    fn decoder_accepts_width_two_that_no_encoder_writes() {
637        // SPEC.md 7.3. liblsl never writes this, and it always reads it.
638        let c = Codec::new(Format::String, 1, ByteOrder::Little);
639        let mut b = vec![TAG_TRANSMITTED_TIMESTAMP];
640        b.extend_from_slice(&0.0f64.to_le_bytes());
641        b.push(2);
642        b.extend_from_slice(&3u16.to_le_bytes());
643        b.extend_from_slice(b"abc");
644        let (s, n) = c.decode(&b).unwrap();
645        assert_eq!(n, b.len());
646        assert_eq!(s.values, vec![Value::Str(b"abc".to_vec())]);
647    }
648
649    #[test]
650    fn bad_length_width_is_an_error() {
651        let c = Codec::new(Format::String, 1, ByteOrder::Little);
652        let mut b = vec![TAG_TRANSMITTED_TIMESTAMP];
653        b.extend_from_slice(&0.0f64.to_le_bytes());
654        b.push(3);
655        assert_eq!(c.decode(&b), Err(Error::BadLengthWidth(3)));
656    }
657
658    #[test]
659    fn bad_tag_is_an_error() {
660        let c = Codec::new(Format::Int8, 1, ByteOrder::Little);
661        assert_eq!(c.decode(&[7, 0]), Err(Error::BadTag(7)));
662    }
663
664    #[test]
665    fn truncated_input_is_an_error() {
666        let c = Codec::new(Format::Double64, 4, ByteOrder::Little);
667        let b = vec![TAG_DEDUCED_TIMESTAMP, 0, 0, 0];
668        assert_eq!(c.decode(&b), Err(Error::Truncated));
669    }
670
671    #[test]
672    fn nan_and_infinity_survive_a_round_trip() {
673        let c = Codec::new(Format::Double64, 4, ByteOrder::Little);
674        let s = Sample {
675            timestamp: 0.0,
676            values: vec![
677                Value::F64(f64::INFINITY),
678                Value::F64(f64::NEG_INFINITY),
679                Value::F64(-0.0),
680                Value::F64(f64::MIN_POSITIVE / 2.0),
681            ],
682        };
683        let got = rt(&c, &s);
684        assert_eq!(got, s);
685        // -0.0 == 0.0 compares true, so compare the bits.
686        if let (Value::F64(a), Value::F64(b)) = (&got.values[2], &s.values[2]) {
687            assert_eq!(a.to_bits(), b.to_bits());
688        }
689    }
690
691    #[test]
692    fn big_endian_differs_from_little_endian() {
693        let le = Codec::new(Format::Int32, 1, ByteOrder::Little);
694        let be = Codec::new(Format::Int32, 1, ByteOrder::Big);
695        let s = Sample {
696            timestamp: 0.0,
697            values: vec![Value::I32(1)],
698        };
699        assert_ne!(le.encode_to_vec(&s).unwrap(), be.encode_to_vec(&s).unwrap());
700        assert_eq!(be.decode(&be.encode_to_vec(&s).unwrap()).unwrap().0, s);
701    }
702
703    #[test]
704    fn int8_never_swaps() {
705        let le = Codec::new(Format::Int8, 3, ByteOrder::Little);
706        let be = Codec::new(Format::Int8, 3, ByteOrder::Big);
707        let s = Sample {
708            timestamp: DEDUCED_TIMESTAMP,
709            values: vec![Value::I8(1), Value::I8(-2), Value::I8(3)],
710        };
711        assert_eq!(le.encode_to_vec(&s).unwrap(), be.encode_to_vec(&s).unwrap());
712    }
713
714    #[test]
715    fn subnormal_suppression_keeps_the_sign() {
716        let c = Codec::new(Format::Float32, 2, ByteOrder::Little).with_suppress_subnormals(true);
717        let plain = Codec::new(Format::Float32, 2, ByteOrder::Little);
718        let s = Sample {
719            timestamp: 0.0,
720            values: vec![
721                Value::F32(f32::from_bits(0x0000_0001)),
722                Value::F32(f32::from_bits(0x8000_0001)),
723            ],
724        };
725        let b = plain.encode_to_vec(&s).unwrap();
726        let (got, _) = c.decode(&b).unwrap();
727        assert_eq!(got.values[0], Value::F32(0.0));
728        match got.values[1] {
729            Value::F32(v) => assert_eq!(v.to_bits(), 0x8000_0000),
730            _ => panic!("wrong format"),
731        }
732    }
733
734    #[test]
735    fn channel_count_mismatch_is_an_error() {
736        let c = Codec::new(Format::Int16, 4, ByteOrder::Little);
737        let s = Sample {
738            timestamp: 0.0,
739            values: vec![Value::I16(1)],
740        };
741        assert_eq!(
742            c.encode(&s, &mut Vec::new()),
743            Err(Error::ChannelCountMismatch {
744                expected: 4,
745                actual: 1
746            })
747        );
748    }
749
750    #[test]
751    fn deducer_adds_one_period() {
752        let mut d = TimestampDeducer::new(100.0);
753        assert!((d.apply(DEDUCED_TIMESTAMP) - 0.01).abs() < 1e-12);
754        assert!((d.apply(DEDUCED_TIMESTAMP) - 0.02).abs() < 1e-12);
755        assert_eq!(d.apply(5.0), 5.0);
756        assert!((d.apply(DEDUCED_TIMESTAMP) - 5.01).abs() < 1e-12);
757    }
758
759    #[test]
760    fn deducer_holds_the_value_for_an_irregular_rate() {
761        let mut d = TimestampDeducer::new(IRREGULAR_RATE);
762        assert_eq!(d.apply(DEDUCED_TIMESTAMP), 0.0);
763        assert_eq!(d.apply(7.0), 7.0);
764        assert_eq!(d.apply(DEDUCED_TIMESTAMP), 7.0);
765    }
766
767    #[test]
768    fn test_pattern_matches_the_captured_stream() {
769        // From captures/feed.json. SPEC.md 6.4.
770        let s = test_pattern(Format::Float32, 8, 4);
771        assert_eq!(s.timestamp, TEST_PATTERN_TIMESTAMP);
772        let want: Vec<f32> = vec![4.0, -5.0, 6.0, -7.0, 8.0, -9.0, 10.0, -11.0];
773        let got: Vec<f32> = s
774            .values
775            .iter()
776            .map(|v| match v {
777                Value::F32(x) => *x,
778                _ => panic!("wrong format"),
779            })
780            .collect();
781        assert_eq!(got, want);
782    }
783}