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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// Low level file and block handling utilities for MdfWriter
use alloc::format;
use alloc::string::ToString;
use alloc::vec;
use super::{MdfWrite, MdfWriter};
use crate::{Error, Result};
#[cfg(feature = "std")]
use super::FileWriter;
#[cfg(feature = "std")]
impl MdfWriter<FileWriter> {
/// Creates a new MdfWriter for the given file path using a 1 MB internal
/// buffer. Use [`Self::new_with_capacity`] to customize the buffer size.
pub fn new(path: &str) -> Result<Self> {
Self::new_with_capacity(path, 1_048_576)
}
/// Creates a new MdfWriter with the specified `BufWriter` capacity.
///
/// # Arguments
/// * `path` - Path to the output file
/// * `capacity` - Buffer size in bytes (default is 1 MB)
///
/// # Example
/// ```no_run
/// use mdf4_rs::MdfWriter;
///
/// // Use a 4 MB buffer for better performance with large files
/// let writer = MdfWriter::new_with_capacity("output.mf4", 4 * 1024 * 1024)?;
/// # Ok::<(), mdf4_rs::Error>(())
/// ```
pub fn new_with_capacity(path: &str, capacity: usize) -> Result<Self> {
let file_writer = FileWriter::with_capacity(path, capacity)?;
Ok(Self::from_writer(file_writer))
}
}
impl<W: MdfWrite> MdfWriter<W> {
/// Writes a block to the file, aligning to 8 bytes and zero-padding as needed.
/// Returns the starting offset of the block in the file.
pub fn write_block(&mut self, block_bytes: &[u8]) -> Result<u64> {
let align = (8 - (self.offset % 8)) % 8;
if align != 0 {
let padding = vec![0u8; align as usize];
self.writer.write_all(&padding)?;
self.offset += align;
}
self.writer.write_all(block_bytes)?;
let block_start = self.offset;
self.offset += block_bytes.len() as u64;
Ok(block_start)
}
/// Writes a block to the file and tracks its position with the given ID.
pub fn write_block_with_id(&mut self, block_bytes: &[u8], block_id: &str) -> Result<u64> {
let block_start = self.write_block(block_bytes)?;
self.block_positions
.insert(block_id.to_string(), block_start);
Ok(block_start)
}
/// Retrieves the file position of a previously written block.
pub fn get_block_position(&self, block_id: &str) -> Option<u64> {
self.block_positions.get(block_id).copied()
}
/// Updates a link (u64 address) at a specific offset in the file.
pub fn update_link(&mut self, offset: u64, address: u64) -> Result<()> {
let current_pos = self.offset;
self.writer.seek(offset)?;
self.writer.write_all(&address.to_le_bytes())?;
self.writer.seek(current_pos)?;
Ok(())
}
/// Updates a link using block IDs instead of raw offsets.
pub fn update_block_link(
&mut self,
source_id: &str,
link_offset: u64,
target_id: &str,
) -> Result<()> {
let source_pos = self.get_block_position(source_id).ok_or_else(|| {
Error::BlockLinkError(format!("Source block '{}' not found", source_id))
})?;
let target_pos = self.get_block_position(target_id).ok_or_else(|| {
Error::BlockLinkError(format!("Target block '{}' not found", target_id))
})?;
let link_pos = source_pos + link_offset;
self.update_link(link_pos, target_pos)
}
fn update_u32(&mut self, offset: u64, value: u32) -> Result<()> {
let current_pos = self.offset;
self.writer.seek(offset)?;
self.writer.write_all(&value.to_le_bytes())?;
self.writer.seek(current_pos)?;
Ok(())
}
fn update_u64(&mut self, offset: u64, value: u64) -> Result<()> {
let current_pos = self.offset;
self.writer.seek(offset)?;
self.writer.write_all(&value.to_le_bytes())?;
self.writer.seek(current_pos)?;
Ok(())
}
fn update_u8(&mut self, offset: u64, value: u8) -> Result<()> {
let current_pos = self.offset;
self.writer.seek(offset)?;
self.writer.write_all(&[value])?;
self.writer.seek(current_pos)?;
Ok(())
}
/// Patch a little-endian u32 at `offset` inside an already-written block.
pub(super) fn update_block_u32(
&mut self,
block_id: &str,
field_offset: u64,
value: u32,
) -> Result<()> {
let block_pos = self
.get_block_position(block_id)
.ok_or_else(|| Error::BlockLinkError(format!("Block '{}' not found", block_id)))?;
self.update_u32(block_pos + field_offset, value)
}
/// Patch a single byte at `offset` inside an already-written block.
pub(super) fn update_block_u8(
&mut self,
block_id: &str,
field_offset: u64,
value: u8,
) -> Result<()> {
let block_pos = self
.get_block_position(block_id)
.ok_or_else(|| Error::BlockLinkError(format!("Block '{}' not found", block_id)))?;
self.update_u8(block_pos + field_offset, value)
}
/// Patch a little-endian u64 at `offset` inside an already-written block.
pub(super) fn update_block_u64(
&mut self,
block_id: &str,
field_offset: u64,
value: u64,
) -> Result<()> {
let block_pos = self
.get_block_position(block_id)
.ok_or_else(|| Error::BlockLinkError(format!("Block '{}' not found", block_id)))?;
self.update_u64(block_pos + field_offset, value)
}
/// Returns the current file offset (for block address calculation).
pub fn offset(&self) -> u64 {
self.offset
}
/// Flush buffered data to the underlying writer.
///
/// This method flushes all buffered record data to disk without finalizing
/// the file. It's useful for long-running captures where you want to ensure
/// data is persisted periodically.
///
/// After a flush:
/// - All buffered data is written to disk
/// - The file remains in a valid state (DT blocks have proper sizes)
/// - Writing can continue normally
///
/// Note: This does NOT create DL blocks or update final record counts.
/// Those are handled during [`finish_data_block()`](Self::finish_data_block) and finalization.
pub fn flush(&mut self) -> Result<()> {
self.writer.flush()?;
self.flush_state.on_flush();
Ok(())
}
/// Check if auto-flush should be triggered and perform it if needed.
///
/// This is called internally after each write_record when a flush policy is set.
pub(super) fn maybe_auto_flush(&mut self) -> Result<bool> {
if self.flush_state.should_flush(&self.streaming_config.policy) {
self.flush()?;
Ok(true)
} else {
Ok(false)
}
}
/// Record that data was written for streaming tracking.
pub(super) fn record_write(&mut self, records: u64, bytes: u64) {
self.flush_state.record_write(records, bytes);
}
/// Finalizes the file (flushes all data to disk).
pub fn finalize(&mut self) -> Result<()> {
self.writer.flush()?;
Ok(())
}
}