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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/*
* ANISE Toolkit
* Copyright (C) 2021-onward Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. AUTHORS.md)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Documentation: https://nyxspace.com/
*/
use super::{
DAFError, DecodingNameSnafu, NAIFDataSet, NAIFSummaryRecord, NameRecord, RCRD_LEN, daf::DAF,
};
use crate::{
DBL_SIZE,
errors::DecodingError,
naif::daf::{NAIFRecord, SummaryRecord, file_record::FileRecordError},
};
use bytes::BytesMut;
use hifitime::Epoch;
use snafu::ResultExt;
use zerocopy::IntoBytes;
impl<R: NAIFSummaryRecord> DAF<R> {
/// Sets the name record of this mutable DAF file to the one provided as a parameter.
pub fn set_name_record(&mut self, new_name_record: NameRecord) -> Result<(), DAFError> {
let rcrd_idx = self.file_record()?.fwrd_idx() * RCRD_LEN;
let size = self.bytes.len();
let rcrd_bytes = self
.bytes
.get_mut(rcrd_idx..rcrd_idx + RCRD_LEN)
.ok_or_else(|| DecodingError::InaccessibleBytes {
start: rcrd_idx,
end: rcrd_idx + RCRD_LEN,
size,
})
.context(DecodingNameSnafu { kind: R::NAME })?;
rcrd_bytes.copy_from_slice(new_name_record.as_bytes());
Ok(())
}
/// Sets the data for the n-th segment of this DAF file.
pub fn set_nth_data<'a, S: NAIFDataSet<'a>>(
&mut self,
idx: usize,
new_data: S,
new_start_epoch: Epoch,
new_end_epoch: Epoch,
) -> Result<(), DAFError> {
// NOTE: This function will be rewritten in full after https://github.com/nyx-space/anise/issues/262
let summaries = self.data_summaries(None)?;
let this_summary = summaries
.get(idx)
.ok_or(DAFError::InvalidIndex { idx, kind: R::NAME })?;
if self.file_record()?.is_empty() {
return Err(DAFError::FileRecord {
kind: R::NAME,
source: FileRecordError::EmptyRecord,
});
}
let orig_index_end = this_summary.end_index();
// The summary's start pointer is a 1-based array index, so a zero is
// malformed; guard the subtraction so a crafted summary errors out
// instead of underflowing.
let orig_index_start =
this_summary
.start_index()
.checked_sub(1)
.ok_or(DAFError::DecodingData {
kind: R::NAME,
idx,
source: DecodingError::InaccessibleBytes {
start: 0,
end: orig_index_end.saturating_mul(DBL_SIZE),
size: self.bytes.len(),
},
})?;
let orig_data_start = orig_index_start.saturating_mul(DBL_SIZE);
let orig_data_end = orig_index_end.saturating_mul(DBL_SIZE);
// The end pointer is read verbatim from the file as well, so it may sit before the start
// pointer or past the end of the data: `nth_data` rejects both because it reads through
// `bytes.get`, but the splice below would panic on either.
if orig_data_end < orig_data_start || orig_data_end > self.bytes.len() {
return Err(DAFError::DecodingData {
kind: R::NAME,
idx,
source: DecodingError::InaccessibleBytes {
start: orig_data_start,
end: orig_data_end,
size: self.bytes.len(),
},
});
}
let original_size = ((orig_data_end - orig_data_start) / DBL_SIZE) as isize;
let new_data_bytes = new_data
.to_f64_daf_vec()
.or(Err(DAFError::DataBuildError { kind: R::NAME }))?;
let new_size = new_data_bytes.len() as isize;
// Size change will be positive if the new data is _smaller_ than the previous one, and vice versa.
let size_change = original_size - new_size;
// Update the bytes
let mut new_bytes = self.bytes.to_vec();
new_bytes.splice(
orig_data_start..orig_data_end,
new_data_bytes.as_bytes().iter().cloned(),
);
let mut new_summaries: Vec<R> = summaries.to_vec();
for (sno, summary) in new_summaries.iter_mut().enumerate() {
if sno < idx {
continue;
} else if sno == idx {
// Only update the end index for the data we modified
summary.update_indexes(
orig_index_start + 1,
(orig_index_end as isize - size_change) as usize,
);
summary.update_epochs(new_start_epoch, new_end_epoch);
} else if !summary.is_empty() {
// Shift all of the indexes.
let prev_start = summary.start_index();
let prev_end = summary.end_index();
summary.update_indexes(
(prev_start as isize - size_change) as usize,
(prev_end as isize - size_change) as usize,
);
}
}
let summary_bytes: Vec<u8> = new_summaries.as_bytes().to_vec();
let rcrd_idx = (self.file_record()?.fwrd_idx() - 1) * RCRD_LEN;
// Note: we use copy_from_slice here because we have the guarantee that the summary bytes are the same length as the original version.
// The splice above may have shrunk the data, so the summary record is no longer
// necessarily within the new bytes.
let new_size = new_bytes.len();
let orig_summary_bytes =
new_bytes
.get_mut(rcrd_idx..rcrd_idx + RCRD_LEN)
.ok_or(DAFError::DecodingData {
kind: R::NAME,
idx,
source: DecodingError::InaccessibleBytes {
start: rcrd_idx,
end: rcrd_idx + RCRD_LEN,
size: new_size,
},
})?;
orig_summary_bytes[SummaryRecord::SIZE..].copy_from_slice(&summary_bytes);
self.bytes = BytesMut::from_iter(new_bytes);
Ok(())
}
/// Deletes the data for the n-th segment of this DAF file.
pub fn delete_nth_data(&mut self, idx: usize) -> Result<(), DAFError> {
// NOTE: This function will be rewritten in full after https://github.com/nyx-space/anise/issues/262
let summaries = self.data_summaries(None)?;
let this_summary = summaries
.get(idx)
.ok_or(DAFError::InvalidIndex { idx, kind: R::NAME })?;
if self.file_record()?.is_empty() {
return Err(DAFError::FileRecord {
kind: R::NAME,
source: FileRecordError::EmptyRecord,
});
}
let orig_index_end = this_summary.end_index();
// The summary's start pointer is a 1-based array index, so a zero is
// malformed; guard the subtraction so a crafted summary errors out
// instead of underflowing.
let orig_index_start =
this_summary
.start_index()
.checked_sub(1)
.ok_or(DAFError::DecodingData {
kind: R::NAME,
idx,
source: DecodingError::InaccessibleBytes {
start: 0,
end: orig_index_end.saturating_mul(DBL_SIZE),
size: self.bytes.len(),
},
})?;
let orig_data_start = orig_index_start.saturating_mul(DBL_SIZE);
let orig_data_end = orig_index_end.saturating_mul(DBL_SIZE);
// The end pointer is read verbatim from the file as well, so it may sit before the start
// pointer or past the end of the data: `nth_data` rejects both because it reads through
// `bytes.get`, but the drain below would panic on either.
if orig_data_end < orig_data_start || orig_data_end > self.bytes.len() {
return Err(DAFError::DecodingData {
kind: R::NAME,
idx,
source: DecodingError::InaccessibleBytes {
start: orig_data_start,
end: orig_data_end,
size: self.bytes.len(),
},
});
}
let original_size = ((orig_data_end - orig_data_start) / DBL_SIZE) as isize;
// Size change will be positive if the new data is _smaller_ than the previous one, and vice versa.
let size_change = original_size;
// Update the bytes
let mut new_bytes = self.bytes.to_vec();
new_bytes.drain(orig_data_start..orig_data_end);
let mut new_summaries: Vec<R> = summaries.to_vec();
for (sno, summary) in new_summaries.iter_mut().enumerate() {
if sno < idx {
continue;
} else if sno == idx {
// We've removed this data, so clear the summary.
*summary = R::default()
} else if !summary.is_empty() {
// Shift all of the indexes.
let prev_start = summary.start_index();
let prev_end = summary.end_index();
summary.update_indexes(
(prev_start as isize - size_change) as usize,
(prev_end as isize - size_change) as usize,
);
}
}
// Remove empty entries from the summary all together
let cleaned_summaries: Vec<R> = new_summaries
.iter()
.filter(|summary| !summary.is_empty())
.cloned()
.collect();
let mut summary_bytes: Vec<u8> = cleaned_summaries.as_bytes().to_vec();
// We need to pad with zeros all of the summaries we've removed.
summary_bytes.extend(vec![0x0; 1000 - summary_bytes.len()]);
let rcrd_idx = (self.file_record()?.fwrd_idx() - 1) * RCRD_LEN;
// Note: we use copy_from_slice here because we have the guarantee that the summary bytes are the same length as the original version.
// The drain above may have shrunk the data, so the summary record is no longer
// necessarily within the new bytes.
let new_size = new_bytes.len();
let orig_summary_bytes =
new_bytes
.get_mut(rcrd_idx..rcrd_idx + RCRD_LEN)
.ok_or(DAFError::DecodingData {
kind: R::NAME,
idx,
source: DecodingError::InaccessibleBytes {
start: rcrd_idx,
end: rcrd_idx + RCRD_LEN,
size: new_size,
},
})?;
orig_summary_bytes[SummaryRecord::SIZE..].copy_from_slice(&summary_bytes);
self.bytes = BytesMut::from_iter(new_bytes);
Ok(())
}
}
#[cfg(test)]
mod mut_daf_ut {
use crate::naif::daf::summary_record::SummaryRecord;
use crate::naif::daf::{DAFError, FileRecord};
use crate::naif::spk::summary::SPKSummaryRecord;
use bytes::BytesMut;
use zerocopy::IntoBytes;
/// Builds a three record SPK whose single summary has the provided data pointers.
fn craft(start_idx: i32, end_idx: i32) -> BytesMut {
let mut file_record = FileRecord::spk("TEST");
file_record.forward = 2;
file_record.nd = 2;
file_record.ni = 6;
let mut bytes = Vec::new();
bytes.extend_from_slice(file_record.as_bytes());
bytes.resize(1024, 0);
// Summary record (record 2): a single summary.
let summary_header = SummaryRecord {
next_record: 0.0,
prev_record: 0.0,
num_summaries: 1.0,
};
let summary = SPKSummaryRecord {
data_type_i: 13,
start_idx,
end_idx,
..Default::default()
};
let mut summary_record = Vec::new();
summary_record.extend_from_slice(summary_header.as_bytes());
summary_record.extend_from_slice(summary.as_bytes());
summary_record.resize(1024, 0);
bytes.extend(summary_record);
// Name record (record 3).
bytes.extend(vec![0u8; 1024]);
BytesMut::from_iter(bytes)
}
#[test]
fn malformed_data_range_delete() {
// Each of these summaries is malformed in a different way: the end pointer is before the
// start pointer, it is negative and so sign extends to a huge index on the cast to usize,
// and it is past the end of the file. All three used to panic in the drain.
for (start_idx, end_idx) in [(5, 2), (1, -3), (1, 100_000)] {
let mut daf = super::DAF::<SPKSummaryRecord>::parse(craft(start_idx, end_idx)).unwrap();
match daf.delete_nth_data(0) {
Err(DAFError::DecodingData { .. }) => {}
Ok(_) => panic!("unexpected success for summary ({start_idx}, {end_idx})"),
Err(e) => panic!("unexpected error for summary ({start_idx}, {end_idx}): {e}"),
}
}
}
#[test]
fn data_range_over_summary_record_delete() {
// This summary points at 1600 bytes of data starting at the very beginning of the file, so
// deleting it leaves fewer bytes than the offset of the summary record that then has to be
// written back, which used to panic.
let mut daf = super::DAF::<SPKSummaryRecord>::parse(craft(1, 200)).unwrap();
match daf.delete_nth_data(0) {
Err(DAFError::DecodingData { .. }) => {}
Ok(_) => panic!("unexpected success for a segment overlapping the summary record"),
Err(e) => panic!("unexpected error: {e}"),
}
}
}