use crate::container::observation::{
ObservationIndex, ObservationSelector as IndexSelector, SECTION_PDF_SELECTORS, SELECTOR_OBJECT,
SELECTOR_REVISION, SELECTOR_STREAM,
};
use crate::container::{Descriptor, ParsedDescriptor};
use crate::dra::{Op, Program};
use crate::error::{Error, Result};
use crate::limits::Limits;
#[cfg(feature = "rans")]
use crate::entropy::rans::{Capsule, decode_channel};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObservationSelector {
ByteRange {
offset: u64,
len: u64,
},
PdfIndirectObject {
object: u32,
generation: u16,
},
PdfEncodedStream {
object: u32,
generation: u16,
},
PdfRevision {
index: u32,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ObservationStats {
pub ops_evaluated: usize,
pub ops_total: usize,
pub objects_fetched: usize,
pub objects_total: usize,
pub channels_decoded: usize,
pub channels_total: usize,
pub entropy_bytes_decoded: u64,
pub descriptor_bytes_traversed: u64,
pub output_bytes: u64,
}
impl ObservationStats {
pub fn work_amplification(&self) -> f64 {
if self.ops_total == 0 {
0.0
} else {
self.ops_evaluated as f64 / self.ops_total as f64
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ObservationReport {
pub range: (u64, u64),
pub bytes: Vec<u8>,
pub stats: ObservationStats,
}
pub fn materialize_observation(
parsed: &ParsedDescriptor,
selector: ObservationSelector,
limits: Limits,
) -> Result<ObservationReport> {
let d = &parsed.descriptor;
let index = d
.observation_index
.as_ref()
.ok_or_else(|| Error::unsupported_feature("descriptor has no observation index"))?;
let (a, b) = resolve_selector(index, selector, d.source_len)?;
let object_lens: Vec<u64> = d.objects.iter().map(|o| o.len() as u64).collect();
let channel_lens: Vec<u64> = d.channels.iter().map(|c| c.decoded_length).collect();
let per_op = d.program.analyze_ops(&object_lens, &channel_lens, limits)?;
let mut starts: Vec<u64> = Vec::with_capacity(per_op.len());
let mut ends: Vec<u64> = Vec::with_capacity(per_op.len());
let mut acc: u64 = 0;
for &len in &per_op {
starts.push(acc);
acc = acc
.checked_add(len)
.ok_or_else(|| Error::invalid_graph("observation op length overflow"))?;
ends.push(acc);
}
let intersects = |i: usize| per_op[i] > 0 && starts[i] < b && ends[i] > a;
let first = (0..per_op.len())
.find(|&i| intersects(i))
.ok_or_else(|| Error::invalid_graph("observation range is not covered by the program"))?;
let last = (0..per_op.len())
.rev()
.find(|&i| intersects(i))
.ok_or_else(|| Error::invalid_graph("observation range is not covered by the program"))?;
let linear = is_linear_independent(&d.program);
let (selected_ops, buf_start): (Vec<Op>, u64) = if linear {
let ops = d
.program
.ops
.iter()
.enumerate()
.filter(|&(i, _)| intersects(i))
.map(|(_, op)| op.clone())
.collect();
(ops, starts[first])
} else {
(d.program.ops[..=last].to_vec(), 0)
};
let mut objects_used = vec![false; d.objects.len()];
let mut channels_used = vec![false; d.channels.len()];
for op in &selected_ops {
mark_references(op, &mut objects_used, &mut channels_used);
}
let channels = decode_referenced_channels(d, &channels_used, limits)?;
let sub = Program::new(selected_ops);
let out = sub.eval(&d.objects, &channels, limits)?;
let lo = a
.checked_sub(buf_start)
.and_then(|v| usize::try_from(v).ok())
.ok_or_else(|| Error::internal_invariant("observation window precedes evaluated buffer"))?;
let hi = b
.checked_sub(buf_start)
.and_then(|v| usize::try_from(v).ok())
.ok_or_else(|| Error::internal_invariant("observation window overflow"))?;
if hi > out.len() {
return Err(Error::internal_invariant(
"evaluated buffer is shorter than the requested observation window",
));
}
let bytes = out[lo..hi].to_vec();
let mut entropy_bytes_decoded: u64 = 0;
let mut referenced_channel_bytes: u64 = 0;
let mut channels_decoded: usize = 0;
for (id, used) in channels_used.iter().enumerate() {
if *used {
channels_decoded += 1;
let payload_len = d.channels[id].payload.len() as u64;
entropy_bytes_decoded += payload_len;
referenced_channel_bytes += payload_len;
}
}
let mut objects_fetched: usize = 0;
let mut referenced_object_bytes: u64 = 0;
for (id, used) in objects_used.iter().enumerate() {
if *used {
objects_fetched += 1;
referenced_object_bytes += d.objects[id].len() as u64;
}
}
let descriptor_bytes_traversed =
parsed.cost.graph + parsed.cost.index + referenced_object_bytes + referenced_channel_bytes;
let stats = ObservationStats {
ops_evaluated: sub.ops.len(),
ops_total: d.program.ops.len(),
objects_fetched,
objects_total: d.objects.len(),
channels_decoded,
channels_total: d.channels.len(),
entropy_bytes_decoded,
descriptor_bytes_traversed,
output_bytes: bytes.len() as u64,
};
Ok(ObservationReport {
range: (a, b),
bytes,
stats,
})
}
fn resolve_selector(
index: &ObservationIndex,
selector: ObservationSelector,
source_len: u64,
) -> Result<(u64, u64)> {
match selector {
ObservationSelector::ByteRange { offset, len } => {
if len == 0 {
return Err(Error::usage("observation byte range must be non-empty"));
}
let end = offset
.checked_add(len)
.ok_or_else(|| Error::usage("observation byte range overflows"))?;
if end > source_len {
return Err(Error::usage(format!(
"observation byte range {offset}..{end} exceeds source length {source_len}"
)));
}
Ok((offset, end))
}
ObservationSelector::PdfIndirectObject { object, generation } => resolve_pdf(
index,
SELECTOR_OBJECT,
object,
u32::from(generation),
"indirect object",
),
ObservationSelector::PdfEncodedStream { object, generation } => resolve_pdf(
index,
SELECTOR_STREAM,
object,
u32::from(generation),
"encoded stream",
),
ObservationSelector::PdfRevision { index: rev } => {
if index.section_flags & SECTION_PDF_SELECTORS == 0 {
return Err(Error::unsupported_feature(
"observation index has no PDF selector table",
));
}
let hits: Vec<&IndexSelector> = index
.selectors
.iter()
.filter(|s| s.kind == SELECTOR_REVISION && s.number == rev)
.collect();
match hits.as_slice() {
[one] => selector_range(one, "PDF revision", u64::from(rev)),
[] => Err(Error::unsupported_feature(format!(
"observation index has no selector for PDF revision {rev}"
))),
_ => Err(Error::invalid_container(format!(
"observation index has ambiguous selectors for PDF revision {rev}"
))),
}
}
}
}
fn resolve_pdf(
index: &ObservationIndex,
kind: u8,
number: u32,
generation: u32,
label: &str,
) -> Result<(u64, u64)> {
if index.section_flags & SECTION_PDF_SELECTORS == 0 {
return Err(Error::unsupported_feature(
"observation index has no PDF selector table",
));
}
let hits: Vec<&IndexSelector> = index
.selectors
.iter()
.filter(|s| s.kind == kind && s.number == number && s.generation == generation)
.collect();
match hits.as_slice() {
[one] => selector_range(one, label, u64::from(number)),
[] => Err(Error::unsupported_feature(format!(
"observation index has no selector for {label} {number} {generation}"
))),
_ => Err(Error::invalid_container(format!(
"observation index has ambiguous selectors for {label} {number} {generation}"
))),
}
}
fn selector_range(selector: &IndexSelector, label: &str, id: u64) -> Result<(u64, u64)> {
let end = selector
.out_off
.checked_add(selector.out_len)
.ok_or_else(|| {
Error::invalid_container(format!("{label} {id} selector range overflows"))
})?;
Ok((selector.out_off, end))
}
fn is_linear_independent(program: &Program) -> bool {
program.ops.iter().all(|op| {
matches!(
op,
Op::EmitObject { .. }
| Op::Inline { .. }
| Op::DecodeChannel { .. }
| Op::DeflateReplay { .. }
| Op::InterleaveChannels { .. }
)
})
}
fn mark_references(op: &Op, objects: &mut [bool], channels: &mut [bool]) {
match op {
Op::EmitObject { object_id } => mark(objects, *object_id),
Op::Inline { .. }
| Op::MarkOffset { .. }
| Op::EmitOffset { .. }
| Op::RepeatLast { .. } => {}
Op::DecodeChannel { channel_id } => mark(channels, *channel_id),
Op::InterleaveChannels {
kinds_channel,
lengths_channel,
first_payload_channel,
payload_channel_count,
} => {
mark(channels, *kinds_channel);
mark(channels, *lengths_channel);
for k in 0..u32::from(*payload_channel_count) {
if let Some(id) = first_payload_channel.checked_add(k) {
mark(channels, id);
}
}
}
Op::PackSegments { data_object, .. } => mark(objects, *data_object),
Op::PackedChannels {
data_channel,
plan_channel,
..
} => {
mark(channels, *data_channel);
mark(channels, *plan_channel);
}
Op::DeflateReplay {
source_kind,
source_id,
corrections_object,
..
} => {
match *source_kind {
crate::dra::op::DEFLATE_SOURCE_OBJECT => mark(objects, *source_id),
crate::dra::op::DEFLATE_SOURCE_CHANNEL => mark(channels, *source_id),
_ => {}
}
mark(objects, *corrections_object);
}
}
}
fn mark(flags: &mut [bool], id: u32) {
if let Some(slot) = flags.get_mut(id as usize) {
*slot = true;
}
}
#[cfg(feature = "rans")]
fn decode_referenced_channels(
d: &Descriptor,
channels_used: &[bool],
limits: Limits,
) -> Result<Vec<Vec<u8>>> {
let mut channels: Vec<Vec<u8>> = vec![Vec::new(); d.channels.len()];
for (id, used) in channels_used.iter().enumerate() {
if *used {
channels[id] = decode_channel_by_id(d, id, limits)?;
}
}
Ok(channels)
}
#[cfg(not(feature = "rans"))]
fn decode_referenced_channels(
d: &Descriptor,
channels_used: &[bool],
_limits: Limits,
) -> Result<Vec<Vec<u8>>> {
if channels_used.iter().any(|&used| used) {
return Err(Error::unsupported_feature(
"this build was compiled without the `rans` feature",
));
}
Ok(vec![Vec::new(); d.channels.len()])
}
#[cfg(feature = "rans")]
fn decode_channel_by_id(d: &Descriptor, id: usize, limits: Limits) -> Result<Vec<u8>> {
let channel = d.channels.get(id).ok_or_else(|| {
Error::invalid_model(format!(
"observation references missing entropy channel {id}"
))
})?;
let model = d.models.get(channel.model_id as usize).ok_or_else(|| {
Error::invalid_model(format!(
"entropy channel {id} references missing model {}",
channel.model_id
))
})?;
let capsule = Capsule {
initial_state: channel.initial_state,
payload: channel.payload.clone(),
symbol_count: channel.symbol_count,
decoded_length: channel.decoded_length,
};
decode_channel(model, &capsule, limits)
}