mdf4_rs/index.rs
1//! MDF File Indexing System
2//!
3//! This module provides a lightweight indexing system for MDF4 files, enabling
4//! efficient random access to channel data without loading the entire file into
5//! memory. Indexes can be serialized to JSON for caching and reuse.
6//!
7//! # Overview
8//!
9//! The MDF index system addresses a key challenge with large measurement files:
10//! reading specific channel data efficiently. Instead of parsing the entire file
11//! structure each time, you can:
12//!
13//! 1. **Build an index** that captures channel metadata and data locations
14//! 2. **Save the index** to disk for reuse across sessions
15//! 3. **Read channel data** by seeking directly to the relevant byte ranges
16//!
17//! This approach is particularly valuable for:
18//! - Large files (hundreds of MB to GB)
19//! - Remote files accessed via HTTP range requests
20//! - Applications that need to read specific channels repeatedly
21//!
22//! # Index Contents
23//!
24//! An [`MdfIndex`] contains:
25//! - Channel group metadata (names, record sizes, record counts)
26//! - Channel metadata (names, data types, byte offsets, conversions)
27//! - Data block locations (file offsets and sizes)
28//!
29//! # Performance Comparison
30//!
31//! | Operation | Full Parse | With Index |
32//! |-----------|-----------|------------|
33//! | Open 1GB file | 5-10 sec | <100ms |
34//! | Read 1 channel | Full parse | ~50ms |
35//! | Second channel | Full parse | ~50ms |
36//!
37//! # Example: Building and Using an Index
38//!
39//! ```no_run
40//! use mdf4_rs::{MdfIndex, FileRangeReader, Result};
41//!
42//! fn read_efficiently() -> Result<()> {
43//! // Option 1: Create index with streaming (minimal memory)
44//! let index = MdfIndex::from_file_streaming("large_file.mf4")?;
45//!
46//! // Save for later use (requires serde_json feature)
47//! index.save_to_file("large_file.index")?;
48//!
49//! // Option 2: Load pre-built index (instant)
50//! let index = MdfIndex::load_from_file("large_file.index")?;
51//!
52//! // Read only the channel you need
53//! let mut reader = FileRangeReader::new("large_file.mf4")?;
54//! let values = index.read_channel_values_by_name("Temperature", &mut reader)?;
55//!
56//! Ok(())
57//! }
58//! ```
59//!
60//! # Reader Types
61//!
62//! The index system supports multiple reader implementations:
63//!
64//! - [`FileRangeReader`]: Direct file access (simple, low memory)
65//! - [`BufferedRangeReader`]: Buffered file access (better for sequential reads)
66//! - Custom implementations: HTTP range requests, cloud storage, etc.
67//!
68//! # Feature Flags
69//!
70//! - `serde`: Enables index serialization/deserialization
71//! - `serde_json`: Enables JSON file save/load methods
72
73use crate::{
74 Error, MDF, Result,
75 blocks::{
76 BlockHeader, BlockParse, ChannelBlock, ChannelGroupBlock, ConversionBlock, ConversionType,
77 DataGroupBlock, DataListBlock, DataType, HeaderBlock, IdentificationBlock, TextBlock,
78 },
79 parsing::decoder::{DecodedValue, decode_channel_value_with_validity},
80};
81use std::collections::BTreeMap;
82use std::io::{Read, Seek, SeekFrom};
83
84/// Location and metadata for a data block within the MDF file.
85///
86/// Each channel group can have multiple data blocks, especially in files
87/// created with streaming writes. This struct stores the information needed
88/// to locate and read a specific data block.
89///
90/// # Data Block Types
91///
92/// - **DT blocks**: Uncompressed raw data (most common)
93/// - **DZ blocks**: Zlib-compressed data (requires decompression)
94/// - **DL blocks**: Data lists pointing to multiple blocks
95#[derive(Debug, Clone)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub struct DataBlockInfo {
98 /// Absolute file offset where the block header starts.
99 /// The actual data begins 24 bytes after this offset (after the block header).
100 pub file_offset: u64,
101 /// Total size of the block including the 24-byte header.
102 pub size: u64,
103 /// Whether this block contains compressed data (DZ block).
104 /// Compressed blocks require decompression before reading values.
105 pub is_compressed: bool,
106}
107
108/// Metadata for a single channel, containing all information needed to decode values.
109///
110/// This struct captures the essential channel properties from the MDF file's
111/// CN blocks, including data type, bit layout, and conversion formula. It enables
112/// decoding channel values without re-parsing the original MDF structure.
113///
114/// # Bit Layout
115///
116/// Values are extracted using `byte_offset`, `bit_offset`, and `bit_count`:
117/// - `byte_offset`: Starting byte within the record (after record ID)
118/// - `bit_offset`: Starting bit within that byte (0-7)
119/// - `bit_count`: Total number of bits to read
120///
121/// # Channel Types
122///
123/// - **Type 0**: Regular data channel
124/// - **Type 1**: Variable Length Signal Data (VLSD)
125/// - **Type 2**: Master channel (time, angle, etc.)
126/// - **Type 3**: Virtual master channel
127/// - **Type 4**: Synchronization channel
128/// - **Type 5**: Maximum length channel
129/// - **Type 6**: Virtual data channel
130#[derive(Debug, Clone)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132pub struct IndexedChannel {
133 /// Channel name (e.g., "EngineRPM", "Temperature")
134 pub name: Option<String>,
135 /// Physical unit (e.g., "rpm", "°C", "m/s")
136 pub unit: Option<String>,
137 /// Data type determining how raw bytes are interpreted
138 pub data_type: DataType,
139 /// Byte offset within each record (after record ID bytes)
140 pub byte_offset: u32,
141 /// Bit offset within the starting byte (0-7)
142 pub bit_offset: u8,
143 /// Number of bits for this channel's raw value
144 pub bit_count: u32,
145 /// Channel type (0=data, 1=VLSD, 2=master, etc.)
146 pub channel_type: u8,
147 /// Channel flags indicating invalidation bit presence and other properties
148 pub flags: u32,
149 /// Position of invalidation bit within invalidation bytes (if used)
150 pub pos_invalidation_bit: u32,
151 /// Conversion formula to transform raw values to physical units.
152 /// If `None`, raw values are used directly.
153 pub conversion: Option<ConversionBlock>,
154 /// For VLSD channels: file address of signal data blocks
155 pub vlsd_data_address: Option<u64>,
156}
157
158/// Metadata and layout for a channel group (measurement data collection).
159///
160/// A channel group represents a collection of channels that share the same
161/// time base and record structure. All channels in a group have synchronized
162/// samples stored together in fixed-size records.
163///
164/// # Record Structure
165///
166/// Each record has the following layout:
167/// ```text
168/// [Record ID (0-8 bytes)] [Channel Data (record_size bytes)] [Invalidation (invalidation_bytes bytes)]
169/// ```
170///
171/// The total record size is: `record_id_size + record_size + invalidation_bytes`
172#[derive(Debug, Clone)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174pub struct IndexedChannelGroup {
175 /// Group name (e.g., "CAN1", "EngineData", "GPS")
176 pub name: Option<String>,
177 /// Group description or comment
178 pub comment: Option<String>,
179 /// Size of record ID prefix in bytes (0, 1, 2, 4, or 8)
180 pub record_id_size: u8,
181 /// Size of channel data portion in each record (bytes)
182 pub record_size: u32,
183 /// Size of invalidation bytes at end of each record
184 pub invalidation_bytes: u32,
185 /// Total number of records (samples) in this group
186 pub record_count: u64,
187 /// Channels belonging to this group
188 pub channels: Vec<IndexedChannel>,
189 /// Data block locations containing this group's records
190 pub data_blocks: Vec<DataBlockInfo>,
191}
192
193/// Complete index of an MDF file for efficient random access.
194///
195/// The index captures all structural information needed to read channel
196/// data without parsing the entire MDF file. It can be serialized to JSON
197/// for caching across sessions.
198///
199/// # Creating an Index
200///
201/// ```no_run
202/// use mdf4_rs::MdfIndex;
203///
204/// // From file (loads entire structure into memory)
205/// let index = MdfIndex::from_file("data.mf4")?;
206///
207/// // From file with streaming (minimal memory)
208/// let index = MdfIndex::from_file_streaming("large_file.mf4")?;
209/// # Ok::<(), mdf4_rs::Error>(())
210/// ```
211///
212/// # Reading Channel Data
213///
214/// ```no_run
215/// use mdf4_rs::{MdfIndex, FileRangeReader};
216///
217/// let index = MdfIndex::from_file_streaming("data.mf4")?;
218/// let mut reader = FileRangeReader::new("data.mf4")?;
219///
220/// // By name (searches all groups)
221/// let values = index.read_channel_values_by_name("Temperature", &mut reader)?;
222///
223/// // By index (faster, no search)
224/// let values = index.read_channel_values(0, 1, &mut reader)?;
225/// # Ok::<(), mdf4_rs::Error>(())
226/// ```
227#[derive(Debug, Clone)]
228#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
229pub struct MdfIndex {
230 /// Original file size in bytes (for validation)
231 pub file_size: u64,
232 /// All channel groups in the file
233 pub channel_groups: Vec<IndexedChannelGroup>,
234}
235
236/// Trait for reading arbitrary byte ranges from a data source.
237///
238/// This trait abstracts the data source, allowing the index system to work
239/// with local files, HTTP resources, cloud storage, or any other source
240/// that supports random access.
241///
242/// # Implementing Custom Readers
243///
244/// ```ignore
245/// use mdf4_rs::index::ByteRangeReader;
246///
247/// struct HttpRangeReader {
248/// url: String,
249/// client: reqwest::blocking::Client,
250/// }
251///
252/// impl ByteRangeReader for HttpRangeReader {
253/// type Error = mdf4_rs::Error;
254///
255/// fn read_range(&mut self, offset: u64, length: u64) -> Result<Vec<u8>, Self::Error> {
256/// let end = offset + length - 1;
257/// let response = self.client
258/// .get(&self.url)
259/// .header("Range", format!("bytes={}-{}", offset, end))
260/// .send()
261/// .map_err(|e| mdf4_rs::Error::BlockSerializationError(e.to_string()))?;
262/// response.bytes()
263/// .map(|b| b.to_vec())
264/// .map_err(|e| mdf4_rs::Error::BlockSerializationError(e.to_string()))
265/// }
266/// }
267/// ```
268pub trait ByteRangeReader {
269 /// Error type returned by read operations
270 type Error;
271
272 /// Read `length` bytes starting at `offset`.
273 ///
274 /// # Arguments
275 /// * `offset` - Byte offset from the start of the data source
276 /// * `length` - Number of bytes to read
277 ///
278 /// # Returns
279 /// The requested bytes, or an error if the read fails.
280 fn read_range(
281 &mut self,
282 offset: u64,
283 length: u64,
284 ) -> core::result::Result<Vec<u8>, Self::Error>;
285}
286
287/// Simple file reader that seeks and reads for each request.
288///
289/// This reader has minimal memory overhead but may have higher I/O latency
290/// when reading many small ranges. For sequential access patterns, consider
291/// using [`BufferedRangeReader`] instead.
292///
293/// # Example
294///
295/// ```no_run
296/// use mdf4_rs::{MdfIndex, FileRangeReader};
297///
298/// let index = MdfIndex::from_file_streaming("data.mf4")?;
299/// let mut reader = FileRangeReader::new("data.mf4")?;
300/// let values = index.read_channel_values(0, 0, &mut reader)?;
301/// # Ok::<(), mdf4_rs::Error>(())
302/// ```
303pub struct FileRangeReader {
304 file: std::fs::File,
305}
306
307impl FileRangeReader {
308 /// Open a file for range reading.
309 ///
310 /// # Arguments
311 /// * `file_path` - Path to the file
312 ///
313 /// # Errors
314 /// Returns an error if the file cannot be opened.
315 pub fn new(file_path: &str) -> Result<Self> {
316 let file = std::fs::File::open(file_path).map_err(Error::IOError)?;
317 Ok(Self { file })
318 }
319}
320
321impl ByteRangeReader for FileRangeReader {
322 type Error = Error;
323
324 fn read_range(
325 &mut self,
326 offset: u64,
327 length: u64,
328 ) -> core::result::Result<Vec<u8>, Self::Error> {
329 self.file
330 .seek(SeekFrom::Start(offset))
331 .map_err(Error::IOError)?;
332
333 let mut buffer = vec![0u8; length as usize];
334 self.file.read_exact(&mut buffer).map_err(Error::IOError)?;
335
336 Ok(buffer)
337 }
338}
339
340/// Buffered file reader with read-ahead caching for better I/O performance.
341///
342/// This reader maintains an internal buffer and prefetches data to minimize
343/// system calls when reading many small ranges sequentially.
344pub struct BufferedRangeReader {
345 file: std::fs::File,
346 buffer: Vec<u8>,
347 buffer_start: u64,
348 buffer_end: u64,
349 buffer_capacity: usize,
350}
351
352impl BufferedRangeReader {
353 /// Create a new buffered reader with the default buffer size (64 KB).
354 pub fn new(file_path: &str) -> Result<Self> {
355 Self::with_capacity(file_path, 64 * 1024)
356 }
357
358 /// Create a new buffered reader with a custom buffer size.
359 pub fn with_capacity(file_path: &str, capacity: usize) -> Result<Self> {
360 let file = std::fs::File::open(file_path).map_err(Error::IOError)?;
361 Ok(Self {
362 file,
363 buffer: Vec::with_capacity(capacity),
364 buffer_start: 0,
365 buffer_end: 0,
366 buffer_capacity: capacity,
367 })
368 }
369
370 /// Fill the internal buffer starting at the given offset.
371 fn fill_buffer(&mut self, offset: u64) -> Result<()> {
372 self.file
373 .seek(SeekFrom::Start(offset))
374 .map_err(Error::IOError)?;
375
376 self.buffer.clear();
377 self.buffer.resize(self.buffer_capacity, 0);
378
379 let bytes_read = self.file.read(&mut self.buffer).map_err(Error::IOError)?;
380 self.buffer.truncate(bytes_read);
381 self.buffer_start = offset;
382 self.buffer_end = offset + bytes_read as u64;
383
384 Ok(())
385 }
386}
387
388impl ByteRangeReader for BufferedRangeReader {
389 type Error = Error;
390
391 fn read_range(
392 &mut self,
393 offset: u64,
394 length: u64,
395 ) -> core::result::Result<Vec<u8>, Self::Error> {
396 let end = offset + length;
397
398 // Check if the requested range is fully within the buffer
399 if offset >= self.buffer_start && end <= self.buffer_end {
400 let start_idx = (offset - self.buffer_start) as usize;
401 let end_idx = start_idx + length as usize;
402 return Ok(self.buffer[start_idx..end_idx].to_vec());
403 }
404
405 // If the request is larger than our buffer, read directly
406 if length as usize > self.buffer_capacity {
407 self.file
408 .seek(SeekFrom::Start(offset))
409 .map_err(Error::IOError)?;
410 let mut buffer = vec![0u8; length as usize];
411 self.file.read_exact(&mut buffer).map_err(Error::IOError)?;
412 return Ok(buffer);
413 }
414
415 // Fill buffer starting at the requested offset
416 self.fill_buffer(offset)?;
417
418 // Now read from buffer
419 if end <= self.buffer_end {
420 let start_idx = (offset - self.buffer_start) as usize;
421 let end_idx = start_idx + length as usize;
422 Ok(self.buffer[start_idx..end_idx].to_vec())
423 } else {
424 // Buffer didn't have enough data (near end of file)
425 Err(Error::TooShortBuffer {
426 actual: (self.buffer_end - offset) as usize,
427 expected: length as usize,
428 file: file!(),
429 line: line!(),
430 })
431 }
432 }
433}
434
435/// Example HTTP range reader (would be implemented in production)
436/// ```rust,ignore
437/// use mdf4_rs::index::ByteRangeReader;
438/// use mdf4_rs::error::MdfError;
439///
440/// pub struct HttpRangeReader {
441/// client: reqwest::blocking::Client,
442/// url: String,
443/// }
444///
445/// impl HttpRangeReader {
446/// pub fn new(url: String) -> Self {
447/// Self {
448/// client: reqwest::blocking::Client::new(),
449/// url,
450/// }
451/// }
452/// }
453///
454/// impl ByteRangeReader for HttpRangeReader {
455/// type Error = MdfError;
456///
457/// fn read_range(&mut self, offset: u64, length: u64) -> Result<Vec<u8>, Self::Error> {
458/// let range_header = format!("bytes={}-{}", offset, offset + length - 1);
459///
460/// let response = self.client
461/// .get(&self.url)
462/// .header("Range", range_header)
463/// .send()
464/// .map_err(|e| MdfError::BlockSerializationError(format!("HTTP error: {}", e)))?;
465///
466/// if !response.status().is_success() {
467/// return Err(MdfError::BlockSerializationError(
468/// format!("HTTP error: {}", response.status())
469/// ));
470/// }
471///
472/// let bytes = response.bytes()
473/// .map_err(|e| MdfError::BlockSerializationError(format!("Response error: {}", e)))?;
474///
475/// Ok(bytes.to_vec())
476/// }
477/// }
478/// ```
479pub struct _HttpRangeReaderExample;
480
481impl MdfIndex {
482 /// Create an index from an MDF file
483 pub fn from_file(file_path: &str) -> Result<Self> {
484 let mdf = MDF::from_file(file_path)?;
485 let file_size = std::fs::metadata(file_path).map_err(Error::IOError)?.len();
486
487 let mut indexed_groups = Vec::new();
488
489 for group in mdf.channel_groups() {
490 let mut indexed_channels = Vec::new();
491 let mmap = group.mmap(); // Get memory mapped file data for resolving conversions
492
493 // Index each channel in the group
494 for channel in group.channels() {
495 let block = channel.block();
496
497 // Clone and resolve conversion dependencies if present
498 let resolved_conversion = if let Some(mut conversion) = block.conversion.clone() {
499 // Resolve all dependencies for this conversion block
500 if let Err(e) = conversion.resolve_all_dependencies(mmap) {
501 eprintln!(
502 "Warning: Failed to resolve conversion dependencies for channel '{}': {}",
503 block.name.as_deref().unwrap_or("<unnamed>"),
504 e
505 );
506 }
507 Some(conversion)
508 } else {
509 None
510 };
511
512 let indexed_channel = IndexedChannel {
513 name: channel.name()?,
514 unit: channel.unit()?,
515 data_type: block.data_type,
516 byte_offset: block.byte_offset,
517 bit_offset: block.bit_offset,
518 bit_count: block.bit_count,
519 channel_type: block.channel_type,
520 flags: block.flags,
521 pos_invalidation_bit: block.pos_invalidation_bit,
522 conversion: resolved_conversion,
523 vlsd_data_address: if block.channel_type == 1 && block.data_addr != 0 {
524 Some(block.data_addr)
525 } else {
526 None
527 },
528 };
529 indexed_channels.push(indexed_channel);
530 }
531
532 // Get data block information
533 let data_blocks = Self::extract_data_blocks(&group)?;
534
535 let indexed_group = IndexedChannelGroup {
536 name: group.name()?,
537 comment: group.comment()?,
538 record_id_size: group.raw_data_group().block.record_id_size,
539 record_size: group.raw_channel_group().block.record_size,
540 invalidation_bytes: group.raw_channel_group().block.invalidation_size,
541 record_count: group.raw_channel_group().block.cycle_count,
542 channels: indexed_channels,
543 data_blocks,
544 };
545 indexed_groups.push(indexed_group);
546 }
547
548 Ok(MdfIndex {
549 file_size,
550 channel_groups: indexed_groups,
551 })
552 }
553
554 /// Create an index from a file using streaming reads (minimal memory usage).
555 ///
556 /// This method reads only the metadata blocks needed to build the index,
557 /// without loading the entire file into memory. Ideal for large files.
558 ///
559 /// # Arguments
560 /// * `file_path` - Path to the MDF file
561 ///
562 /// # Example
563 /// ```no_run
564 /// use mdf4_rs::MdfIndex;
565 ///
566 /// let index = MdfIndex::from_file_streaming("large_recording.mf4")?;
567 /// # Ok::<(), mdf4_rs::Error>(())
568 /// ```
569 pub fn from_file_streaming(file_path: &str) -> Result<Self> {
570 let file_size = std::fs::metadata(file_path).map_err(Error::IOError)?.len();
571 let mut reader = BufferedRangeReader::new(file_path)?;
572 Self::from_reader(&mut reader, file_size)
573 }
574
575 /// Create an index from any byte range reader.
576 ///
577 /// This is the most flexible method, allowing index creation from files,
578 /// HTTP sources, or any other data source implementing `ByteRangeReader`.
579 ///
580 /// # Arguments
581 /// * `reader` - Any implementation of `ByteRangeReader`
582 /// * `file_size` - Total size of the file in bytes
583 pub fn from_reader<R: ByteRangeReader<Error = Error>>(
584 reader: &mut R,
585 file_size: u64,
586 ) -> Result<Self> {
587 // Read and validate ID block (64 bytes at offset 0)
588 let id_bytes = reader.read_range(0, 64)?;
589 let _id_block = IdentificationBlock::from_bytes(&id_bytes)?;
590
591 // Read HD block (104 bytes at offset 64)
592 let hd_bytes = reader.read_range(64, 104)?;
593 let header = HeaderBlock::from_bytes(&hd_bytes)?;
594
595 let mut indexed_groups = Vec::new();
596
597 // Follow the DG chain
598 let mut dg_addr = header.first_dg_addr;
599 while dg_addr != 0 {
600 // Read DG block (64 bytes)
601 let dg_bytes = reader.read_range(dg_addr, 64)?;
602 let dg_block = DataGroupBlock::from_bytes(&dg_bytes)?;
603
604 // Follow the CG chain within this DG
605 let mut cg_addr = dg_block.first_cg_addr;
606 while cg_addr != 0 {
607 // Read CG block (104 bytes)
608 let cg_bytes = reader.read_range(cg_addr, 104)?;
609 let cg_block = ChannelGroupBlock::from_bytes(&cg_bytes)?;
610
611 // Read CG name if present
612 let cg_name = Self::read_text_block(reader, cg_block.acq_name_addr)?;
613 let cg_comment = Self::read_text_block(reader, cg_block.comment_addr)?;
614
615 // Follow the CN chain within this CG
616 let mut indexed_channels = Vec::new();
617 let mut cn_addr = cg_block.first_ch_addr;
618 while cn_addr != 0 {
619 // Read CN block (160 bytes)
620 let cn_bytes = reader.read_range(cn_addr, 160)?;
621 let cn_block = ChannelBlock::from_bytes(&cn_bytes)?;
622
623 // Read channel name
624 let ch_name = Self::read_text_block(reader, cn_block.name_addr)?;
625
626 // Read unit
627 let ch_unit = Self::read_text_block(reader, cn_block.unit_addr)?;
628
629 // Read and resolve conversion block if present
630 let conversion =
631 Self::read_conversion_block_streaming(reader, cn_block.conversion_addr)?;
632
633 let indexed_channel = IndexedChannel {
634 name: ch_name,
635 unit: ch_unit,
636 data_type: cn_block.data_type,
637 byte_offset: cn_block.byte_offset,
638 bit_offset: cn_block.bit_offset,
639 bit_count: cn_block.bit_count,
640 channel_type: cn_block.channel_type,
641 flags: cn_block.flags,
642 pos_invalidation_bit: cn_block.pos_invalidation_bit,
643 conversion,
644 vlsd_data_address: if cn_block.channel_type == 1 && cn_block.data_addr != 0
645 {
646 Some(cn_block.data_addr)
647 } else {
648 None
649 },
650 };
651 indexed_channels.push(indexed_channel);
652
653 cn_addr = cn_block.next_ch_addr;
654 }
655
656 // Extract data block info for this CG
657 let data_blocks =
658 Self::extract_data_blocks_streaming(reader, dg_block.data_block_addr)?;
659
660 let indexed_group = IndexedChannelGroup {
661 name: cg_name,
662 comment: cg_comment,
663 record_id_size: dg_block.record_id_size,
664 record_size: cg_block.record_size,
665 invalidation_bytes: cg_block.invalidation_size,
666 record_count: cg_block.cycle_count,
667 channels: indexed_channels,
668 data_blocks,
669 };
670 indexed_groups.push(indexed_group);
671
672 cg_addr = cg_block.next_cg_addr;
673 }
674
675 dg_addr = dg_block.next_dg_addr;
676 }
677
678 Ok(MdfIndex {
679 file_size,
680 channel_groups: indexed_groups,
681 })
682 }
683
684 /// Read a text block at the given address, returning None if address is 0.
685 fn read_text_block<R: ByteRangeReader<Error = Error>>(
686 reader: &mut R,
687 addr: u64,
688 ) -> Result<Option<String>> {
689 if addr == 0 {
690 return Ok(None);
691 }
692
693 // First read the header to get block length (24 bytes)
694 let header_bytes = reader.read_range(addr, 24)?;
695 let header = BlockHeader::from_bytes(&header_bytes)?;
696
697 // Now read the full block
698 let block_bytes = reader.read_range(addr, header.length)?;
699 let text_block = TextBlock::from_bytes(&block_bytes)?;
700
701 Ok(Some(text_block.text))
702 }
703
704 /// Read and parse a conversion block at the given address.
705 fn read_conversion_block_streaming<R: ByteRangeReader<Error = Error>>(
706 reader: &mut R,
707 addr: u64,
708 ) -> Result<Option<ConversionBlock>> {
709 if addr == 0 {
710 return Ok(None);
711 }
712
713 // First read the header to get block length
714 let header_bytes = reader.read_range(addr, 24)?;
715 let header = BlockHeader::from_bytes(&header_bytes)?;
716
717 // Read the full conversion block
718 let block_bytes = reader.read_range(addr, header.length)?;
719 let mut conv_block = ConversionBlock::from_bytes(&block_bytes)?;
720
721 // Resolve references based on conversion type
722 Self::resolve_conversion_refs(reader, &mut conv_block)?;
723
724 Ok(Some(conv_block))
725 }
726
727 /// Resolve references in a conversion block based on its type.
728 fn resolve_conversion_refs<R: ByteRangeReader<Error = Error>>(
729 reader: &mut R,
730 conv: &mut ConversionBlock,
731 ) -> Result<()> {
732 match conv.conversion_type {
733 // Algebraic conversion - first cc_ref is formula text
734 ConversionType::Algebraic => {
735 if let Some(&formula_addr) = conv.refs.first() {
736 if formula_addr != 0 {
737 conv.formula = Self::read_text_block(reader, formula_addr)?;
738 }
739 }
740 }
741 // Text-based conversions - resolve text references
742 ConversionType::ValueToText
743 | ConversionType::RangeToText
744 | ConversionType::TextToValue
745 | ConversionType::TextToText
746 | ConversionType::BitfieldText => {
747 let mut resolved = BTreeMap::new();
748 for (idx, &ref_addr) in conv.refs.iter().enumerate() {
749 if ref_addr != 0 {
750 // Check if this is a text block or nested conversion
751 let header_bytes = reader.read_range(ref_addr, 24)?;
752 let header = BlockHeader::from_bytes(&header_bytes)?;
753
754 if header.id == "##TX" || header.id == "##MD" {
755 if let Ok(Some(text)) = Self::read_text_block(reader, ref_addr) {
756 resolved.insert(idx, text);
757 }
758 }
759 // Skip nested conversions for now - they're complex
760 }
761 }
762 if !resolved.is_empty() {
763 conv.resolved_texts = Some(resolved);
764 }
765 }
766 // Linear and other numeric conversions don't need text resolution
767 _ => {}
768 }
769
770 Ok(())
771 }
772
773 /// Extract data block information using streaming reads.
774 fn extract_data_blocks_streaming<R: ByteRangeReader<Error = Error>>(
775 reader: &mut R,
776 data_addr: u64,
777 ) -> Result<Vec<DataBlockInfo>> {
778 let mut data_blocks = Vec::new();
779 let mut current_addr = data_addr;
780
781 while current_addr != 0 {
782 // Read block header (24 bytes)
783 let header_bytes = reader.read_range(current_addr, 24)?;
784 let header = BlockHeader::from_bytes(&header_bytes)?;
785
786 match header.id.as_str() {
787 "##DT" | "##DV" => {
788 data_blocks.push(DataBlockInfo {
789 file_offset: current_addr,
790 size: header.length,
791 is_compressed: false,
792 });
793 current_addr = 0;
794 }
795 "##DZ" => {
796 data_blocks.push(DataBlockInfo {
797 file_offset: current_addr,
798 size: header.length,
799 is_compressed: true,
800 });
801 current_addr = 0;
802 }
803 "##DL" => {
804 // Read the full DL block
805 let dl_bytes = reader.read_range(current_addr, header.length)?;
806 let dl_block = DataListBlock::from_bytes(&dl_bytes)?;
807
808 // Process each fragment
809 for &fragment_addr in &dl_block.data_block_addrs {
810 if fragment_addr != 0 {
811 let frag_header_bytes = reader.read_range(fragment_addr, 24)?;
812 let frag_header = BlockHeader::from_bytes(&frag_header_bytes)?;
813
814 data_blocks.push(DataBlockInfo {
815 file_offset: fragment_addr,
816 size: frag_header.length,
817 is_compressed: frag_header.id == "##DZ",
818 });
819 }
820 }
821
822 current_addr = dl_block.next_dl_addr;
823 }
824 _ => {
825 // Unknown block type, stop
826 current_addr = 0;
827 }
828 }
829 }
830
831 Ok(data_blocks)
832 }
833
834 /// Extract data block information from a channel group
835 fn extract_data_blocks(
836 group: &crate::channel_group::ChannelGroup,
837 ) -> Result<Vec<DataBlockInfo>> {
838 let mut data_blocks = Vec::new();
839 let raw_data_group = group.raw_data_group();
840 let mmap = group.mmap();
841
842 // Start at the group's primary data pointer
843 let mut current_block_address = raw_data_group.block.data_block_addr;
844 while current_block_address != 0 {
845 let byte_offset = current_block_address as usize;
846
847 // Read the block header
848 let block_header = BlockHeader::from_bytes(&mmap[byte_offset..byte_offset + 24])?;
849
850 match block_header.id.as_str() {
851 "##DT" | "##DV" => {
852 // Single contiguous DataBlock
853 let data_block_info = DataBlockInfo {
854 file_offset: current_block_address,
855 size: block_header.length,
856 is_compressed: false,
857 };
858 data_blocks.push(data_block_info);
859 // No list to follow, we're done
860 current_block_address = 0;
861 }
862 "##DZ" => {
863 // Compressed data block
864 let data_block_info = DataBlockInfo {
865 file_offset: current_block_address,
866 size: block_header.length,
867 is_compressed: true,
868 };
869 data_blocks.push(data_block_info);
870 current_block_address = 0;
871 }
872 "##DL" => {
873 // Fragmented list of data blocks
874 let data_list_block = DataListBlock::from_bytes(&mmap[byte_offset..])?;
875
876 // Parse each fragment in this list
877 for &fragment_address in &data_list_block.data_block_addrs {
878 let fragment_offset = fragment_address as usize;
879 let fragment_header =
880 BlockHeader::from_bytes(&mmap[fragment_offset..fragment_offset + 24])?;
881
882 let is_compressed = fragment_header.id == "##DZ";
883 let data_block_info = DataBlockInfo {
884 file_offset: fragment_address,
885 size: fragment_header.length,
886 is_compressed,
887 };
888 data_blocks.push(data_block_info);
889 }
890
891 // Move to the next DLBLOCK in the chain (0 = end)
892 current_block_address = data_list_block.next_dl_addr;
893 }
894
895 unexpected_id => {
896 return Err(Error::BlockIDError {
897 actual: unexpected_id.to_string(),
898 expected: "##DT / ##DV / ##DL / ##DZ".to_string(),
899 });
900 }
901 }
902 }
903
904 Ok(data_blocks)
905 }
906
907 /// Save the index to a JSON file.
908 ///
909 /// Requires the `serde` and `serde_json` features.
910 #[cfg(feature = "serde_json")]
911 pub fn save_to_file(&self, index_path: &str) -> Result<()> {
912 let json = serde_json::to_string_pretty(self).map_err(|e| {
913 Error::BlockSerializationError(format!("JSON serialization failed: {}", e))
914 })?;
915
916 std::fs::write(index_path, json).map_err(Error::IOError)?;
917
918 Ok(())
919 }
920
921 /// Load an index from a JSON file.
922 ///
923 /// Requires the `serde` and `serde_json` features.
924 #[cfg(feature = "serde_json")]
925 pub fn load_from_file(index_path: &str) -> Result<Self> {
926 let json = std::fs::read_to_string(index_path).map_err(Error::IOError)?;
927
928 let index: MdfIndex = serde_json::from_str(&json).map_err(|e| {
929 Error::BlockSerializationError(format!("JSON deserialization failed: {}", e))
930 })?;
931
932 Ok(index)
933 }
934
935 /// Read channel values using the index and a byte range reader
936 ///
937 /// # Returns
938 /// A vector of `Option<DecodedValue>` where:
939 /// - `Some(value)` represents a valid decoded value
940 /// - `None` represents an invalid value (invalidation bit set or decoding failed)
941 pub fn read_channel_values<R: ByteRangeReader<Error = Error>>(
942 &self,
943 group_index: usize,
944 channel_index: usize,
945 reader: &mut R,
946 ) -> Result<Vec<Option<DecodedValue>>> {
947 let group = self
948 .channel_groups
949 .get(group_index)
950 .ok_or_else(|| Error::BlockSerializationError("Invalid group index".to_string()))?;
951
952 let channel = group
953 .channels
954 .get(channel_index)
955 .ok_or_else(|| Error::BlockSerializationError("Invalid channel index".to_string()))?;
956
957 // Handle VLSD channels differently
958 if channel.channel_type == 1 && channel.vlsd_data_address.is_some() {
959 return self.read_vlsd_channel_values(group, channel, reader);
960 }
961
962 // For regular channels, read from data blocks
963 self.read_regular_channel_values(group, channel, reader)
964 }
965
966 /// Read values for a regular (non-VLSD) channel using byte range reader
967 fn read_regular_channel_values<R: ByteRangeReader<Error = Error>>(
968 &self,
969 group: &IndexedChannelGroup,
970 channel: &IndexedChannel,
971 reader: &mut R,
972 ) -> Result<Vec<Option<DecodedValue>>> {
973 // Record structure: record_id + data_bytes + invalidation_bytes
974 let record_size = group.record_id_size as usize
975 + group.record_size as usize
976 + group.invalidation_bytes as usize;
977 let mut values = Vec::new();
978
979 // Read from each data block
980 for data_block in &group.data_blocks {
981 // Handle compression if needed
982 if data_block.is_compressed {
983 // TODO: Implement decompression for DZ blocks
984 return Err(Error::BlockSerializationError(
985 "Compressed blocks not yet supported in index reader".to_string(),
986 ));
987 }
988
989 // Read the block data (skip 24-byte block header)
990 let block_data =
991 reader.read_range(data_block.file_offset + 24, data_block.size - 24)?;
992
993 // Process records in this block
994 let record_count = block_data.len() / record_size;
995 for i in 0..record_count {
996 let record_start = i * record_size;
997 let record_end = record_start + record_size;
998 let record = &block_data[record_start..record_end];
999
1000 // Create a ChannelBlock for decoding
1001 let temp_channel_block = ChannelBlock {
1002 header: BlockHeader {
1003 id: "##CN".to_string(),
1004 reserved: 0,
1005 length: 160,
1006 link_count: 8,
1007 },
1008 next_ch_addr: 0,
1009 component_addr: 0,
1010 name_addr: 0,
1011 source_addr: 0,
1012 conversion_addr: 0,
1013 data_addr: 0,
1014 unit_addr: 0,
1015 comment_addr: 0,
1016 channel_type: channel.channel_type,
1017 sync_type: 0,
1018 data_type: channel.data_type,
1019 bit_offset: channel.bit_offset,
1020 byte_offset: channel.byte_offset,
1021 bit_count: channel.bit_count,
1022 flags: channel.flags,
1023 pos_invalidation_bit: channel.pos_invalidation_bit,
1024 precision: 0,
1025 reserved1: 0,
1026 attachment_count: 0,
1027 min_raw_value: 0.0,
1028 max_raw_value: 0.0,
1029 lower_limit: 0.0,
1030 upper_limit: 0.0,
1031 lower_ext_limit: 0.0,
1032 upper_ext_limit: 0.0,
1033 name: channel.name.clone(),
1034 conversion: channel.conversion.clone(),
1035 };
1036
1037 // Decode with validity checking
1038 if let Some(decoded) = decode_channel_value_with_validity(
1039 record,
1040 group.record_id_size as usize,
1041 group.record_size,
1042 &temp_channel_block,
1043 ) {
1044 if decoded.is_valid {
1045 // Apply conversion if present
1046 let final_value = if let Some(conversion) = &channel.conversion {
1047 conversion.apply_decoded(decoded.value, &[])?
1048 } else {
1049 decoded.value
1050 };
1051 values.push(Some(final_value));
1052 } else {
1053 // Invalid sample
1054 values.push(None);
1055 }
1056 } else {
1057 // Decoding failed
1058 values.push(None);
1059 }
1060 }
1061 }
1062
1063 Ok(values)
1064 }
1065
1066 /// Read values for a VLSD channel
1067 fn read_vlsd_channel_values<R: ByteRangeReader<Error = Error>>(
1068 &self,
1069 _group: &IndexedChannelGroup,
1070 _channel: &IndexedChannel,
1071 _reader: &mut R,
1072 ) -> Result<Vec<Option<DecodedValue>>> {
1073 // TODO: Implement VLSD channel reading
1074 Err(Error::BlockSerializationError(
1075 "VLSD channels not yet supported in index reader".to_string(),
1076 ))
1077 }
1078
1079 /// Get channel information for a specific group and channel
1080 pub fn get_channel_info(
1081 &self,
1082 group_index: usize,
1083 channel_index: usize,
1084 ) -> Option<&IndexedChannel> {
1085 self.channel_groups
1086 .get(group_index)?
1087 .channels
1088 .get(channel_index)
1089 }
1090
1091 /// List all channel groups with their basic information
1092 pub fn list_channel_groups(&self) -> Vec<(usize, &str, usize)> {
1093 self.channel_groups
1094 .iter()
1095 .enumerate()
1096 .map(|(i, group)| {
1097 (
1098 i,
1099 group.name.as_deref().unwrap_or("<unnamed>"),
1100 group.channels.len(),
1101 )
1102 })
1103 .collect()
1104 }
1105
1106 /// List all channels in a specific group
1107 pub fn list_channels(&self, group_index: usize) -> Option<Vec<(usize, &str, &DataType)>> {
1108 let group = self.channel_groups.get(group_index)?;
1109 Some(
1110 group
1111 .channels
1112 .iter()
1113 .enumerate()
1114 .map(|(i, ch)| (i, ch.name.as_deref().unwrap_or("<unnamed>"), &ch.data_type))
1115 .collect(),
1116 )
1117 }
1118
1119 /// Get the exact byte ranges needed to read all data for a specific channel
1120 ///
1121 /// Returns a vector of (file_offset, length) tuples representing the byte ranges
1122 /// that need to be read from the file to get all data for the specified channel.
1123 ///
1124 /// # Arguments
1125 /// * `group_index` - Index of the channel group
1126 /// * `channel_index` - Index of the channel within the group
1127 ///
1128 /// # Returns
1129 /// * `Ok(Vec<(u64, u64)>)` - Vector of (offset, length) byte ranges
1130 /// * `Err(MdfError)` - If indices are invalid or channel type not supported
1131 pub fn get_channel_byte_ranges(
1132 &self,
1133 group_index: usize,
1134 channel_index: usize,
1135 ) -> Result<Vec<(u64, u64)>> {
1136 let group = self
1137 .channel_groups
1138 .get(group_index)
1139 .ok_or_else(|| Error::BlockSerializationError("Invalid group index".to_string()))?;
1140
1141 let channel = group
1142 .channels
1143 .get(channel_index)
1144 .ok_or_else(|| Error::BlockSerializationError("Invalid channel index".to_string()))?;
1145
1146 // Handle VLSD channels differently
1147 if channel.channel_type == 1 && channel.vlsd_data_address.is_some() {
1148 return Err(Error::BlockSerializationError(
1149 "VLSD channels not yet supported for byte range calculation".to_string(),
1150 ));
1151 }
1152
1153 // For regular channels, calculate byte ranges from data blocks
1154 self.calculate_regular_channel_byte_ranges(group, channel)
1155 }
1156
1157 /// Get the exact byte ranges for a specific record range of a channel
1158 ///
1159 /// This is useful when you only want to read a subset of records rather than all data.
1160 ///
1161 /// # Arguments
1162 /// * `group_index` - Index of the channel group
1163 /// * `channel_index` - Index of the channel within the group
1164 /// * `start_record` - Starting record index (0-based)
1165 /// * `record_count` - Number of records to read
1166 ///
1167 /// # Returns
1168 /// * `Ok(Vec<(u64, u64)>)` - Vector of (offset, length) byte ranges
1169 /// * `Err(MdfError)` - If indices are invalid, range is out of bounds, or channel type not supported
1170 pub fn get_channel_byte_ranges_for_records(
1171 &self,
1172 group_index: usize,
1173 channel_index: usize,
1174 start_record: u64,
1175 record_count: u64,
1176 ) -> Result<Vec<(u64, u64)>> {
1177 let group = self
1178 .channel_groups
1179 .get(group_index)
1180 .ok_or_else(|| Error::BlockSerializationError("Invalid group index".to_string()))?;
1181
1182 let channel = group
1183 .channels
1184 .get(channel_index)
1185 .ok_or_else(|| Error::BlockSerializationError("Invalid channel index".to_string()))?;
1186
1187 // Validate record range
1188 if start_record + record_count > group.record_count {
1189 return Err(Error::BlockSerializationError(format!(
1190 "Record range {}-{} exceeds total records {}",
1191 start_record,
1192 start_record + record_count - 1,
1193 group.record_count
1194 )));
1195 }
1196
1197 // Handle VLSD channels differently
1198 if channel.channel_type == 1 && channel.vlsd_data_address.is_some() {
1199 return Err(Error::BlockSerializationError(
1200 "VLSD channels not yet supported for byte range calculation".to_string(),
1201 ));
1202 }
1203
1204 self.calculate_channel_byte_ranges_for_records(group, channel, start_record, record_count)
1205 }
1206
1207 /// Calculate byte ranges for a regular (non-VLSD) channel for all records
1208 fn calculate_regular_channel_byte_ranges(
1209 &self,
1210 group: &IndexedChannelGroup,
1211 channel: &IndexedChannel,
1212 ) -> Result<Vec<(u64, u64)>> {
1213 self.calculate_channel_byte_ranges_for_records(group, channel, 0, group.record_count)
1214 }
1215
1216 /// Calculate byte ranges for a regular channel for a specific record range
1217 fn calculate_channel_byte_ranges_for_records(
1218 &self,
1219 group: &IndexedChannelGroup,
1220 channel: &IndexedChannel,
1221 start_record: u64,
1222 record_count: u64,
1223 ) -> Result<Vec<(u64, u64)>> {
1224 // Record structure: record_id + data_bytes + invalidation_bytes
1225 let record_size = group.record_id_size as usize
1226 + group.record_size as usize
1227 + group.invalidation_bytes as usize;
1228 let channel_offset_in_record = group.record_id_size as usize + channel.byte_offset as usize;
1229
1230 // Calculate how many bytes this channel needs per record
1231 let channel_bytes_per_record = if matches!(
1232 channel.data_type,
1233 DataType::StringLatin1
1234 | DataType::StringUtf8
1235 | DataType::StringUtf16LE
1236 | DataType::StringUtf16BE
1237 | DataType::ByteArray
1238 | DataType::MimeSample
1239 | DataType::MimeStream
1240 ) {
1241 channel.bit_count as usize / 8
1242 } else {
1243 (channel.bit_offset as usize + channel.bit_count as usize)
1244 .div_ceil(8)
1245 .max(1)
1246 };
1247
1248 let mut byte_ranges = Vec::new();
1249 let mut records_processed = 0u64;
1250
1251 for data_block in &group.data_blocks {
1252 if data_block.is_compressed {
1253 return Err(Error::BlockSerializationError(
1254 "Compressed blocks not supported for byte range calculation".to_string(),
1255 ));
1256 }
1257
1258 let block_data_start = data_block.file_offset + 24; // Skip block header
1259 let block_data_size = data_block.size - 24;
1260 let records_in_block = block_data_size / record_size as u64;
1261
1262 // Determine which records from this block we need
1263 let block_start_record = records_processed;
1264 let block_end_record = records_processed + records_in_block;
1265
1266 let need_start = start_record.max(block_start_record);
1267 let need_end = (start_record + record_count).min(block_end_record);
1268
1269 if need_start < need_end {
1270 // We need some records from this block
1271 let first_record_in_block = need_start - block_start_record;
1272 let last_record_in_block = need_end - block_start_record - 1;
1273
1274 // Calculate byte range for the channel data in these records
1275 let first_channel_byte = block_data_start
1276 + first_record_in_block * record_size as u64
1277 + channel_offset_in_record as u64;
1278
1279 let last_channel_byte = block_data_start
1280 + last_record_in_block * record_size as u64
1281 + channel_offset_in_record as u64
1282 + channel_bytes_per_record as u64
1283 - 1;
1284
1285 let range_length = last_channel_byte - first_channel_byte + 1;
1286 byte_ranges.push((first_channel_byte, range_length));
1287 }
1288
1289 records_processed = block_end_record;
1290
1291 // Early exit if we've processed all needed records
1292 if records_processed >= start_record + record_count {
1293 break;
1294 }
1295 }
1296
1297 Ok(byte_ranges)
1298 }
1299
1300 /// Get a summary of byte ranges for a channel (total bytes, number of ranges)
1301 ///
1302 /// This is useful for understanding the I/O pattern before actually reading.
1303 ///
1304 /// # Returns
1305 /// * `(total_bytes, number_of_ranges)` - Total bytes to read and number of separate ranges
1306 pub fn get_channel_byte_summary(
1307 &self,
1308 group_index: usize,
1309 channel_index: usize,
1310 ) -> Result<(u64, usize)> {
1311 let ranges = self.get_channel_byte_ranges(group_index, channel_index)?;
1312 let total_bytes: u64 = ranges.iter().map(|(_, len)| len).sum();
1313 Ok((total_bytes, ranges.len()))
1314 }
1315
1316 /// Find a channel group index by name
1317 ///
1318 /// # Arguments
1319 /// * `group_name` - Name of the channel group to find
1320 ///
1321 /// # Returns
1322 /// * `Some(group_index)` if found
1323 /// * `None` if not found
1324 pub fn find_channel_group_by_name(&self, group_name: &str) -> Option<usize> {
1325 self.channel_groups
1326 .iter()
1327 .enumerate()
1328 .find(|(_, group)| group.name.as_deref() == Some(group_name))
1329 .map(|(index, _)| index)
1330 }
1331
1332 /// Find a channel index by name within a specific group
1333 ///
1334 /// # Arguments
1335 /// * `group_index` - Index of the channel group to search in
1336 /// * `channel_name` - Name of the channel to find
1337 ///
1338 /// # Returns
1339 /// * `Some(channel_index)` if found
1340 /// * `None` if group doesn't exist or channel not found
1341 pub fn find_channel_by_name(&self, group_index: usize, channel_name: &str) -> Option<usize> {
1342 let group = self.channel_groups.get(group_index)?;
1343
1344 group
1345 .channels
1346 .iter()
1347 .enumerate()
1348 .find(|(_, channel)| channel.name.as_deref() == Some(channel_name))
1349 .map(|(index, _)| index)
1350 }
1351
1352 /// Find a channel by name across all groups
1353 ///
1354 /// # Arguments
1355 /// * `channel_name` - Name of the channel to find
1356 ///
1357 /// # Returns
1358 /// * `Some((group_index, channel_index))` if found
1359 /// * `None` if not found
1360 pub fn find_channel_by_name_global(&self, channel_name: &str) -> Option<(usize, usize)> {
1361 for (group_index, group) in self.channel_groups.iter().enumerate() {
1362 for (channel_index, channel) in group.channels.iter().enumerate() {
1363 if channel.name.as_deref() == Some(channel_name) {
1364 return Some((group_index, channel_index));
1365 }
1366 }
1367 }
1368 None
1369 }
1370
1371 /// Find all channels with a given name across all groups
1372 ///
1373 /// This is useful when the same channel name appears in multiple groups.
1374 ///
1375 /// # Arguments
1376 /// * `channel_name` - Name of the channels to find
1377 ///
1378 /// # Returns
1379 /// * `Vec<(group_index, channel_index)>` - All matching channels
1380 pub fn find_all_channels_by_name(&self, channel_name: &str) -> Vec<(usize, usize)> {
1381 let mut matches = Vec::new();
1382
1383 for (group_index, group) in self.channel_groups.iter().enumerate() {
1384 for (channel_index, channel) in group.channels.iter().enumerate() {
1385 if channel.name.as_deref() == Some(channel_name) {
1386 matches.push((group_index, channel_index));
1387 }
1388 }
1389 }
1390
1391 matches
1392 }
1393
1394 /// Read channel values by name using a byte range reader
1395 ///
1396 /// Convenience method that finds the channel by name and reads its values.
1397 /// If multiple channels have the same name, uses the first one found.
1398 ///
1399 /// # Arguments
1400 /// * `channel_name` - Name of the channel to read
1401 /// * `reader` - Byte range reader implementation
1402 ///
1403 /// # Returns
1404 /// * `Ok(Vec<Option<DecodedValue>>)` - Channel values (None for invalid samples)
1405 /// * `Err(MdfError)` - If channel not found or reading fails
1406 pub fn read_channel_values_by_name<R: ByteRangeReader<Error = Error>>(
1407 &self,
1408 channel_name: &str,
1409 reader: &mut R,
1410 ) -> Result<Vec<Option<DecodedValue>>> {
1411 let (group_index, channel_index) = self
1412 .find_channel_by_name_global(channel_name)
1413 .ok_or_else(|| {
1414 Error::BlockSerializationError(format!("Channel '{}' not found", channel_name))
1415 })?;
1416
1417 self.read_channel_values(group_index, channel_index, reader)
1418 }
1419
1420 /// Get byte ranges for a channel by name
1421 ///
1422 /// # Arguments
1423 /// * `channel_name` - Name of the channel
1424 ///
1425 /// # Returns
1426 /// * `Ok(Vec<(u64, u64)>)` - Byte ranges as (offset, length) tuples
1427 /// * `Err(MdfError)` - If channel not found or calculation fails
1428 pub fn get_channel_byte_ranges_by_name(&self, channel_name: &str) -> Result<Vec<(u64, u64)>> {
1429 let (group_index, channel_index) = self
1430 .find_channel_by_name_global(channel_name)
1431 .ok_or_else(|| {
1432 Error::BlockSerializationError(format!("Channel '{}' not found", channel_name))
1433 })?;
1434
1435 self.get_channel_byte_ranges(group_index, channel_index)
1436 }
1437
1438 /// Get channel information by name
1439 ///
1440 /// # Arguments
1441 /// * `channel_name` - Name of the channel
1442 ///
1443 /// # Returns
1444 /// * `Some((group_index, channel_index, &IndexedChannel))` - Channel info if found
1445 /// * `None` - If channel not found
1446 pub fn get_channel_info_by_name(
1447 &self,
1448 channel_name: &str,
1449 ) -> Option<(usize, usize, &IndexedChannel)> {
1450 let (group_index, channel_index) = self.find_channel_by_name_global(channel_name)?;
1451 let channel = self.get_channel_info(group_index, channel_index)?;
1452 Some((group_index, channel_index, channel))
1453 }
1454}