use crate::container::ParsedDescriptor;
use crate::container::observation::{
ObservationIndex, ObservationSelector as IndexSelector, SECTION_PDF_SELECTORS, SELECTOR_OBJECT,
SELECTOR_REVISION, SELECTOR_STREAM,
};
use crate::dra::{Op, Program};
use crate::entropy::codec::EntropyChannelDescriptor;
use crate::entropy::model::EntropyModel;
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,
pub bytes_read: u64,
pub integrity_verified: bool,
}
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,
}
#[derive(Debug, Clone)]
pub(crate) struct OpWindow {
pub ops: Vec<Op>,
pub buf_start: u64,
pub ops_total: usize,
}
pub(crate) fn select_ops(
program: &Program,
object_lens: &[u64],
channel_lens: &[u64],
a: u64,
b: u64,
limits: Limits,
) -> Result<OpWindow> {
let per_op = 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 (ops, buf_start) = if is_linear_independent(program) {
let ops = program
.ops
.iter()
.enumerate()
.filter(|&(i, _)| intersects(i))
.map(|(_, op)| op.clone())
.collect();
(ops, starts[first])
} else {
(program.ops[..=last].to_vec(), 0)
};
Ok(OpWindow {
ops,
buf_start,
ops_total: program.ops.len(),
})
}
pub(crate) fn selection_references(
ops: &[Op],
objects_len: usize,
channels_len: usize,
) -> (Vec<bool>, Vec<bool>) {
let mut objects_used = vec![false; objects_len];
let mut channels_used = vec![false; channels_len];
for op in ops {
mark_references(op, &mut objects_used, &mut channels_used);
}
(objects_used, channels_used)
}
pub(crate) struct ServedSelection {
pub bytes: Vec<u8>,
pub ops_evaluated: usize,
pub ops_total: usize,
pub objects_fetched: usize,
pub referenced_object_bytes: u64,
pub channels_decoded: usize,
pub entropy_bytes_decoded: u64,
pub referenced_channel_bytes: u64,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn serve_selection(
objects: &[Vec<u8>],
channels_desc: &[EntropyChannelDescriptor],
models: &[EntropyModel],
window: OpWindow,
objects_used: &[bool],
channels_used: &[bool],
a: u64,
b: u64,
limits: Limits,
) -> Result<ServedSelection> {
let channels = decode_referenced_channels(channels_desc, models, channels_used, limits)?;
let ops_evaluated = window.ops.len();
let ops_total = window.ops_total;
let buf_start = window.buf_start;
let sub = Program::new(window.ops);
let out = sub.eval(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 = channels_desc[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 += objects[id].len() as u64;
}
}
Ok(ServedSelection {
bytes,
ops_evaluated,
ops_total,
objects_fetched,
referenced_object_bytes,
channels_decoded,
entropy_bytes_decoded,
referenced_channel_bytes,
})
}
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"))?;
if d.objects
.iter()
.any(|o| matches!(o, crate::container::ObjectSource::External { .. }))
{
return Err(Error::unsupported_feature(
"partial observation cannot resolve external objects (no resolver supplied)",
));
}
let objects: Vec<Vec<u8>> = d
.objects
.iter()
.map(|o| o.as_inline().unwrap_or(&[]).to_vec())
.collect();
let (a, b) = resolve_selector(index, selector, d.source_len)?;
let object_lens: Vec<u64> = objects.iter().map(|o| o.len() as u64).collect();
let channel_lens: Vec<u64> = d.channels.iter().map(|c| c.decoded_length).collect();
let window = select_ops(&d.program, &object_lens, &channel_lens, a, b, limits)?;
let (objects_used, channels_used) =
selection_references(&window.ops, objects.len(), d.channels.len());
let served = serve_selection(
&objects,
&d.channels,
&d.models,
window,
&objects_used,
&channels_used,
a,
b,
limits,
)?;
let descriptor_bytes_traversed = parsed.cost.graph
+ parsed.cost.index
+ served.referenced_object_bytes
+ served.referenced_channel_bytes;
let stats = ObservationStats {
ops_evaluated: served.ops_evaluated,
ops_total: served.ops_total,
objects_fetched: served.objects_fetched,
objects_total: objects.len(),
channels_decoded: served.channels_decoded,
channels_total: d.channels.len(),
entropy_bytes_decoded: served.entropy_bytes_decoded,
descriptor_bytes_traversed,
output_bytes: served.bytes.len() as u64,
bytes_read: 0,
integrity_verified: false,
};
Ok(ObservationReport {
range: (a, b),
bytes: served.bytes,
stats,
})
}
pub(crate) 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(
channels_desc: &[EntropyChannelDescriptor],
models: &[EntropyModel],
channels_used: &[bool],
limits: Limits,
) -> Result<Vec<Vec<u8>>> {
let mut channels: Vec<Vec<u8>> = vec![Vec::new(); channels_desc.len()];
for (id, used) in channels_used.iter().enumerate() {
if *used {
channels[id] = decode_channel_by_id(channels_desc, models, id, limits)?;
}
}
Ok(channels)
}
#[cfg(not(feature = "rans"))]
fn decode_referenced_channels(
channels_desc: &[EntropyChannelDescriptor],
_models: &[EntropyModel],
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(); channels_desc.len()])
}
#[cfg(feature = "rans")]
fn decode_channel_by_id(
channels_desc: &[EntropyChannelDescriptor],
models: &[EntropyModel],
id: usize,
limits: Limits,
) -> Result<Vec<u8>> {
let channel = channels_desc.get(id).ok_or_else(|| {
Error::invalid_model(format!(
"observation references missing entropy channel {id}"
))
})?;
let model = 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)
}