1use serde::{Deserialize, Deserializer, Serialize};
4
5use crate::{Error, Result};
6
7#[path = "strict_rtti_decoder.rs"]
8pub(crate) mod decoder;
9pub use decoder::{decode_strict_rtti, decode_strict_rtti_from_source};
10
11const HARD_RECORDS: u64 = 4_000_000;
12const HARD_SYMBOLS: u64 = 16_000_000;
13const HARD_BASES: u64 = 8_000_000;
14const HARD_NAME_BYTES: u64 = 65_536;
15const HARD_EVIDENCE_BYTES: u64 = 1 << 31;
16const HARD_INPUT_BYTES: u64 = 1 << 34;
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct StrictRttiLimits {
22 pub max_input_bytes: u64,
24 pub max_symbols: u64,
26 pub max_records: u64,
28 pub max_bases: u64,
30 pub max_name_bytes: u64,
32 pub max_evidence_bytes: u64,
34}
35
36impl Default for StrictRttiLimits {
37 fn default() -> Self {
38 Self {
39 max_input_bytes: 1 << 30,
40 max_symbols: 4_000_000,
41 max_records: 1_000_000,
42 max_bases: 2_000_000,
43 max_name_bytes: 4_096,
44 max_evidence_bytes: 1 << 29,
45 }
46 }
47}
48
49impl StrictRttiLimits {
50 pub fn validate(self) -> Result<()> {
52 for (value, hard, name) in [
53 (self.max_input_bytes, HARD_INPUT_BYTES, "RTTI input-byte"),
54 (self.max_symbols, HARD_SYMBOLS, "RTTI symbol"),
55 (self.max_records, HARD_RECORDS, "RTTI record"),
56 (self.max_bases, HARD_BASES, "RTTI base"),
57 (self.max_name_bytes, HARD_NAME_BYTES, "RTTI name-byte"),
58 (
59 self.max_evidence_bytes,
60 HARD_EVIDENCE_BYTES,
61 "RTTI evidence-byte",
62 ),
63 ] {
64 if value == 0 || value > hard {
65 return Err(Error::format(format!(
66 "{name} limit is zero or exceeds its hard maximum"
67 )));
68 }
69 }
70 Ok(())
71 }
72}
73
74#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum StrictRttiOutcome {
78 Absent,
80 Complete,
82 Rejected,
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
88#[serde(deny_unknown_fields)]
89pub struct StrictRttiConservation {
90 pub attempted: u64,
92 pub included: u64,
94 pub unknown: u64,
96 pub excluded: u64,
98}
99
100impl StrictRttiConservation {
101 pub(crate) fn validate(self) -> Result<()> {
102 let balanced = self
103 .included
104 .checked_add(self.unknown)
105 .and_then(|value| value.checked_add(self.excluded));
106 if balanced != Some(self.attempted) {
107 return Err(Error::format("strict RTTI conservation does not balance"));
108 }
109 Ok(())
110 }
111}
112
113#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
115#[serde(rename_all = "snake_case")]
116pub enum StrictRttiGapCode {
117 StructuralLimitExceeded,
119 PointerUnresolved,
121 RecordMalformed,
123 FamilyUnsupported,
125 TypeNameInvalid,
127}
128
129#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
131#[serde(deny_unknown_fields)]
132pub struct StrictRttiGap {
133 pub symbol: Option<String>,
135 pub field: String,
137 pub code: StrictRttiGapCode,
139 pub source_code: Option<String>,
141}
142
143#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
145#[serde(rename_all = "snake_case")]
146pub enum StrictRttiObservationKind {
147 Pointer,
149 Integer,
151 TypeName,
153}
154
155#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
157#[serde(deny_unknown_fields)]
158pub struct StrictRttiObservation {
159 pub ordinal: u64,
161 pub symbol: String,
163 pub field: String,
165 pub va: u64,
167 pub file_offset: u64,
169 pub length: u64,
171 pub kind: StrictRttiObservationKind,
173}
174
175#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
177#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
178pub enum StrictPointerEncoding {
179 Direct,
181 ChainedRebase,
183 ChainedBind {
185 addend: i64,
187 weak: bool,
189 },
190 LegacyRebase,
192 LegacyBind {
194 addend: i64,
196 weak: bool,
198 lazy: bool,
200 },
201}
202
203#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
205#[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)]
206pub enum StrictPointerAuthentication {
207 NotApplicable,
209 Authenticated {
211 key: u8,
213 diversity: u16,
215 address_diversity: bool,
217 },
218}
219
220#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
222#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
223pub enum StrictPointerTarget {
224 Null,
226 Local {
228 va: u64,
230 },
231 External {
233 symbol: String,
235 library_ordinal: i32,
237 },
238}
239
240#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
242#[serde(deny_unknown_fields)]
243pub struct StrictPointerObservation {
244 pub observation_ordinal: u64,
246 pub raw_value: u64,
248 pub width: u8,
250 pub encoding: StrictPointerEncoding,
252 pub authentication: StrictPointerAuthentication,
254 pub target: StrictPointerTarget,
256}
257
258#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
260#[serde(rename_all = "snake_case")]
261pub enum ItaniumTypeInfoFamily {
262 Fundamental,
264 Array,
266 Function,
268 Enum,
270 Class,
272 SingleInheritanceClass,
274 VirtualMultipleInheritanceClass,
276 Pointer,
278 PointerToMember,
280 Qualified,
282}
283
284#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
286#[serde(deny_unknown_fields)]
287pub struct ItaniumBaseRecord {
288 pub ordinal: u64,
290 pub typeinfo: StrictPointerObservation,
292 pub offset_flags: u64,
294 pub signed_offset: i64,
296 pub is_virtual: bool,
298 pub is_public: bool,
300}
301
302#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
304#[serde(deny_unknown_fields)]
305pub struct ItaniumPointeeRecord {
306 pub flags: u32,
308 pub pointee: StrictPointerObservation,
310 pub member_of: Option<StrictPointerObservation>,
312}
313
314#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
316#[serde(deny_unknown_fields)]
317pub struct ItaniumTypeInfoRecord {
318 pub symbol: String,
320 pub va: u64,
322 pub file_offset: u64,
324 pub family: ItaniumTypeInfoFamily,
326 pub type_name: String,
328 pub type_name_non_unique: bool,
330 pub runtime_vtable: StrictPointerObservation,
332 pub type_name_pointer: StrictPointerObservation,
334 pub class_flags: u32,
336 pub bases: Vec<ItaniumBaseRecord>,
338 pub pointee: Option<ItaniumPointeeRecord>,
340 pub weak_definition: bool,
342}
343
344#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
346#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
347pub enum StrictRttiRecord {
348 TypeInfo {
350 record: Box<ItaniumTypeInfoRecord>,
352 },
353 ExternalTypeInfo {
355 symbol: String,
357 library_ordinal: i32,
359 weak: bool,
361 },
362}
363
364#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
366#[serde(deny_unknown_fields)]
367pub struct StrictRttiBatch {
368 pub outcome: StrictRttiOutcome,
370 pub records: Vec<StrictRttiRecord>,
372 pub observations: Vec<StrictRttiObservation>,
374 pub gaps: Vec<StrictRttiGap>,
376 pub conservation: StrictRttiConservation,
378}
379
380impl StrictRttiBatch {
381 fn validate(&self, limits: StrictRttiLimits) -> Result<()> {
382 limits.validate()?;
383 self.conservation.validate()?;
384 if self.observations.iter().enumerate().any(|(index, value)| {
385 value.ordinal != u64::try_from(index).unwrap_or(u64::MAX) || value.length == 0
386 }) {
387 return Err(Error::format(
388 "strict RTTI observations are not canonical and nonempty",
389 ));
390 }
391 let evidence_bytes = self.observations.iter().try_fold(0_u64, |total, value| {
392 total
393 .checked_add(value.length)
394 .ok_or_else(|| Error::format("strict RTTI evidence bytes overflow"))
395 })?;
396 if evidence_bytes > limits.max_evidence_bytes {
397 return Err(Error::format("strict RTTI evidence exceeds its limit"));
398 }
399 if u64::try_from(self.records.len()).ok() != Some(self.conservation.included) {
400 return Err(Error::format(
401 "strict RTTI included count differs from its record count",
402 ));
403 }
404 let preflight_gap = u64::from(
405 self.outcome == StrictRttiOutcome::Rejected && self.conservation.attempted == 0,
406 );
407 let expected_gaps = self
408 .conservation
409 .unknown
410 .checked_add(u64::from(self.conservation.excluded > 0))
411 .and_then(|value| value.checked_add(preflight_gap))
412 .ok_or_else(|| Error::format("strict RTTI expected gap count overflows"))?;
413 if u64::try_from(self.gaps.len()).ok() != Some(expected_gaps)
414 || self
415 .gaps
416 .iter()
417 .any(|gap| gap.field.is_empty() || gap.field.chars().any(char::is_control))
418 {
419 return Err(Error::format(
420 "strict RTTI gaps do not reconstruct unknown and excluded candidates",
421 ));
422 }
423 let mut prior_key: Option<(String, u64)> = None;
424 let mut base_count = 0_u64;
425 for record in &self.records {
426 let key = match record {
427 StrictRttiRecord::TypeInfo { record } => {
428 self.validate_typeinfo(record, limits, &mut base_count)?;
429 (record.symbol.clone(), record.va)
430 }
431 StrictRttiRecord::ExternalTypeInfo { symbol, .. } => {
432 if symbol.is_empty() || !decoder::is_typeinfo_symbol(symbol) {
433 return Err(Error::format(
434 "strict external RTTI record has an invalid symbol",
435 ));
436 }
437 (symbol.clone(), 0)
438 }
439 };
440 if prior_key.as_ref().is_some_and(|prior| prior >= &key) {
441 return Err(Error::format(
442 "strict RTTI records are not canonical and unique",
443 ));
444 }
445 prior_key = Some(key);
446 }
447 match self.outcome {
448 StrictRttiOutcome::Absent
449 if self.conservation.attempted == 0
450 && self.records.is_empty()
451 && self.gaps.is_empty() => {}
452 StrictRttiOutcome::Complete
453 if self.conservation.attempted > 0
454 && self.conservation.included == self.conservation.attempted
455 && self.gaps.is_empty() => {}
456 StrictRttiOutcome::Rejected if !self.gaps.is_empty() => {}
457 _ => {
458 return Err(Error::format(
459 "strict RTTI outcome, gaps, and conservation disagree",
460 ));
461 }
462 }
463 Ok(())
464 }
465
466 fn validate_typeinfo(
467 &self,
468 record: &ItaniumTypeInfoRecord,
469 limits: StrictRttiLimits,
470 base_count: &mut u64,
471 ) -> Result<()> {
472 if record.symbol.is_empty()
473 || !decoder::is_typeinfo_symbol(&record.symbol)
474 || record.type_name.is_empty()
475 || u64::try_from(record.type_name.len())
476 .ok()
477 .is_none_or(|length| length > limits.max_name_bytes)
478 {
479 return Err(Error::format(
480 "strict RTTI record identity or name is invalid",
481 ));
482 }
483 self.validate_pointer(&record.symbol, &record.runtime_vtable)?;
484 self.validate_pointer(&record.symbol, &record.type_name_pointer)?;
485 *base_count = base_count
486 .checked_add(
487 u64::try_from(record.bases.len())
488 .map_err(|_| Error::format("strict RTTI base count exceeds UInt64"))?,
489 )
490 .ok_or_else(|| Error::format("strict RTTI base count overflows"))?;
491 if *base_count > limits.max_bases
492 || record
493 .bases
494 .iter()
495 .enumerate()
496 .any(|(index, base)| base.ordinal != index as u64)
497 {
498 return Err(Error::format(
499 "strict RTTI bases exceed limits or are not canonical",
500 ));
501 }
502 for base in &record.bases {
503 self.validate_pointer(&record.symbol, &base.typeinfo)?;
504 if base.signed_offset != (base.offset_flags as i64) >> 8
505 || base.is_virtual != (base.offset_flags & 1 != 0)
506 || base.is_public != (base.offset_flags & 2 != 0)
507 {
508 return Err(Error::format(
509 "strict RTTI base semantics disagree with offset_flags",
510 ));
511 }
512 }
513 if let Some(pointee) = &record.pointee {
514 self.validate_pointer(&record.symbol, &pointee.pointee)?;
515 if let Some(member_of) = &pointee.member_of {
516 self.validate_pointer(&record.symbol, member_of)?;
517 }
518 }
519 let shape_matches = match record.family {
520 ItaniumTypeInfoFamily::SingleInheritanceClass => {
521 record.bases.len() == 1 && record.pointee.is_none() && record.class_flags == 0
522 }
523 ItaniumTypeInfoFamily::VirtualMultipleInheritanceClass => record.pointee.is_none(),
524 ItaniumTypeInfoFamily::Pointer => {
525 record
526 .pointee
527 .as_ref()
528 .is_some_and(|pointee| pointee.member_of.is_none())
529 && record.bases.is_empty()
530 && record.class_flags == 0
531 }
532 ItaniumTypeInfoFamily::PointerToMember => {
533 record
534 .pointee
535 .as_ref()
536 .is_some_and(|pointee| pointee.member_of.is_some())
537 && record.bases.is_empty()
538 && record.class_flags == 0
539 }
540 _ => record.bases.is_empty() && record.pointee.is_none() && record.class_flags == 0,
541 };
542 if !shape_matches {
543 return Err(Error::format(
544 "strict RTTI family disagrees with its family-specific fields",
545 ));
546 }
547 Ok(())
548 }
549
550 fn validate_pointer(&self, symbol: &str, pointer: &StrictPointerObservation) -> Result<()> {
551 let observation = usize::try_from(pointer.observation_ordinal)
552 .ok()
553 .and_then(|index| self.observations.get(index))
554 .ok_or_else(|| Error::format("strict RTTI pointer observation is absent"))?;
555 if observation.symbol != symbol
556 || observation.kind != StrictRttiObservationKind::Pointer
557 || observation.length != u64::from(pointer.width)
558 || !matches!(pointer.width, 4 | 8)
559 {
560 return Err(Error::format(
561 "strict RTTI pointer disagrees with its byte observation",
562 ));
563 }
564 Ok(())
565 }
566}
567
568#[derive(Deserialize)]
569#[serde(deny_unknown_fields)]
570struct StrictRttiBatchWire {
571 outcome: StrictRttiOutcome,
572 records: Vec<StrictRttiRecord>,
573 observations: Vec<StrictRttiObservation>,
574 gaps: Vec<StrictRttiGap>,
575 conservation: StrictRttiConservation,
576}
577
578impl<'de> Deserialize<'de> for StrictRttiBatch {
579 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
580 where
581 D: Deserializer<'de>,
582 {
583 let wire = StrictRttiBatchWire::deserialize(deserializer)?;
584 let value = Self {
585 outcome: wire.outcome,
586 records: wire.records,
587 observations: wire.observations,
588 gaps: wire.gaps,
589 conservation: wire.conservation,
590 };
591 value
592 .validate(StrictRttiLimits::default())
593 .map_err(serde::de::Error::custom)?;
594 Ok(value)
595 }
596}