1#![deny(
34 clippy::unwrap_used,
35 clippy::expect_used,
36 clippy::indexing_slicing,
37 clippy::panic,
38 clippy::unreachable
39)]
40
41use std::{
42 io::{self, Read},
43 path::Path,
44};
45
46use crate::nxasm::{Component, Kind};
47
48const MAGIC: &[u8; 8] = b"V5_CFV2\0";
50pub const HEAD: usize = 64 * 1024;
52pub const MAX_PRODUCT: u64 = 64 * 1024 * 1024;
54const MAX_STREAMS: usize = 64;
56const MAX_INSTANCES: usize = 100_000;
58
59const CONTAINER_ENTRY: [u8; 4] = [0x02, 0x00, 0x81, 0x20];
60const UUID_TAG: [u8; 8] = [0x03, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x00, 0x00];
61
62pub fn uuid_text(raw: &[u8]) -> Option<String> {
65 let w =
66 |k: usize| -> Option<u32> { Some(u32::from_be_bytes(raw.get(k..k + 4)?.try_into().ok()?)) };
67 Some(format!(
68 "{:08x}_{:x}_{:08x}_{:x}",
69 w(0)?,
70 w(4)?,
71 w(8)?,
72 w(12)?
73 ))
74}
75
76pub fn containers(data: &[u8]) -> Vec<(String, String, String)> {
78 let head = data.get(..data.len().min(HEAD)).unwrap_or_default();
79 let mut out = Vec::new();
80 let mut at = 0;
81 while let Some(found) = find(head, &CONTAINER_ENTRY, at) {
82 at = found + 1;
83 if let Some(entry) = container_at(head, found + CONTAINER_ENTRY.len() + 4) {
84 out.push(entry);
85 }
86 }
87 out
88}
89
90fn container_at(d: &[u8], at: usize) -> Option<(String, String, String)> {
91 let word = |from: usize| -> Option<(String, usize)> {
92 let rest = d.get(from..)?;
93 let len = rest.iter().take(64).position(|&b| b == 0)?;
94 let text = rest.get(..len)?;
95 if len == 0 || !text.iter().all(|b| b.is_ascii_alphanumeric() || *b == b'_') {
96 return None;
97 }
98 Some((String::from_utf8_lossy(text).into_owned(), from + len + 1))
99 };
100 let (name, at) = word(at)?;
101 let (kind, mut at) = word(at)?;
102 for _ in 0..8 {
103 if d.get(at) != Some(&0) {
104 break;
105 }
106 at += 1;
107 }
108 if d.get(at..at + UUID_TAG.len())? != UUID_TAG {
109 return None;
110 }
111 let uuid = uuid_text(d.get(at + UUID_TAG.len()..at + UUID_TAG.len() + 16)?)?;
112 Some((name, kind, uuid))
113}
114
115pub fn document(data: &[u8]) -> Option<String> {
118 if !data.starts_with(MAGIC) {
119 return None;
120 }
121 containers(data)
122 .into_iter()
123 .find(|(name, _, _)| name == "CATProdCont")
124 .map(|(_, _, uuid)| uuid)
125}
126
127pub fn document_of(path: &Path) -> io::Result<Option<String>> {
129 let mut head = Vec::with_capacity(HEAD);
130 std::fs::File::open(path)?
131 .take(HEAD as u64)
132 .read_to_end(&mut head)?;
133 Ok(document(&head))
134}
135
136pub fn kind_of(path: &Path) -> io::Result<Kind> {
138 let mut data = Vec::new();
139 std::fs::File::open(path)?
140 .take(MAX_PRODUCT)
141 .read_to_end(&mut data)?;
142 Ok(match components(&data) {
143 Some(c) if !c.is_empty() => Kind::Assembly(c),
144 _ => Kind::Other,
145 })
146}
147
148fn find(hay: &[u8], needle: &[u8], from: usize) -> Option<usize> {
149 hay.get(from..)?
150 .windows(needle.len())
151 .position(|w| w == needle)
152 .map(|p| p + from)
153}
154
155fn u32_at(d: &[u8], at: usize) -> Option<u32> {
156 Some(u32::from_le_bytes(d.get(at..at + 4)?.try_into().ok()?))
157}
158
159fn varint(d: &[u8], at: usize) -> Option<(u64, usize)> {
161 let b = *d.get(at)?;
162 match b {
163 0x80 => Some((u64::from(u32_at(d, at + 1)?), at + 5)),
164 0x81..=0xcf => Some((u64::from(b - 0x81), at + 1)),
165 0xd1..=0xe3 => Some((
166 (u64::from(b - 0xd1) << 8) + u64::from(*d.get(at + 1)?),
167 at + 2,
168 )),
169 _ => None,
170 }
171}
172
173fn strref(d: &[u8], at: usize) -> Option<(usize, usize)> {
175 match *d.get(at)? {
176 0x32 => Some(((u32_at(d, at + 1)? as usize).checked_sub(1)?, at + 5)),
177 b @ 0x34..=0xff => Some((usize::from(b - 0x34), at + 1)),
178 _ => None,
179 }
180}
181
182fn strref_bytes(index: usize) -> Vec<u8> {
184 match u8::try_from(index + 0x34) {
185 Ok(b) => vec![b],
186 Err(_) => {
187 let mut v = vec![0x32];
188 v.extend((index as u32 + 1).to_le_bytes());
189 v
190 }
191 }
192}
193
194fn number(d: &[u8], at: usize) -> Option<(f64, usize)> {
196 match *d.get(at)? {
197 0xe6 => Some((
198 f64::from_le_bytes(d.get(at + 1..at + 9)?.try_into().ok()?),
199 at + 9,
200 )),
201 0xe7 => Some((1.0, at + 1)),
202 0xe8 => Some((0.0, at + 1)),
203 0xe9 => Some((-1.0, at + 1)),
204 _ => None,
205 }
206}
207
208fn section(d: &[u8], at: usize, kind: u8) -> Option<(usize, usize)> {
210 if d.get(at..at + 2)? != [0x7c, kind] {
211 return None;
212 }
213 let end = at.checked_add(u32_at(d, at + 2)? as usize)?;
214 (end >= at + 6 && end <= d.len()).then_some((at + 6, end))
215}
216
217fn section_near(d: &[u8], at: usize, kind: u8) -> Option<(usize, usize, usize)> {
219 (at..at + 8).find_map(|p| section(d, p, kind).map(|(s, e)| (p, s, e)))
220}
221
222struct Object {
224 id: Option<u32>,
225 class: Option<usize>,
226 name: Option<usize>,
227 attributes: (usize, usize),
228}
229
230#[derive(Default)]
232struct Side {
233 link: Option<String>,
234 bags: Vec<u64>,
235}
236
237struct Stream<'a> {
239 d: &'a [u8],
240 strings: Vec<&'a [u8]>,
241 objects: (usize, usize),
242 sides: (usize, usize),
243}
244
245impl<'a> Stream<'a> {
246 fn at(d: &'a [u8], osmx: usize) -> Option<Stream<'a>> {
247 let head = find(d.get(..d.len().min(osmx + 0x200))?, &[0x7c, 0x0c], osmx)?;
248 let (_, end) = section(d, head, 0x0c)?;
249 let (_, obj_start, obj_end) = section_near(d, end, 0x04)?;
250 let (_, side_start, side_end) = section_near(d, obj_end, 0x08)?;
251 let (_, _, ext_end) = section_near(d, side_end, 0x0b)?;
252 let (_, str_start, str_end) = section_near(d, ext_end, 0x02)?;
253 let strings = strings(d, str_start, str_end)?;
254 Some(Stream {
255 d,
256 strings,
257 objects: (obj_start, obj_end),
258 sides: (side_start, side_end),
259 })
260 }
261
262 fn index(&self, text: &str) -> Option<usize> {
263 self.strings.iter().position(|s| *s == text.as_bytes())
264 }
265
266 fn text(&self, index: Option<usize>) -> Option<String> {
267 let s = self.strings.get(index?)?;
268 Some(s.iter().map(|&b| char::from(b)).collect())
269 }
270
271 fn objects(&self) -> Vec<Object> {
272 let d = self.d;
273 let (mut p, end) = self.objects;
274 let mut out = Vec::new();
275 while p + 7 < end && out.len() <= MAX_INSTANCES * 4 {
276 let Some((_, next)) = section(d, p, 0x05) else {
277 break;
278 };
279 if next <= p {
280 break;
281 }
282 if let Some(object) = self.object(p + 7) {
283 out.push(object);
284 }
285 p = next;
286 }
287 out
288 }
289
290 fn object(&self, at: usize) -> Option<Object> {
291 let d = self.d;
292 let (h, hend) = section(d, at, 0x06)?;
293 let (class, q) = strref(d, h + 2)?;
294 let (name, q) = strref(d, q)?;
295 let id = (d.get(q) == Some(&0xea))
296 .then(|| u32_at(d, q + 1))
297 .flatten();
298 let attributes = section(d, hend, 0x07)?;
299 Some(Object {
300 id,
301 class: Some(class),
302 name: Some(name),
303 attributes,
304 })
305 }
306
307 fn sides(&self, count: usize, bags: Option<usize>) -> Option<Vec<Side>> {
310 let d = self.d;
311 let (mut p, end) = self.sides;
312 let mut out = Vec::new();
313 while p < end && out.len() <= count {
314 let (s, e) = section(d, p, 0x09)?;
315 out.push(side(d, s, e, bags));
316 p = e;
317 }
318 (out.len() == count).then_some(out)
319 }
320
321 fn attribute(&self, object: &Object, name: Option<usize>) -> Option<(usize, u8)> {
324 let d = self.d;
325 let pattern = strref_bytes(name?);
326 let (a, end) = object.attributes;
327 let mut p = a;
328 while p < end {
329 let starts = p == a || d.get(p.wrapping_sub(1)) == Some(&0xfe);
330 if starts && d.get(p..p + pattern.len()) == Some(pattern.as_slice()) {
331 if let Some((_, q)) = varint(d, p + pattern.len()) {
332 if matches!(d.get(q), Some(0xe8 | 0xe9)) {
333 let kind = *d.get(q + 2)?;
334 let mut r = q + 3;
335 if d.get(r) != Some(&0x37) {
336 r += 1;
337 }
338 if d.get(r) == Some(&0x37) && r < end {
339 return Some((r + 1, kind));
340 }
341 }
342 }
343 }
344 p += 1;
345 }
346 None
347 }
348
349 fn string_attribute(&self, object: &Object, name: Option<usize>) -> Option<String> {
350 let (at, kind) = self.attribute(object, name)?;
351 if kind != 0x12 {
352 return None;
353 }
354 self.text(Some(strref(self.d, at)?.0))
355 }
356
357 fn doubles(&self, object: &Object, name: Option<usize>) -> Option<Option<Vec<f64>>> {
360 let (at, kind) = self.attribute(object, name)?;
361 let read = || -> Option<Vec<f64>> {
362 if kind != 0x07 {
363 return None;
364 }
365 let (n, mut p) = varint(self.d, at)?;
366 (_, p) = varint(self.d, p)?;
367 let mut values = Vec::new();
368 for _ in 0..n.min(64) {
369 let (v, next) = number(self.d, p)?;
370 values.push(v);
371 p = next;
372 }
373 (self.d.get(p) == Some(&0xfe)).then_some(values)
374 };
375 Some(read())
376 }
377}
378
379fn strings(d: &[u8], start: usize, end: usize) -> Option<Vec<&[u8]>> {
381 let (count, mut p) = varint(d, start)?;
382 let mut out = Vec::new();
383 while p < end {
384 let len = *d.get(p)?;
385 let (from, to) = if len == 0 {
386 let n = u32_at(d, p + 1)? as usize;
387 (p + 5, p.checked_add(5)?.checked_add(n)?)
388 } else {
389 (p + 1, p + usize::from(len))
390 };
391 out.push(d.get(from..to.min(end))?);
392 p = to;
393 }
394 (out.len() as u64 == count).then_some(out)
395}
396
397fn side(d: &[u8], s: usize, e: usize, bags: Option<usize>) -> Side {
400 let mut out = Side::default();
401 let mut q = s + 2;
402 if d.get(q) == Some(&0xe5) {
403 let Some(len) = u32_at(d, q + 1) else {
404 return out;
405 };
406 let start = q + 5;
407 let end = start.saturating_add(len as usize).min(e);
408 out.link = d.get(start..end).and_then(link);
409 q = end;
410 }
411 while q < e && d.get(q..q + 2) != Some(&[0x7c, 0x0a]) {
412 match varint(d, q) {
413 Some((_, next)) => q = next,
414 None => return out,
415 }
416 }
417 let Some((mut r, end)) = section(d, q, 0x0a) else {
418 return out;
419 };
420 while r < end && d.get(r) != Some(&0xfe) {
421 let Some((name, next)) = strref(d, r) else {
422 break;
423 };
424 let Some((count, next)) = varint(d, next) else {
425 break;
426 };
427 let Some((_, mut next)) = varint(d, next) else {
428 break;
429 };
430 let mut ids = Vec::new();
431 for _ in 0..count.min(1_000_000) {
432 let Some((id, after)) = varint(d, next) else {
433 return out;
434 };
435 ids.push(id);
436 next = if d.get(after) == Some(&0x80) {
437 after + 5
438 } else {
439 after + 1
440 };
441 if next > end {
442 return out;
443 }
444 }
445 if Some(name) == bags {
446 out.bags = ids;
447 }
448 r = next;
449 }
450 out
451}
452
453fn link(blob: &[u8]) -> Option<String> {
455 if blob.get(..2)? != [0x11, 0x03] {
456 return None;
457 }
458 uuid_text(blob.get(2..18)?)
459}
460
461fn placement(values: &[f64]) -> Option<([[f64; 3]; 3], [f64; 3])> {
464 let v: &[f64; 12] = values.try_into().ok()?;
465 if !v.iter().all(|x| x.is_finite() && x.abs() < 1e9) {
466 return None;
467 }
468 let rotation = [[v[0], v[1], v[2]], [v[3], v[4], v[5]], [v[6], v[7], v[8]]];
469 if rotation
471 .iter()
472 .any(|a| ((a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt() - 1.0).abs() > 1e-3)
473 {
474 return None;
475 }
476 Some((rotation, [v[9], v[10], v[11]]))
477}
478
479pub fn components(data: &[u8]) -> Option<Vec<Component>> {
483 if !data.starts_with(MAGIC) {
484 return None;
485 }
486 let mut at = 0;
487 let mut tried = 0;
488 let stream = loop {
489 let osmx = find(data, b"OSMX", at)?;
490 at = osmx + 4;
491 tried += 1;
492 if tried > MAX_STREAMS {
493 return None;
494 }
495 if let Some(s) = Stream::at(data, osmx) {
496 if s.index("ASMPRODUCT").is_some() && s.index("_InstanceName").is_some() {
497 break s;
498 }
499 }
500 };
501 let asm = stream.index("ASMPRODUCT");
502 let bag_class = stream.index("PRDBAGREP");
503 let instance_name = stream.index("_InstanceName");
504 let position = stream.index("_Position");
505 let objects = stream.objects();
506 let by_id: std::collections::HashMap<u64, usize> = objects
507 .iter()
508 .enumerate()
509 .filter_map(|(k, o)| Some((u64::from(o.id?), k)))
510 .collect();
511 let sides = stream.sides(objects.len(), stream.index("_BagRepsList"));
512 let mut out = Vec::new();
513 for (k, object) in objects.iter().enumerate() {
514 if object.class != asm || out.len() >= MAX_INSTANCES {
515 continue;
516 }
517 let Some(instance) = stream.string_attribute(object, instance_name) else {
518 continue; };
520 let side = sides.as_ref().and_then(|s| s.get(k));
521 let Some(document) = side.and_then(|s| s.link.clone()) else {
522 continue; };
524 let listed = side
526 .and_then(|s| s.bags.first())
527 .and_then(|id| by_id.get(id))
528 .and_then(|&k| objects.get(k));
529 let bag = listed
530 .or_else(|| objects.get(k + 1))
531 .filter(|o| o.class == bag_class);
532 let (rotation, translation) = match bag.and_then(|b| stream.doubles(b, position)) {
533 None => (
534 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
535 [0.0; 3],
536 ),
537 Some(values) => match values.as_deref().and_then(placement) {
538 Some(p) => p,
539 None => continue,
540 },
541 };
542 let part_number = stream.text(object.name).filter(|n| !n.trim().is_empty());
543 out.push(Component {
544 name: part_number.unwrap_or(instance),
545 rotation,
546 translation,
547 layer: None,
548 occurrence: None,
549 document: Some(document),
550 });
551 }
552 Some(out)
553}
554
555pub type SampleInstance<'a> = (&'a str, [u8; 16], Option<[f64; 12]>);
558
559#[doc(hidden)]
564pub fn sample(own: [u8; 16], instances: &[SampleInstance]) -> Vec<u8> {
565 let mut strings: Vec<String> = [
566 "ASMPRODUCT",
567 "PRDBAGREP",
568 "Product1",
569 "_Component",
570 "_BagRepsList",
571 "_InstanceName",
572 "_Position",
573 "",
574 ]
575 .iter()
576 .map(|s| s.to_string())
577 .collect();
578 let mut s = |text: String| -> Vec<u8> {
579 let k = match strings.iter().position(|x| *x == text) {
580 Some(k) => k,
581 None => {
582 strings.push(text);
583 strings.len() - 1
584 }
585 };
586 strref_bytes(k)
587 };
588 let small = |n: usize| -> Vec<u8> {
589 if n < 0x4f {
590 vec![0x81 + n as u8]
591 } else {
592 let mut v = vec![0x80];
593 v.extend((n as u32).to_le_bytes());
594 v
595 }
596 };
597 let sect = |kind: u8, body: &[u8]| -> Vec<u8> {
598 let mut v = vec![0x7c, kind];
599 v.extend((body.len() as u32 + 6).to_le_bytes());
600 v.extend(body);
601 v
602 };
603 let object = |class: &[u8], name: &[u8], id: u32, attributes: &[u8]| -> Vec<u8> {
604 let mut header = vec![0x00, 0x08];
605 header.extend(class);
606 header.extend(name);
607 header.push(0xea);
608 header.extend(id.to_le_bytes());
609 let mut body = vec![0x00];
610 body.extend(sect(0x06, &header));
611 body.extend(sect(0x07, attributes));
612 sect(0x05, &body)
613 };
614 let list = |name: &[u8], ids: &[u32]| -> Vec<u8> {
615 let mut v = name.to_vec();
616 v.extend(small(ids.len()));
617 v.push(0x81);
618 for id in ids {
619 v.extend(small(*id as usize));
620 v.push(0x81);
621 }
622 v
623 };
624 let (asm, bag, product, component, bags, instance_name, position, empty) = (
625 s("ASMPRODUCT".into()),
626 s("PRDBAGREP".into()),
627 s("Product1".into()),
628 s("_Component".into()),
629 s("_BagRepsList".into()),
630 s("_InstanceName".into()),
631 s("_Position".into()),
632 s(String::new()),
633 );
634 let mut objects = object(&asm, &product, 2, &[0xfe]);
636 let ids: Vec<u32> = (0..instances.len() as u32).map(|k| 10 + 2 * k).collect();
637 let mut root_lists = list(&component, &ids);
638 root_lists.push(0xfe);
639 let mut sides = sect(
640 0x09,
641 &[[0x14, 0x01, 0x82].as_slice(), §(0x0a, &root_lists)].concat(),
642 );
643 for (k, (part_number, document, at)) in instances.iter().enumerate() {
644 let id = ids.get(k).copied().unwrap_or_default();
645 let mut attributes = instance_name.clone();
646 attributes.extend([0x82, 0xe8, 0xc0, 0x12, 0x37]);
647 attributes.extend(s(format!("{part_number}.{}", k + 1)));
648 attributes.extend([0xfe, 0xfe]);
649 objects.extend(object(&asm, &s(part_number.to_string()), id, &attributes));
650 let mut placed = Vec::new();
651 if let Some(values) = at {
652 placed.extend(&position);
653 placed.extend([0x82, 0xe8, 0xf4, 0x07, 0x37, 0x8d, 0x81]);
654 for v in values {
655 placed.push(0xe6);
656 placed.extend(v.to_le_bytes());
657 }
658 placed.push(0xfe);
659 }
660 placed.push(0xfe);
661 objects.extend(object(&bag, &empty, id + 1, &placed));
662 let mut target = vec![0x11, 0x03];
663 target.extend(document);
664 target.extend([7, 0, 0, 0, 5, 0, 9]);
665 target.extend(2u32.to_le_bytes());
666 target.push(0);
667 target.extend([0x0c, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0]);
668 let mut block = vec![0x14, 0x00, 0xe5];
669 block.extend((target.len() as u32).to_le_bytes());
670 block.extend(target);
671 block.push(0x86);
672 let mut lists = list(&bags, &[id + 1]);
673 lists.push(0xfe);
674 block.extend(sect(0x0a, &lists));
675 sides.extend(sect(0x09, &block));
676 sides.extend(sect(
677 0x09,
678 &[0x14, 0x01, 0x82, 0x7c, 0x0a, 7, 0, 0, 0, 0xfe],
679 ));
680 }
681 let mut table = small(strings.len());
682 for text in &strings {
683 table.push(text.len() as u8 + 1);
684 table.extend(text.as_bytes());
685 }
686 let mut out = MAGIC.to_vec();
687 out.resize(0x800, 0);
688 out.extend(CONTAINER_ENTRY);
689 out.extend(4u32.to_le_bytes());
690 out.extend(b"CATProdCont\0CATFeatCont\0");
691 out.extend(UUID_TAG);
692 out.extend(own);
693 out.resize(0x1000, 0);
694 out.extend(b"OSMX");
695 out.extend([0xa4, 0x82, 0x00]);
696 out.extend(sect(0x0c, &[]));
697 objects.push(0x00);
698 out.extend(sect(0x04, &objects));
699 out.extend(sect(0x08, &sides));
700 out.extend(sect(0x0b, &[]));
701 out.extend(sect(0x02, &table));
702 out
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708
709 const OWN: [u8; 16] = [
710 0xbd, 0x77, 0xb6, 0xa4, 0, 0, 0x1b, 0xa4, 0x64, 0xce, 0x5d, 0x6a, 0, 0, 6, 0x24,
711 ];
712 const PART: [u8; 16] = [
713 0xbd, 0x77, 0xb6, 0xa4, 0, 0, 0x1b, 0xa4, 0x64, 0xce, 0x51, 0xd8, 0, 0, 0, 0x89,
714 ];
715
716 #[test]
717 fn uuids_read_like_catia_names_its_containers() {
718 assert_eq!(uuid_text(&OWN).unwrap(), "bd77b6a4_1ba4_64ce5d6a_624");
719 let product = sample(OWN, &[]);
720 assert_eq!(document(&product).unwrap(), "bd77b6a4_1ba4_64ce5d6a_624");
721 assert_eq!(document(b"not a CATIA file"), None);
722 }
723
724 #[test]
725 fn instances_with_their_documents_and_placements() {
726 let beam = [
728 0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, -0.3, -480.0, 718.0,
729 ];
730 let product = sample(OWN, &[("frame", PART, None), ("beam", PART, Some(beam))]);
731 let c = components(&product).unwrap();
732 assert_eq!(c.len(), 2);
733 assert_eq!(c[0].name, "frame");
734 assert_eq!(c[0].document.as_deref(), Some("bd77b6a4_1ba4_64ce51d8_89"));
735 assert_eq!(c[0].place([1.0, 2.0, 3.0]), [1.0, 2.0, 3.0]);
736 assert_eq!(c[1].name, "beam");
737 assert_eq!(c[1].place([1.0, 0.0, 0.0]), [-0.3, -481.0, 718.0]);
739 assert_eq!(c[1].place([0.0, 1.0, 0.0]), [0.7, -480.0, 718.0]);
740 }
741
742 #[test]
743 fn a_broken_placement_leaves_the_instance_out() {
744 let mut skew = [0.0; 12];
745 skew[0] = 2.0;
746 let product = sample(OWN, &[("a", PART, Some(skew)), ("b", PART, None)]);
747 let c = components(&product).unwrap();
748 assert_eq!(c.iter().map(|c| c.name.as_str()).collect::<Vec<_>>(), ["b"]);
749 }
750
751 #[test]
752 fn long_string_references_are_read() {
753 assert_eq!(strref(&strref_bytes(5), 0), Some((5, 1)));
754 assert_eq!(strref(&strref_bytes(500), 0), Some((500, 5)));
755 assert_eq!(varint(&[0xd2, 0x3a], 0), Some((314, 2)));
756 assert_eq!(varint(&[0x8d], 0), Some((12, 1)));
757 }
758
759 #[test]
760 fn damaged_products_never_panic() {
761 let beam = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 5.0, 6.0, 7.0];
762 let product = sample(OWN, &[("frame", PART, None), ("beam", PART, Some(beam))]);
763 let entry = 0x800..0x800 + 64;
766 let stream = 0x1000..product.len();
767 for at in entry.chain(stream.step_by(3)) {
768 let mut copy = product.clone();
769 copy[at] ^= 0x5a;
770 let _ = components(©);
771 let _ = document(©);
772 }
773 for cut in (0x1000..product.len()).step_by(7) {
774 let _ = components(&product[..cut]);
775 }
776 }
777}