1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use super::{RawChannel, RawChannelGroup, RawDataGroup};
use crate::{
Error, Result,
blocks::{BlockParse, ChannelGroupBlock, DataGroupBlock, HeaderBlock, IdentificationBlock},
};
use std::collections::HashSet;
use std::fs::File;
use std::io::Read;
#[derive(Debug, Clone)]
pub struct MdfFile {
pub identification: IdentificationBlock,
pub header: HeaderBlock,
pub data_groups: Vec<RawDataGroup>,
/// File data buffer. Stored to guarantee lifetime for slices used during parsing.
pub mmap: Vec<u8>,
/// Whether this is an unfinalized MDF file (file_id == "UnFinMF ").
pub is_unfinalized: bool,
}
impl MdfFile {
/// Parse an MDF file from a given file path.
///
/// # Arguments
/// * `path` - Path to the `.mf4` file on disk.
///
/// # Returns
/// An [`MdfFile`] containing all parsed blocks or an [`crate::Error`] if the
/// file could not be read or decoded.
pub fn parse_from_file(path: &str) -> Result<Self> {
let mut file = File::open(path)?;
let file_size = file.metadata()?.len() as usize;
// Read entire file into memory
let mut data = Vec::with_capacity(file_size);
file.read_to_end(&mut data)?;
Self::parse_from_bytes(data)
}
/// Parse an MDF file from a byte buffer.
///
/// # Arguments
/// * `data` - Complete file contents as a byte vector.
///
/// # Returns
/// An [`MdfFile`] containing all parsed blocks or an [`crate::Error`] if the
/// data could not be decoded.
pub fn parse_from_bytes(data: Vec<u8>) -> Result<Self> {
// Validate minimum file size
if data.len() < 64 + 104 {
return Err(Error::TooShortBuffer {
actual: data.len(),
expected: 64 + 104,
file: file!(),
line: line!(),
});
}
// Parse Identification block (first 64 bytes) and Header block (next 104 bytes)
let identification = IdentificationBlock::from_bytes(&data[0..64])?;
let header = HeaderBlock::from_bytes(&data[64..64 + 104])?;
// Check if file is unfinalized
let is_unfinalized = identification.file_id.trim() == "UnFinMF";
// Parse Data Groups, assume a linked list of data groups.
// Track visited link addresses to guard against malformed files whose
// "linked lists" contain cycles, which would otherwise loop forever.
let mut data_groups = Vec::new();
let mut seen_dg: HashSet<u64> = HashSet::new();
let mut dg_addr = header.first_dg_addr;
while dg_addr != 0 {
if !seen_dg.insert(dg_addr) {
return Err(Error::LinkCycle { address: dg_addr });
}
let dg_offset = dg_addr as usize;
// Bounds check
if dg_offset >= data.len() {
return Err(Error::TooShortBuffer {
actual: data.len(),
expected: dg_offset + 1,
file: file!(),
line: line!(),
});
}
let data_group_block = DataGroupBlock::from_bytes(&data[dg_offset..])?;
// Save next dg address before moving data_group_block.
let next_dg_addr = data_group_block.next_dg_addr;
let mut next_cg_addr = data_group_block.first_cg_addr;
let mut raw_channel_groups = Vec::new();
let mut seen_cg: HashSet<u64> = HashSet::new();
while next_cg_addr != 0 {
if !seen_cg.insert(next_cg_addr) {
return Err(Error::LinkCycle {
address: next_cg_addr,
});
}
// Parse channel group
let offset = next_cg_addr as usize;
// Bounds check
if offset >= data.len() {
return Err(Error::TooShortBuffer {
actual: data.len(),
expected: offset + 1,
file: file!(),
line: line!(),
});
}
let mut channel_group_block = ChannelGroupBlock::from_bytes(&data[offset..])?;
next_cg_addr = channel_group_block.next_cg_addr;
let channels = channel_group_block.read_channels(&data)?;
let raw_channels: Vec<RawChannel> = channels
.into_iter()
.map(|channel_block| RawChannel {
block: channel_block,
})
.collect();
let channel_group = RawChannelGroup {
block: channel_group_block,
raw_channels,
};
raw_channel_groups.push(channel_group);
}
let dg = RawDataGroup {
block: data_group_block,
channel_groups: raw_channel_groups,
is_unfinalized,
};
data_groups.push(dg);
dg_addr = next_dg_addr;
}
Ok(Self {
identification,
header,
data_groups,
mmap: data,
is_unfinalized,
})
}
}