1#![deny(clippy::disallowed_methods)]
9
10use std::io::Read;
11
12use cadmpeg_ir::codec::CodecError;
13use cadmpeg_ir::decode::{ByteRange, DecodeContext, ExpandSpec, View};
14use flate2::read::ZlibDecoder;
15
16use crate::container::Container;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum StreamKind {
21 Partition,
23 Deltas,
25 Plain,
27 Preview,
29}
30
31impl StreamKind {
32 pub fn label(self) -> &'static str {
34 match self {
35 StreamKind::Partition => "partition",
36 StreamKind::Deltas => "deltas",
37 StreamKind::Plain => "plain",
38 StreamKind::Preview => "preview",
39 }
40 }
41
42 pub fn is_parasolid(self) -> bool {
44 !matches!(self, StreamKind::Preview)
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct Stream {
51 pub file_offset: usize,
53 pub consumed: u64,
57 pub inflated: Vec<u8>,
59 pub kind: StreamKind,
61 pub schema: Option<String>,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct AttributeDefinition<'a> {
68 pub offset: usize,
70 pub xmt: u16,
72 pub name: &'a str,
74 pub field_count: u32,
76 pub field_record_xmt: u16,
78 pub field_record_references: [u16; 2],
80 pub field_record_header_words: [u16; 2],
82 pub field_descriptor_prefix: [u8; 26],
84 pub field_codes: &'a [u8],
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct Entity51Record {
91 pub offset: usize,
93 pub byte_len: usize,
95 pub flags: u32,
97 pub xmt: u32,
99 pub sequence: u32,
101 pub discriminator: u16,
103 pub references: Vec<u32>,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct Entity54StringRecord<'a> {
110 pub offset: usize,
112 pub byte_len: usize,
114 pub xmt: u32,
116 pub value: &'a str,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct Entity52IntegerRecord {
123 pub offset: usize,
125 pub byte_len: usize,
127 pub xmt: u32,
129 pub values: Vec<u32>,
131}
132
133#[derive(Debug, Clone, PartialEq)]
135pub struct Entity53DoubleRecord {
136 pub offset: usize,
138 pub byte_len: usize,
140 pub xmt: u32,
142 pub values: Vec<f64>,
144}
145
146pub fn entity_52_integer_records(bytes: &[u8]) -> Vec<Entity52IntegerRecord> {
148 counted_value_records(bytes, 0x52, 4, |value| {
149 Some(u32::from_be_bytes(value.try_into().ok()?))
150 })
151 .into_iter()
152 .map(|record| Entity52IntegerRecord {
153 offset: record.offset,
154 byte_len: record.byte_len,
155 xmt: record.xmt,
156 values: record.values,
157 })
158 .collect()
159}
160
161pub fn entity_53_double_records(bytes: &[u8]) -> Vec<Entity53DoubleRecord> {
163 counted_value_records(bytes, 0x53, 8, |value| {
164 let value = f64::from_be_bytes(value.try_into().ok()?);
165 value.is_finite().then_some(value)
166 })
167 .into_iter()
168 .map(|record| Entity53DoubleRecord {
169 offset: record.offset,
170 byte_len: record.byte_len,
171 xmt: record.xmt,
172 values: record.values,
173 })
174 .collect()
175}
176
177struct CountedValueRecord<T> {
178 offset: usize,
179 byte_len: usize,
180 xmt: u32,
181 values: Vec<T>,
182}
183
184fn counted_value_records<T>(
185 bytes: &[u8],
186 tag: u8,
187 value_width: usize,
188 decode: impl Fn(&[u8]) -> Option<T>,
189) -> Vec<CountedValueRecord<T>> {
190 let mut records = Vec::new();
191 for offset in 0..bytes.len().saturating_sub(10) {
192 if bytes.get(offset..offset + 2) != Some(&[0, tag]) {
193 continue;
194 }
195 let mut at = offset + 2;
196 if bytes.get(at) == Some(&0xff) {
197 at += 1;
198 }
199 let Some(count) = bytes
200 .get(at..at + 4)
201 .map(|value| u32::from_be_bytes(value.try_into().expect("four bytes")) as usize)
202 .filter(|count| *count > 0)
203 else {
204 continue;
205 };
206 at += 4;
207 let Some(xmt) = read_xmt(bytes, &mut at).filter(|xmt| *xmt > 1) else {
208 continue;
209 };
210 let Some(values_end) = count
211 .checked_mul(value_width)
212 .and_then(|length| at.checked_add(length))
213 else {
214 continue;
215 };
216 let Some(value_bytes) = bytes.get(at..values_end) else {
217 continue;
218 };
219 let Some(values) = value_bytes
220 .chunks_exact(value_width)
221 .map(&decode)
222 .collect::<Option<Vec<_>>>()
223 else {
224 continue;
225 };
226 records.push(CountedValueRecord {
227 offset,
228 byte_len: values_end - offset,
229 xmt,
230 values,
231 });
232 }
233 records
234}
235
236pub fn entity_54_string_records(bytes: &[u8]) -> Vec<Entity54StringRecord<'_>> {
238 let mut records = Vec::new();
239 for offset in 0..bytes.len().saturating_sub(10) {
240 if bytes.get(offset..offset + 2) != Some(&[0x00, 0x54]) {
241 continue;
242 }
243 let mut at = offset + 2;
244 if bytes.get(at) == Some(&0xff) {
245 at += 1;
246 }
247 let Some(length) = bytes
248 .get(at..at + 4)
249 .map(|value| u32::from_be_bytes(value.try_into().expect("four bytes")) as usize)
250 .filter(|length| *length > 0)
251 else {
252 continue;
253 };
254 at += 4;
255 let Some(xmt) = read_xmt(bytes, &mut at).filter(|xmt| *xmt > 1) else {
256 continue;
257 };
258 let Some(end) = at.checked_add(length) else {
259 continue;
260 };
261 let Some(value) = bytes.get(at..end).filter(|value| {
262 value
263 .iter()
264 .all(|byte| byte.is_ascii_graphic() || *byte == b' ')
265 }) else {
266 continue;
267 };
268 if bytes.get(end) != Some(&0) {
269 continue;
270 }
271 let Ok(value) = std::str::from_utf8(value) else {
272 continue;
273 };
274 records.push(Entity54StringRecord {
275 offset,
276 byte_len: end + 1 - offset,
277 xmt,
278 value,
279 });
280 }
281 records
282}
283
284pub fn entity_51_records(bytes: &[u8]) -> Vec<Entity51Record> {
286 let mut records = Vec::new();
287 for offset in 0..bytes.len().saturating_sub(25) {
288 if bytes.get(offset..offset + 2) != Some(&[0x00, 0x51]) {
289 continue;
290 }
291 let mut at = offset + 2;
292 if bytes.get(at) == Some(&0xff) {
293 at += 1;
294 }
295 let Some(flags) = bytes
296 .get(at..at + 4)
297 .map(|value| u32::from_be_bytes(value.try_into().expect("four bytes")))
298 else {
299 continue;
300 };
301 at += 4;
302 let Some(xmt) = read_xmt(bytes, &mut at) else {
303 continue;
304 };
305 let Some(sequence) = bytes
306 .get(at..at + 4)
307 .map(|value| u32::from_be_bytes(value.try_into().expect("four bytes")))
308 else {
309 continue;
310 };
311 at += 4;
312 let Some(discriminator) = bytes
313 .get(at..at + 2)
314 .map(|value| u16::from_be_bytes(value.try_into().expect("two bytes")))
315 else {
316 continue;
317 };
318 at += 2;
319 let low_flag = (flags & 0xff) as u8;
320 if xmt <= 1 || sequence == 0 || !(1..=0x20).contains(&low_flag) {
321 continue;
322 }
323 let reference_count = match (discriminator, low_flag) {
324 (0x0018 | 0x0020 | 0x0025, 1) => 6,
325 (0x001d | 0x001e, 2) => 7,
326 (0x0020 | 0x0024 | 0x0027, 4) => 9,
327 _ => 6,
328 };
329 let Some(references) = entity_51_references(bytes, &mut at, reference_count) else {
330 continue;
331 };
332 records.push(Entity51Record {
333 offset,
334 byte_len: at - offset,
335 flags,
336 xmt,
337 sequence,
338 discriminator,
339 references,
340 });
341 }
342 records
343}
344
345fn entity_51_references(bytes: &[u8], at: &mut usize, count: usize) -> Option<Vec<u32>> {
346 let start = *at;
347 if bytes.get(*at) == Some(&1) {
348 let mut prefixed_at = *at;
349 let mut references = Vec::new();
350 for _ in 0..count {
351 if bytes.get(prefixed_at) != Some(&1) {
352 references.clear();
353 break;
354 }
355 prefixed_at += 1;
356 references.push(read_xmt(bytes, &mut prefixed_at)?);
357 }
358 if references.len() == count && bytes.get(prefixed_at) == Some(&0) {
359 *at = prefixed_at + 1;
360 return Some(references);
361 }
362 }
363 *at = start;
364 (0..count).map(|_| read_xmt(bytes, at)).collect()
365}
366
367fn read_xmt(bytes: &[u8], at: &mut usize) -> Option<u32> {
368 let first = i16::from_be_bytes([*bytes.get(*at)?, *bytes.get(*at + 1)?]);
369 *at += 2;
370 if first >= 0 {
371 return Some(first as u32);
372 }
373 let quotient = u16::from_be_bytes([*bytes.get(*at)?, *bytes.get(*at + 1)?]);
374 *at += 2;
375 Some(u32::from(quotient) * 32_767 + u32::from(first.unsigned_abs()))
376}
377
378pub fn attribute_definitions(bytes: &[u8]) -> Vec<AttributeDefinition<'_>> {
380 let mut definitions = Vec::new();
381 let mut at = 0;
382 while at + 9 <= bytes.len() {
383 if bytes.get(at..at + 2) != Some(&[0x00, 0x4f]) {
384 at += 1;
385 continue;
386 }
387 let escaped = bytes.get(at + 2) == Some(&0xff);
388 let header = at + 2 + usize::from(escaped);
389 let Some(length_bytes) = bytes.get(header..header + 4) else {
390 break;
391 };
392 let name_len = u32::from_be_bytes(length_bytes.try_into().expect("four bytes")) as usize;
393 let Some(identity) = bytes.get(header + 4..header + 6) else {
394 break;
395 };
396 let name_start = header + 6;
397 let Some(name_end) = name_start.checked_add(name_len) else {
398 at += 1;
399 continue;
400 };
401 let Some(name_bytes) = bytes.get(name_start..name_end) else {
402 at += 1;
403 continue;
404 };
405 let Some(field_header) = bytes.get(name_end..name_end + 16) else {
406 at += 1;
407 continue;
408 };
409 if name_len == 0
410 || !name_bytes
411 .iter()
412 .all(|byte| byte.is_ascii_graphic() && !byte.is_ascii_control())
413 || field_header.get(0..2) != Some(&[0x00, 0x50])
414 {
415 at += 1;
416 continue;
417 }
418 let Ok(name) = std::str::from_utf8(name_bytes) else {
419 at += 1;
420 continue;
421 };
422 let field_count = u32::from_be_bytes(field_header[2..6].try_into().expect("four bytes"));
423 let descriptor_start = name_end + 16;
424 let Some(descriptor_end) = descriptor_start.checked_add(26) else {
425 at += 1;
426 continue;
427 };
428 let Some(field_codes_end) = descriptor_end.checked_add(field_count as usize) else {
429 at += 1;
430 continue;
431 };
432 let Some(field_descriptor_prefix) = bytes
433 .get(descriptor_start..descriptor_end)
434 .and_then(|value| value.try_into().ok())
435 else {
436 at += 1;
437 continue;
438 };
439 let Some(field_codes) = bytes.get(descriptor_end..field_codes_end) else {
440 at += 1;
441 continue;
442 };
443 definitions.push(AttributeDefinition {
444 offset: at,
445 xmt: u16::from_be_bytes([identity[0], identity[1]]),
446 name,
447 field_count,
448 field_record_xmt: u16::from_be_bytes(field_header[6..8].try_into().expect("two bytes")),
449 field_record_references: [
450 u16::from_be_bytes(field_header[8..10].try_into().expect("two bytes")),
451 u16::from_be_bytes(field_header[10..12].try_into().expect("two bytes")),
452 ],
453 field_record_header_words: [
454 u16::from_be_bytes(field_header[12..14].try_into().expect("two bytes")),
455 u16::from_be_bytes(field_header[14..16].try_into().expect("two bytes")),
456 ],
457 field_descriptor_prefix,
458 field_codes,
459 });
460 at = field_codes_end;
461 }
462 definitions
463}
464
465const MIN_INFLATED: usize = 64;
468
469const INFLATE_CHUNK: usize = 8192;
471
472pub fn extract_streams<'a>(
474 ctx: &DecodeContext<'a>,
475 root: View<'a>,
476 container: &Container,
477) -> Result<Vec<Stream>, CodecError> {
478 let Some((part_offset, part_size)) = container
479 .entries
480 .iter()
481 .find(|entry| entry.name == "/Root/UG_PART/UG_PART")
482 .and_then(|entry| entry.file_span)
483 else {
484 return Ok(Vec::new());
485 };
486 let (Ok(start), Ok(size)) = (usize::try_from(part_offset), usize::try_from(part_size)) else {
487 return Ok(Vec::new());
488 };
489 let Some(end) = start.checked_add(size) else {
490 return Ok(Vec::new());
491 };
492 let part_view = ctx.register_slice(
493 root,
494 ByteRange {
495 start: start as u64,
496 end: end as u64,
497 },
498 )?;
499 let part = part_view.window();
500
501 let mut streams = Vec::new();
502 let mut i = 0usize;
503 while i + 2 <= part.len() {
504 if is_zlib_header(part[i], part[i + 1]) {
505 if let Some((inflated, consumed)) = inflate_stream(ctx, part_view, i)? {
506 let (kind, schema) = classify(&inflated);
507 streams.push(Stream {
508 file_offset: start + i,
509 consumed,
510 inflated,
511 kind,
512 schema,
513 });
514 i = i.saturating_add((consumed as usize).max(2));
522 continue;
523 }
524 }
525 i += 1;
526 }
527 Ok(streams)
528}
529
530fn inflate_stream<'a>(
532 ctx: &DecodeContext<'a>,
533 part_view: View<'a>,
534 offset: usize,
535) -> Result<Option<(Vec<u8>, u64)>, CodecError> {
536 let Some(source) = part_view.child(offset, part_view.end()) else {
537 return Ok(None);
538 };
539 let mut decoder = ZlibDecoder::new(source.window());
540 let mut writer = ctx.begin_expand(source, ExpandSpec::Unknown)?;
541 let mut inflated = Vec::new();
542 let mut chunk = [0u8; INFLATE_CHUNK];
543 loop {
544 match decoder.read(&mut chunk) {
545 Ok(0) => break,
546 Ok(read) => {
547 writer.write(&chunk[..read])?;
548 inflated.extend_from_slice(&chunk[..read]);
549 }
550 Err(_) => break,
551 }
552 }
553 if inflated.len() < MIN_INFLATED {
554 return Ok(None);
555 }
556 let consumed = decoder.total_in();
557 writer.finalize()?;
558 Ok(Some((inflated, consumed)))
559}
560
561#[allow(clippy::manual_is_multiple_of)] fn is_zlib_header(cmf: u8, flg: u8) -> bool {
567 cmf & 0x0f == 8 && cmf >> 4 <= 7 && u16::from_be_bytes([cmf, flg]).is_multiple_of(31)
568}
569
570fn classify(inflated: &[u8]) -> (StreamKind, Option<String>) {
572 if !inflated.starts_with(b"PS\x00\x00") {
573 return (StreamKind::Preview, None);
574 }
575 let window = &inflated[..inflated.len().min(512)];
576 let kind = if contains(window, b"(partition)") {
577 StreamKind::Partition
578 } else if contains(window, b"(deltas)") {
579 StreamKind::Deltas
580 } else {
581 StreamKind::Plain
582 };
583 (kind, read_schema(window))
584}
585
586fn read_schema(window: &[u8]) -> Option<String> {
589 let pos = find(window, b"SCH_")?;
590 let mut end = pos;
591 while end < window.len() && (window[end].is_ascii_alphanumeric() || window[end] == b'_') {
592 end += 1;
593 }
594 Some(String::from_utf8_lossy(&window[pos..end]).into_owned())
595}
596
597fn contains(haystack: &[u8], needle: &[u8]) -> bool {
598 find(haystack, needle).is_some()
599}
600
601fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
602 if needle.is_empty() || haystack.len() < needle.len() {
603 return None;
604 }
605 haystack.windows(needle.len()).position(|w| w == needle)
606}