1use super::*;
4
5impl Spec {
6 pub(crate) fn record_columns(
9 &self,
10 header: &HeaderValues,
11 header_size: usize,
12 stride: Option<usize>,
13 ) -> Result<(Vec<ColumnLayout>, u64), String> {
14 let mut columns = Vec::new();
15 let mut at = 0u64;
16 for (i, field) in self.records.fields.iter().enumerate() {
17 let (width, count) = sized(field, header)?;
18 if width == 0 {
19 return Err(format!(
20 "field `{}` takes no bytes",
21 field.name.as_deref().unwrap_or("pad")
22 ));
23 }
24 let start = match self.layout {
25 Layout::Rows => header_size + at as usize,
26 Layout::Columns => header_size,
27 };
28 if let Some(name) = &field.name {
29 let source = match self.layout {
30 Layout::Rows => 0,
31 Layout::Columns => i,
32 };
33 if field.flatten {
34 for j in 0..count as usize {
35 let place = Place {
36 start: start + j * width as usize,
37 stride,
38 width: width as usize,
39 count: 1,
40 };
41 let mut layout =
42 layout_of(self, field, &format!("{name}_{j}"), place, header)?;
43 layout.source = source;
44 columns.push(layout);
45 }
46 } else {
47 let place = Place {
48 start,
49 stride,
50 width: width as usize,
51 count: count as usize,
52 };
53 let mut layout = layout_of(self, field, name, place, header)?;
54 layout.source = source;
55 columns.push(layout);
56 }
57 }
58 at += width * count;
59 }
60 Ok((columns, at))
61 }
62
63 fn check_magic(&self, bytes: &[u8], named: &str) -> Result<(), String> {
65 if self.magic.is_empty() || self.magic_matches(bytes) {
66 return Ok(());
67 }
68 let start = (self.magic_offset as usize).min(bytes.len());
69 let found = &bytes[start..(start + self.magic.len()).min(bytes.len())];
70 Err(format!(
71 "{named} is not {}: expected magic {} at byte {}, found {}",
72 self.name,
73 crate::formats::fixed_records::hex(&self.magic),
74 self.magic_offset,
75 if found.is_empty() {
76 "the end of the file".to_string()
77 } else {
78 crate::formats::fixed_records::hex(found)
79 }
80 ))
81 }
82
83 pub fn open_rows(&self, bytes: Arc<Bytes>, named: &str) -> Result<Opened, String> {
85 if self.is_delimited() {
86 return Err(format!(
87 "{} is a delimited spec; it reads text through the CSV reader",
88 self.name
89 ));
90 }
91 self.open_rows_in(bytes, named, None)
92 }
93
94 pub fn for_file(&self, head: &[u8]) -> std::borrow::Cow<'_, Spec> {
97 if !self.endian_auto {
98 return std::borrow::Cow::Borrowed(self);
99 }
100 let reversed: Vec<u8> = self.magic.iter().rev().copied().collect();
101 let start = self.magic_offset as usize;
102 if reversed != self.magic && head.get(start..start + reversed.len()) == Some(&reversed[..])
103 {
104 let mut spec = self.clone();
105 spec.endian = Endian::Big;
106 spec.endian_auto = false;
107 spec.magic = reversed;
108 return std::borrow::Cow::Owned(spec);
109 }
110 std::borrow::Cow::Borrowed(self)
111 }
112
113 pub fn reads_directory(&self) -> bool {
115 self.files.is_some()
116 || (self.layout == Layout::Columns
117 && !self.records.fields.iter().any(|f| f.at.is_some()))
118 }
119
120 pub fn with_variant(&self, name: &str) -> Result<Spec, String> {
122 if !self.records.variants.iter().any(|v| v.name == name) {
123 let names: Vec<&str> = self
124 .records
125 .variants
126 .iter()
127 .map(|v| v.name.as_str())
128 .collect();
129 return Err(if names.is_empty() {
130 format!("{} has no variants", self.name)
131 } else {
132 format!(
133 "{} has no variant {name}; it has {}",
134 self.name,
135 names.join(", ")
136 )
137 });
138 }
139 let mut spec = self.clone();
140 spec.variant = Some(name.to_string());
141 Ok(spec)
142 }
143
144 pub fn open_rows_in(
148 &self,
149 bytes: Arc<Bytes>,
150 named: &str,
151 path: Option<&Path>,
152 ) -> Result<Opened, String> {
153 let dir = path.and_then(Path::parent);
154 if self.reads_directory() {
155 return Err(format!(
156 "{} reads a directory ({}); open the directory",
157 self.name,
158 if self.files.is_some() {
159 "a tree of its files"
160 } else {
161 "column files, layout = \"columns\""
162 }
163 ));
164 }
165 let spec = self.for_file(bytes.as_slice());
166 let spec = spec.as_ref();
167 if spec.layout == Layout::Columns {
168 return spec.open_columns_file(bytes, named, dir);
169 }
170 let data = bytes.as_slice();
171 let mut header = if spec.capture.is_some() {
172 if !crate::formats::framed_records::capture::is_capture(data) {
173 return Err(format!("{named} is not a pcap or pcapng capture"));
174 }
175 HeaderValues::default()
176 } else {
177 spec.check_magic(data, named)?;
178 read_header(spec, data)?
179 };
180 let mut notes = Vec::new();
181 let full = data.len() as u64;
182 if header.size > full {
183 return Err(format!(
184 "{named} is {full} bytes, shorter than its {}-byte header",
185 header.size
186 ));
187 }
188 let (len, footer_note) = read_footer(spec, data, &mut header)?;
189 notes.extend(footer_note);
190 let fields: Vec<Field> = all_fields(&spec.records).cloned().collect();
191 read_lookups(&fields, dir, &mut header)?;
192 if crate::formats::framed_records::needed(spec) {
193 let (records, more) = crate::formats::framed_records::FramedRecords::open(
194 spec,
195 bytes.clone(),
196 &header,
197 header.size as usize..len as usize,
198 named,
199 path,
200 )?;
201 notes.extend(more);
202 return Ok(Opened {
203 records: Arc::new(records),
204 notes,
205 header,
206 });
207 }
208 let data = &data[..len as usize];
209 let (_, fields_width) = spec.record_columns(&header, 0, Some(1))?;
211 let record = match &spec.records.size {
212 None => fields_width,
213 Some(amount) => {
214 let size = header.resolve(amount, "record size")?;
215 if size < fields_width {
216 return Err(format!(
217 "the record's fields take {fields_width} bytes, more than its size of {size}"
218 ));
219 }
220 size
221 }
222 };
223 if record == 0 || record > MAX_SIZE {
224 return Err(format!(
225 "a record of {record} bytes is outside 1 to {MAX_SIZE}"
226 ));
227 }
228 let room = len - header.size;
229 let whole = room / record;
230 let rows = match &spec.records.count {
231 None => {
232 let trailing = room % record;
233 if trailing > 0 {
234 notes.push(trailing_note(named, &data[(len - trailing) as usize..]));
235 }
236 whole
237 }
238 Some(amount) => {
239 let count = header.resolve(amount, "count")?;
240 if count > whole {
241 notes.push(format!(
242 "header says {count} records {} {whole} whole ones shown",
243 crate::glyphs::get().middot
244 ));
245 whole
246 } else {
247 let end = header.size + count * record;
248 if end < len {
249 let after = len - end;
250 notes.push(format!(
251 "{named} has {after} {} after its {count} records, left out",
252 if after == 1 { "byte" } else { "bytes" }
253 ));
254 }
255 count
256 }
257 }
258 };
259 let (columns, _) =
260 spec.record_columns(&header, header.size as usize, Some(record as usize))?;
261 let records =
262 FixedRecords::new(vec![bytes], columns, rows as usize).map_err(|e| e.to_string())?;
263 past_limit(&mut notes, rows, &records);
264 Ok(Opened {
265 records: Arc::new(records),
266 notes,
267 header,
268 })
269 }
270
271 pub fn open_columns(&self, dir: &Path) -> Result<Opened, String> {
273 if self.is_delimited() {
274 return Err(format!(
275 "{} is a delimited spec; it reads text through the CSV reader",
276 self.name
277 ));
278 }
279 if self.layout == Layout::Rows {
280 return Err(format!(
281 "{} reads one file, and {} is a directory",
282 self.name,
283 dir.display()
284 ));
285 }
286 let mut sources = Vec::new();
287 let mut header: Option<HeaderValues> = None;
288 let mut notes = Vec::new();
289 let mut counts: Vec<(String, u64)> = Vec::new();
290 let mut starts = Vec::new();
292 for field in &self.records.fields {
293 let file_name = field
294 .file
295 .clone()
296 .or_else(|| field.name.clone())
297 .expect("a column field is named");
298 let path = dir.join(&file_name);
299 let bytes = Bytes::map(&path).map_err(|e| format!("{}: {e}", path.display()))?;
300 self.check_magic(bytes.as_slice(), &file_name)?;
301 let read = read_header(self, bytes.as_slice())?;
302 let (width, count) = sized(field, header.as_ref().unwrap_or(&read))?;
303 let cell = width * count;
304 let len = bytes.len() as u64;
305 if read.size > len {
306 return Err(format!(
307 "{file_name} is {len} bytes, shorter than its {}-byte header",
308 read.size
309 ));
310 }
311 let room = len - read.size;
312 if cell > 0 && !room.is_multiple_of(cell) {
313 let trailing = room % cell;
314 notes.push(trailing_note(
315 &file_name,
316 &bytes.as_slice()[(len - trailing) as usize..],
317 ));
318 }
319 counts.push((file_name, room.checked_div(cell).unwrap_or(0)));
320 starts.push(read.size as usize);
321 if header.is_none() {
322 header = Some(read);
323 }
324 sources.push(Arc::new(bytes));
325 }
326 let mut header = header.unwrap_or_default();
327 read_lookups(&self.records.fields, Some(dir), &mut header)?;
328 let fewest = counts.iter().map(|(_, n)| *n).min().unwrap_or(0);
329 if counts.iter().any(|(_, n)| *n != fewest) {
330 let said: Vec<String> = counts
331 .iter()
332 .map(|(name, n)| format!("{name} {n}"))
333 .collect();
334 notes.push(format!(
335 "column files differ in length ({}) {} first {fewest} rows shown",
336 said.join(", "),
337 crate::glyphs::get().middot
338 ));
339 }
340 let mut rows = fewest;
341 if let Some(amount) = &self.records.count {
342 let count = header.resolve(amount, "count")?;
343 if count > fewest {
344 notes.push(format!(
345 "header says {count} records {} {fewest} shown",
346 crate::glyphs::get().middot
347 ));
348 }
349 rows = rows.min(count);
350 }
351 let (mut columns, _) = self.record_columns(&header, 0, None)?;
352 for column in &mut columns {
353 column.start = starts[column.source];
354 }
355 let records =
356 FixedRecords::new(sources, columns, rows as usize).map_err(|e| e.to_string())?;
357 past_limit(&mut notes, rows, &records);
358 Ok(Opened {
359 records: Arc::new(records),
360 notes,
361 header,
362 })
363 }
364
365 fn open_columns_file(
367 &self,
368 bytes: Arc<Bytes>,
369 named: &str,
370 dir: Option<&Path>,
371 ) -> Result<Opened, String> {
372 let data = bytes.as_slice();
373 self.check_magic(data, named)?;
374 let mut header = read_header(self, data)?;
375 let mut notes = Vec::new();
376 let (len, footer_note) = read_footer(self, data, &mut header)?;
377 notes.extend(footer_note);
378 read_lookups(&self.records.fields, dir, &mut header)?;
379 let (mut columns, _) = self.record_columns(&header, 0, None)?;
380 let mut rows = u64::MAX;
381 let mut starts = Vec::new();
382 for field in &self.records.fields {
383 let (width, count) = sized(field, &header)?;
384 let at = field.at.as_ref().expect("checked at parse");
385 let start = header.resolve_any(at, "offset")?;
386 if start < header.size || start > len {
387 return Err(format!(
388 "column `{}` starts at byte {start}, outside the data from {} to {len}",
389 field.name.as_deref().unwrap_or("pad"),
390 header.size
391 ));
392 }
393 rows = rows.min((len - start) / (width * count).max(1));
394 starts.push(start as usize);
395 }
396 if let Some(amount) = &self.records.count {
397 let count = header.resolve_any(amount, "count")?;
398 if count > rows {
399 notes.push(format!(
400 "header says {count} records {} room for {rows} shown",
401 crate::glyphs::get().middot
402 ));
403 }
404 rows = rows.min(count);
405 } else {
406 notes.push(format!(
407 "no count is given, so the rows are the {rows} the shortest column has room for"
408 ));
409 }
410 for column in &mut columns {
411 column.start = starts[column.source];
412 }
413 let sources = vec![bytes; self.records.fields.len()];
414 let records = FixedRecords::new(sources, columns, rows.min(usize::MAX as u64) as usize)
415 .map_err(|e| e.to_string())?;
416 Ok(Opened {
417 records: Arc::new(records),
418 notes,
419 header,
420 })
421 }
422
423 pub fn open(&self, path: &Path, named: &str) -> Result<Opened, String> {
425 if self.files.is_some() {
426 return crate::formats::files::open(self, path);
427 }
428 if self.reads_directory() {
429 return self.open_columns(path);
430 }
431 if path.is_dir() {
432 return Err(format!(
433 "{} reads one file, and {} is a directory",
434 self.name,
435 path.display()
436 ));
437 }
438 let bytes = Bytes::map(path).map_err(|e| format!("{}: {e}", path.display()))?;
439 self.open_rows_in(Arc::new(bytes), named, Some(path))
440 }
441}