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
// 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/.
//! The single place in this crate that decides whether a digit run read
//! from a raw IFC byte slice fits an express id.
//!
//! ISO 10303-21 writes an instance name (`#<digits>`) with no upper bound, so
//! a file MAY legally contain `#4294967297`. Nothing downstream of this crate
//! can hold one: every store that keys on an express id narrows it to `u32`.
//! Accumulating with `wrapping_mul`/`wrapping_add` does not error on an
//! oversized run, it silently maps it onto a real low-numbered id — a value
//! collision, not a missing value (issue #3395, split for the reference
//! readers in #3421).
//!
//! [`parse_express_id`] is called from both sides of that contract: the
//! definition scanner ([`crate::parser::scanner::EntityScanner`]) and every
//! `#<digits>` reference reader in [`crate::fast_parse`] and
//! [`crate::decoder`], and — now that it is `pub` — the REFERENCE readers in
//! `ifc-lite-geometry`, `ifc-lite-export` and `ifc-lite-processing` that read
//! raw STEP bytes outside this crate. A second, independently-written copy of
//! this accumulation is exactly the drift #3395 was careful to avoid, so a
//! new caller must reuse this function rather than writing its own loop.
//!
//! The bound is inclusive: `u32::MAX` is a legitimate express id and parses
//! successfully. Refusal (`None`) is the only outcome for anything past it —
//! there is no saturating variant here. A caller that saturated an oversized
//! reference to `u32::MAX` would risk binding it to a real entity that
//! legitimately holds that id, which is the same collision this function
//! exists to prevent, just relocated to the sentinel value. Contrast
//! [`crate::fast_parse::parse_indices_direct`], which deliberately
//! *saturates* an out-of-range vertex index to `u32::MAX`: that value is a
//! sentinel a downstream bounds check drops, not a key another value could
//! collide with, so saturation is safe there and is not safe here.
/// Parse `digits` — an already-validated, non-empty run of ASCII digit bytes
/// — into a `u32` express id, or `None` if the value does not fit.
///
/// Two loops rather than one: a run of at most 9 digits is at most
/// 999_999_999 and cannot overflow `u32`, so the common case (every real
/// exporter's ids) keeps the unchecked instruction sequence. Only a 10+
/// digit run — which no real exporter emits — pays for `checked_mul` /
/// `checked_add`.
///
/// Callers are expected to have already located the digit run (e.g. by
/// scanning forward while `is_ascii_digit()` holds); this function does not
/// search for one and returns `Some(0)` for an all-zero run rather than
/// treating it as absent — callers that treat id `0` as "no reference" must
/// check that themselves, the same way they did before this helper existed.