tekhsi_rs 0.1.1

High-performance client for Tektronix TekHSI enabled oscilloscopes
Documentation
use zerocopy::FromBytes;
use zerocopy::byteorder::{F32, F64, I16, I32, LE};

macro_rules! impl_read_chunks {
    ($name:ident, $native:ty, $zerocopy:ty, $bytes:expr) => {
        pub(crate) fn $name<T: AsRef<[u8]>>(chunks: &[T], sample_count: usize) -> Vec<$native> {
            let mut output = Vec::with_capacity(sample_count);
            let mut carry_buf = [0u8; $bytes];
            let mut carry_len = 0;

            for chunk in chunks {
                if output.len() >= sample_count {
                    break;
                }
                let mut data = chunk.as_ref();

                // Handle carry from the previous chunk
                if carry_len > 0 {
                    let needed = $bytes - carry_len;
                    if data.len() < needed {
                        carry_buf[carry_len..carry_len + data.len()].copy_from_slice(data);
                        carry_len += data.len();
                        continue;
                    }

                    carry_buf[carry_len..].copy_from_slice(&data[..needed]);
                    // Safely unwrap: we constructed exactly $bytes
                    let val = <$zerocopy>::read_from_bytes(&carry_buf).unwrap();
                    output.push(val.get());
                    carry_len = 0;
                    data = &data[needed..];
                }

                // Handle bulk data
                let remaining = sample_count - output.len();
                let available_elements = data.len() / $bytes;
                let count = remaining.min(available_elements);

                let bytes_to_consume = count * $bytes;
                let (bulk_bytes, rest) = data.split_at(bytes_to_consume);

                // Safety: $zerocopy is FromBytes + Unaligned
                let slice_ref = zerocopy::Ref::<&[u8], [$zerocopy]>::from_bytes(bulk_bytes)
                    .expect("slice length must be multiple of element size");
                let vals: &[$zerocopy] = zerocopy::Ref::into_ref(slice_ref);

                output.extend(vals.iter().map(|v| v.get()));

                data = rest;

                // Store leftover for next carry
                if !data.is_empty() && output.len() < sample_count {
                    carry_buf[..data.len()].copy_from_slice(data);
                    carry_len = data.len();
                }
            }

            // Zero-fill remaining
            if output.len() < sample_count {
                output.resize(sample_count, <$native>::default());
            }

            output
        }
    };
}

// i8 is special (no byte order, 1 byte)
pub(crate) fn read_i8_chunks<T: AsRef<[u8]>>(chunks: &[T], sample_count: usize) -> Vec<i8> {
    let mut output = Vec::with_capacity(sample_count);
    for chunk in chunks {
        if output.len() >= sample_count {
            break;
        }
        let data = chunk.as_ref();
        let remaining = sample_count - output.len();
        let count = remaining.min(data.len());

        // Safety: i8 and u8 have same layout
        let cast_data: &[i8] =
            unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i8, count) };
        output.extend_from_slice(cast_data);
    }
    if output.len() < sample_count {
        output.resize(sample_count, 0);
    }
    output
}

impl_read_chunks!(read_i16_chunks, i16, I16<LE>, 2);
impl_read_chunks!(read_i32_chunks, i32, I32<LE>, 4);
impl_read_chunks!(read_f32_chunks, f32, F32<LE>, 4);
impl_read_chunks!(read_f64_chunks, f64, F64<LE>, 8);

#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
    use super::*;

    #[test]
    fn read_f32_chunks_handles_unaligned_input() {
        // Create a buffer and slice it to be unaligned for f32 (offset 1)
        let mut raw = [0u8; 17];
        // data: [0.0, 1.0, 2.0, 3.0] as bytes
        let values = [0.0f32, 1.0, 2.0, 3.0];
        let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_le_bytes()).collect();

        // Write bytes at offset 1
        raw[1..17].copy_from_slice(&bytes);

        // Pass the unaligned slice
        let chunks = vec![&raw[1..]];
        let output = read_f32_chunks(&chunks, 4);

        assert_eq!(output, values);
    }
}