use serde::{Deserialize, Deserializer, Serialize};
use crate::{Error, Result};
#[path = "strict_rtti_decoder.rs"]
pub(crate) mod decoder;
pub use decoder::{decode_strict_rtti, decode_strict_rtti_from_source};
const HARD_RECORDS: u64 = 4_000_000;
const HARD_SYMBOLS: u64 = 16_000_000;
const HARD_BASES: u64 = 8_000_000;
const HARD_NAME_BYTES: u64 = 65_536;
const HARD_EVIDENCE_BYTES: u64 = 1 << 31;
const HARD_INPUT_BYTES: u64 = 1 << 34;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StrictRttiLimits {
pub max_input_bytes: u64,
pub max_symbols: u64,
pub max_records: u64,
pub max_bases: u64,
pub max_name_bytes: u64,
pub max_evidence_bytes: u64,
}
impl Default for StrictRttiLimits {
fn default() -> Self {
Self {
max_input_bytes: 1 << 30,
max_symbols: 4_000_000,
max_records: 1_000_000,
max_bases: 2_000_000,
max_name_bytes: 4_096,
max_evidence_bytes: 1 << 29,
}
}
}
impl StrictRttiLimits {
pub fn validate(self) -> Result<()> {
for (value, hard, name) in [
(self.max_input_bytes, HARD_INPUT_BYTES, "RTTI input-byte"),
(self.max_symbols, HARD_SYMBOLS, "RTTI symbol"),
(self.max_records, HARD_RECORDS, "RTTI record"),
(self.max_bases, HARD_BASES, "RTTI base"),
(self.max_name_bytes, HARD_NAME_BYTES, "RTTI name-byte"),
(
self.max_evidence_bytes,
HARD_EVIDENCE_BYTES,
"RTTI evidence-byte",
),
] {
if value == 0 || value > hard {
return Err(Error::format(format!(
"{name} limit is zero or exceeds its hard maximum"
)));
}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StrictRttiOutcome {
Absent,
Complete,
Rejected,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StrictRttiConservation {
pub attempted: u64,
pub included: u64,
pub unknown: u64,
pub excluded: u64,
}
impl StrictRttiConservation {
pub(crate) fn validate(self) -> Result<()> {
let balanced = self
.included
.checked_add(self.unknown)
.and_then(|value| value.checked_add(self.excluded));
if balanced != Some(self.attempted) {
return Err(Error::format("strict RTTI conservation does not balance"));
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StrictRttiGapCode {
StructuralLimitExceeded,
PointerUnresolved,
RecordMalformed,
FamilyUnsupported,
TypeNameInvalid,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StrictRttiGap {
pub symbol: Option<String>,
pub field: String,
pub code: StrictRttiGapCode,
pub source_code: Option<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StrictRttiObservationKind {
Pointer,
Integer,
TypeName,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StrictRttiObservation {
pub ordinal: u64,
pub symbol: String,
pub field: String,
pub va: u64,
pub file_offset: u64,
pub length: u64,
pub kind: StrictRttiObservationKind,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum StrictPointerEncoding {
Direct,
ChainedRebase,
ChainedBind {
addend: i64,
weak: bool,
},
LegacyRebase,
LegacyBind {
addend: i64,
weak: bool,
lazy: bool,
},
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)]
pub enum StrictPointerAuthentication {
NotApplicable,
Authenticated {
key: u8,
diversity: u16,
address_diversity: bool,
},
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum StrictPointerTarget {
Null,
Local {
va: u64,
},
External {
symbol: String,
library_ordinal: i32,
},
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StrictPointerObservation {
pub observation_ordinal: u64,
pub raw_value: u64,
pub width: u8,
pub encoding: StrictPointerEncoding,
pub authentication: StrictPointerAuthentication,
pub target: StrictPointerTarget,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ItaniumTypeInfoFamily {
Fundamental,
Array,
Function,
Enum,
Class,
SingleInheritanceClass,
VirtualMultipleInheritanceClass,
Pointer,
PointerToMember,
Qualified,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ItaniumBaseRecord {
pub ordinal: u64,
pub typeinfo: StrictPointerObservation,
pub offset_flags: u64,
pub signed_offset: i64,
pub is_virtual: bool,
pub is_public: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ItaniumPointeeRecord {
pub flags: u32,
pub pointee: StrictPointerObservation,
pub member_of: Option<StrictPointerObservation>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ItaniumTypeInfoRecord {
pub symbol: String,
pub va: u64,
pub file_offset: u64,
pub family: ItaniumTypeInfoFamily,
pub type_name: String,
pub type_name_non_unique: bool,
pub runtime_vtable: StrictPointerObservation,
pub type_name_pointer: StrictPointerObservation,
pub class_flags: u32,
pub bases: Vec<ItaniumBaseRecord>,
pub pointee: Option<ItaniumPointeeRecord>,
pub weak_definition: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum StrictRttiRecord {
TypeInfo {
record: Box<ItaniumTypeInfoRecord>,
},
ExternalTypeInfo {
symbol: String,
library_ordinal: i32,
weak: bool,
},
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct StrictRttiBatch {
pub outcome: StrictRttiOutcome,
pub records: Vec<StrictRttiRecord>,
pub observations: Vec<StrictRttiObservation>,
pub gaps: Vec<StrictRttiGap>,
pub conservation: StrictRttiConservation,
}
impl StrictRttiBatch {
fn validate(&self, limits: StrictRttiLimits) -> Result<()> {
limits.validate()?;
self.conservation.validate()?;
if self.observations.iter().enumerate().any(|(index, value)| {
value.ordinal != u64::try_from(index).unwrap_or(u64::MAX) || value.length == 0
}) {
return Err(Error::format(
"strict RTTI observations are not canonical and nonempty",
));
}
let evidence_bytes = self.observations.iter().try_fold(0_u64, |total, value| {
total
.checked_add(value.length)
.ok_or_else(|| Error::format("strict RTTI evidence bytes overflow"))
})?;
if evidence_bytes > limits.max_evidence_bytes {
return Err(Error::format("strict RTTI evidence exceeds its limit"));
}
if u64::try_from(self.records.len()).ok() != Some(self.conservation.included) {
return Err(Error::format(
"strict RTTI included count differs from its record count",
));
}
let preflight_gap = u64::from(
self.outcome == StrictRttiOutcome::Rejected && self.conservation.attempted == 0,
);
let expected_gaps = self
.conservation
.unknown
.checked_add(u64::from(self.conservation.excluded > 0))
.and_then(|value| value.checked_add(preflight_gap))
.ok_or_else(|| Error::format("strict RTTI expected gap count overflows"))?;
if u64::try_from(self.gaps.len()).ok() != Some(expected_gaps)
|| self
.gaps
.iter()
.any(|gap| gap.field.is_empty() || gap.field.chars().any(char::is_control))
{
return Err(Error::format(
"strict RTTI gaps do not reconstruct unknown and excluded candidates",
));
}
let mut prior_key: Option<(String, u64)> = None;
let mut base_count = 0_u64;
for record in &self.records {
let key = match record {
StrictRttiRecord::TypeInfo { record } => {
self.validate_typeinfo(record, limits, &mut base_count)?;
(record.symbol.clone(), record.va)
}
StrictRttiRecord::ExternalTypeInfo { symbol, .. } => {
if symbol.is_empty() || !decoder::is_typeinfo_symbol(symbol) {
return Err(Error::format(
"strict external RTTI record has an invalid symbol",
));
}
(symbol.clone(), 0)
}
};
if prior_key.as_ref().is_some_and(|prior| prior >= &key) {
return Err(Error::format(
"strict RTTI records are not canonical and unique",
));
}
prior_key = Some(key);
}
match self.outcome {
StrictRttiOutcome::Absent
if self.conservation.attempted == 0
&& self.records.is_empty()
&& self.gaps.is_empty() => {}
StrictRttiOutcome::Complete
if self.conservation.attempted > 0
&& self.conservation.included == self.conservation.attempted
&& self.gaps.is_empty() => {}
StrictRttiOutcome::Rejected if !self.gaps.is_empty() => {}
_ => {
return Err(Error::format(
"strict RTTI outcome, gaps, and conservation disagree",
));
}
}
Ok(())
}
fn validate_typeinfo(
&self,
record: &ItaniumTypeInfoRecord,
limits: StrictRttiLimits,
base_count: &mut u64,
) -> Result<()> {
if record.symbol.is_empty()
|| !decoder::is_typeinfo_symbol(&record.symbol)
|| record.type_name.is_empty()
|| u64::try_from(record.type_name.len())
.ok()
.is_none_or(|length| length > limits.max_name_bytes)
{
return Err(Error::format(
"strict RTTI record identity or name is invalid",
));
}
self.validate_pointer(&record.symbol, &record.runtime_vtable)?;
self.validate_pointer(&record.symbol, &record.type_name_pointer)?;
*base_count = base_count
.checked_add(
u64::try_from(record.bases.len())
.map_err(|_| Error::format("strict RTTI base count exceeds UInt64"))?,
)
.ok_or_else(|| Error::format("strict RTTI base count overflows"))?;
if *base_count > limits.max_bases
|| record
.bases
.iter()
.enumerate()
.any(|(index, base)| base.ordinal != index as u64)
{
return Err(Error::format(
"strict RTTI bases exceed limits or are not canonical",
));
}
for base in &record.bases {
self.validate_pointer(&record.symbol, &base.typeinfo)?;
if base.signed_offset != (base.offset_flags as i64) >> 8
|| base.is_virtual != (base.offset_flags & 1 != 0)
|| base.is_public != (base.offset_flags & 2 != 0)
{
return Err(Error::format(
"strict RTTI base semantics disagree with offset_flags",
));
}
}
if let Some(pointee) = &record.pointee {
self.validate_pointer(&record.symbol, &pointee.pointee)?;
if let Some(member_of) = &pointee.member_of {
self.validate_pointer(&record.symbol, member_of)?;
}
}
let shape_matches = match record.family {
ItaniumTypeInfoFamily::SingleInheritanceClass => {
record.bases.len() == 1 && record.pointee.is_none() && record.class_flags == 0
}
ItaniumTypeInfoFamily::VirtualMultipleInheritanceClass => record.pointee.is_none(),
ItaniumTypeInfoFamily::Pointer => {
record
.pointee
.as_ref()
.is_some_and(|pointee| pointee.member_of.is_none())
&& record.bases.is_empty()
&& record.class_flags == 0
}
ItaniumTypeInfoFamily::PointerToMember => {
record
.pointee
.as_ref()
.is_some_and(|pointee| pointee.member_of.is_some())
&& record.bases.is_empty()
&& record.class_flags == 0
}
_ => record.bases.is_empty() && record.pointee.is_none() && record.class_flags == 0,
};
if !shape_matches {
return Err(Error::format(
"strict RTTI family disagrees with its family-specific fields",
));
}
Ok(())
}
fn validate_pointer(&self, symbol: &str, pointer: &StrictPointerObservation) -> Result<()> {
let observation = usize::try_from(pointer.observation_ordinal)
.ok()
.and_then(|index| self.observations.get(index))
.ok_or_else(|| Error::format("strict RTTI pointer observation is absent"))?;
if observation.symbol != symbol
|| observation.kind != StrictRttiObservationKind::Pointer
|| observation.length != u64::from(pointer.width)
|| !matches!(pointer.width, 4 | 8)
{
return Err(Error::format(
"strict RTTI pointer disagrees with its byte observation",
));
}
Ok(())
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct StrictRttiBatchWire {
outcome: StrictRttiOutcome,
records: Vec<StrictRttiRecord>,
observations: Vec<StrictRttiObservation>,
gaps: Vec<StrictRttiGap>,
conservation: StrictRttiConservation,
}
impl<'de> Deserialize<'de> for StrictRttiBatch {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let wire = StrictRttiBatchWire::deserialize(deserializer)?;
let value = Self {
outcome: wire.outcome,
records: wire.records,
observations: wire.observations,
gaps: wire.gaps,
conservation: wire.conservation,
};
value
.validate(StrictRttiLimits::default())
.map_err(serde::de::Error::custom)?;
Ok(value)
}
}