Skip to main content

mib_rs/mib/index/
codec.rs

1//! Exact decoding, canonical encoding, and object-specific bounds.
2
3use std::fmt;
4use std::ops::Range;
5use std::sync::Arc;
6
7use crate::mib::Oid;
8
9use super::constraint::ConstraintCheck;
10use super::schema::{
11    IndexComponentSchema, IndexSchema, IndexWireType, IntegerConstraint, IntegerIndexKind,
12    LengthConstraint, VariableFraming,
13};
14use super::value::{IndexValue, IndexValueKind, IndexValueRef};
15
16/// Maximum arc count of a complete SMI instance OID.
17pub const MAX_INSTANCE_OID_ARCS: usize = 128;
18
19/// Whether known MIB constraint violations fail exact decoding.
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
21pub enum ConstraintMode {
22    /// Reject values that conflict with known effective constraints.
23    #[default]
24    Enforce,
25    /// Return structurally exact values and report constraint violations.
26    Report,
27}
28
29/// Handling of an encoding value whose validity depends on unresolved metadata.
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
31pub enum IncompleteConstraintMode {
32    /// Reject values that cannot be proven valid.
33    #[default]
34    Reject,
35    /// Allow values that are not proven invalid.
36    Allow,
37}
38
39/// Per-operation exact-decode bounds and constraint policy.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct DecodeOptions {
42    max_suffix_arcs: usize,
43    max_value_arcs: usize,
44    max_components: usize,
45    constraint_mode: ConstraintMode,
46}
47
48impl DecodeOptions {
49    /// Constructs options bounded by a complete suffix length in OID arcs.
50    #[must_use]
51    pub const fn new(max_suffix_arcs: usize) -> Self {
52        Self {
53            max_suffix_arcs,
54            max_value_arcs: max_suffix_arcs,
55            max_components: usize::MAX,
56            constraint_mode: ConstraintMode::Enforce,
57        }
58    }
59
60    /// Sets the maximum OID arcs copied into one semantic value.
61    #[must_use]
62    pub const fn with_max_value_arcs(mut self, maximum: usize) -> Self {
63        self.max_value_arcs = maximum;
64        self
65    }
66
67    /// Sets the maximum schema component count accepted by this operation.
68    #[must_use]
69    pub const fn with_max_components(mut self, maximum: usize) -> Self {
70        self.max_components = maximum;
71        self
72    }
73
74    /// Sets constraint enforcement or reporting.
75    #[must_use]
76    pub const fn with_constraint_mode(mut self, mode: ConstraintMode) -> Self {
77        self.constraint_mode = mode;
78        self
79    }
80
81    /// Returns the maximum accepted suffix length in OID arcs.
82    #[must_use]
83    pub const fn max_suffix_arcs(self) -> usize {
84        self.max_suffix_arcs
85    }
86}
87
88/// Per-operation canonical-encode bounds and incomplete-constraint policy.
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub struct EncodeOptions {
91    max_suffix_arcs: usize,
92    max_value_arcs: usize,
93    incomplete_constraints: IncompleteConstraintMode,
94}
95
96impl EncodeOptions {
97    /// Constructs strict options bounded by a complete suffix length in OID arcs.
98    #[must_use]
99    pub const fn new(max_suffix_arcs: usize) -> Self {
100        Self {
101            max_suffix_arcs,
102            max_value_arcs: max_suffix_arcs,
103            incomplete_constraints: IncompleteConstraintMode::Reject,
104        }
105    }
106
107    /// Sets the maximum OID arcs read from one semantic value.
108    #[must_use]
109    pub const fn with_max_value_arcs(mut self, maximum: usize) -> Self {
110        self.max_value_arcs = maximum;
111        self
112    }
113
114    /// Sets handling for values not decidable from incomplete metadata.
115    #[must_use]
116    pub const fn with_incomplete_constraints(mut self, mode: IncompleteConstraintMode) -> Self {
117        self.incomplete_constraints = mode;
118        self
119    }
120
121    /// Returns the maximum emitted suffix length in OID arcs.
122    #[must_use]
123    pub const fn max_suffix_arcs(self) -> usize {
124        self.max_suffix_arcs
125    }
126}
127
128/// A known conflict between one exact value and effective MIB metadata.
129#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
130pub enum IndexConstraintViolation {
131    /// An integer lies outside the normalized effective range alternatives.
132    #[error("integer value {value} is outside the effective range")]
133    IntegerRange {
134        /// Contains the rejected integer.
135        value: i64,
136    },
137    /// An integer is absent from the normalized effective enumeration.
138    #[error("integer value {value} is not in the effective enumeration")]
139    IntegerEnumeration {
140        /// Contains the rejected integer.
141        value: i64,
142    },
143    /// A value's length violates its effective `SIZE` constraint.
144    ///
145    /// `length` counts octets for octet-valued types and OID arcs for
146    /// `OBJECT IDENTIFIER` values.
147    #[error("value length {length} is outside the effective SIZE constraint")]
148    Length {
149        /// Contains the rejected length in octets or OID arcs.
150        length: usize,
151    },
152}
153
154/// A reported constraint violation associated with a component position.
155#[derive(Clone, Debug, PartialEq, Eq)]
156pub struct ReportedIndexViolation {
157    component_position: usize,
158    violation: IndexConstraintViolation,
159}
160
161impl ReportedIndexViolation {
162    /// Returns the zero-based effective `INDEX` component position.
163    #[must_use]
164    pub const fn component_position(&self) -> usize {
165        self.component_position
166    }
167
168    /// Returns the known constraint conflict.
169    #[must_use]
170    pub const fn violation(&self) -> &IndexConstraintViolation {
171        &self.violation
172    }
173}
174
175/// Complete exact decoding of one suffix.
176#[derive(Clone, Debug, PartialEq, Eq)]
177pub struct DecodedRowIndex<'schema, 'suffix> {
178    schema: &'schema IndexSchema,
179    suffix: &'suffix [u32],
180    values: Box<[IndexValue]>,
181    ranges: Box<[Range<usize>]>,
182    violations: Box<[ReportedIndexViolation]>,
183}
184
185impl<'schema, 'suffix> DecodedRowIndex<'schema, 'suffix> {
186    /// Returns the schema used for the operation.
187    #[must_use]
188    pub const fn schema(&self) -> &'schema IndexSchema {
189        self.schema
190    }
191
192    /// Returns the exact input suffix, all of which was consumed.
193    #[must_use]
194    pub const fn raw_arcs(&self) -> &'suffix [u32] {
195        self.suffix
196    }
197
198    /// Returns semantic values in effective `INDEX` clause order.
199    #[must_use]
200    pub const fn values(&self) -> &[IndexValue] {
201        &self.values
202    }
203
204    /// Iterates component views in effective `INDEX` clause order.
205    #[must_use]
206    pub fn components(&self) -> DecodedIndexComponents<'_, 'schema, 'suffix> {
207        DecodedIndexComponents {
208            decoded: self,
209            position: 0,
210        }
211    }
212
213    /// Returns known constraint conflicts collected in report mode.
214    #[must_use]
215    pub const fn violations(&self) -> &[ReportedIndexViolation] {
216        &self.violations
217    }
218}
219
220/// Borrowed view of one successfully decoded component.
221#[derive(Clone, Copy, Debug)]
222pub struct DecodedIndexComponent<'a, 'schema, 'suffix> {
223    schema: &'schema IndexComponentSchema,
224    value: &'a IndexValue,
225    arc_range: &'a Range<usize>,
226    raw_arcs: &'suffix [u32],
227}
228
229impl<'a, 'schema, 'suffix> DecodedIndexComponent<'a, 'schema, 'suffix> {
230    /// Returns the owned schema metadata for this position.
231    #[must_use]
232    pub const fn schema(&self) -> &'schema IndexComponentSchema {
233        self.schema
234    }
235
236    /// Returns the component name.
237    #[must_use]
238    pub fn name(&self) -> &'schema str {
239        self.schema.name()
240    }
241
242    /// Returns the decoded semantic value.
243    #[must_use]
244    pub const fn value(&self) -> &'a IndexValue {
245        self.value
246    }
247
248    /// Returns the zero-based, half-open range occupied in the complete suffix.
249    ///
250    /// The range includes a length prefix when the component uses one.
251    #[must_use]
252    pub fn arc_range(&self) -> Range<usize> {
253        self.arc_range.clone()
254    }
255
256    /// Returns the exact raw arcs, including a length prefix when present.
257    #[must_use]
258    pub const fn raw_arcs(&self) -> &'suffix [u32] {
259        self.raw_arcs
260    }
261}
262
263/// Iterator over decoded component views.
264pub struct DecodedIndexComponents<'a, 'schema, 'suffix> {
265    decoded: &'a DecodedRowIndex<'schema, 'suffix>,
266    position: usize,
267}
268
269impl<'a, 'schema, 'suffix> Iterator for DecodedIndexComponents<'a, 'schema, 'suffix> {
270    type Item = DecodedIndexComponent<'a, 'schema, 'suffix>;
271
272    fn next(&mut self) -> Option<Self::Item> {
273        let position = self.position;
274        let schema = self.decoded.schema.components().get(position)?;
275        let value = &self.decoded.values[position];
276        let arc_range = &self.decoded.ranges[position];
277        self.position += 1;
278        Some(DecodedIndexComponent {
279            schema,
280            value,
281            arc_range,
282            raw_arcs: &self.decoded.suffix[arc_range.clone()],
283        })
284    }
285
286    fn size_hint(&self) -> (usize, Option<usize>) {
287        let remaining = self.decoded.values.len() - self.position;
288        (remaining, Some(remaining))
289    }
290}
291
292impl ExactSizeIterator for DecodedIndexComponents<'_, '_, '_> {}
293
294/// Successfully decoded component retained on a later failure.
295#[derive(Clone, Debug, PartialEq, Eq)]
296pub struct DecodedPrefixComponent<'suffix> {
297    position: usize,
298    value: IndexValue,
299    arc_range: Range<usize>,
300    raw_arcs: &'suffix [u32],
301}
302
303impl<'suffix> DecodedPrefixComponent<'suffix> {
304    /// Returns the zero-based effective `INDEX` component position.
305    #[must_use]
306    pub const fn position(&self) -> usize {
307        self.position
308    }
309
310    /// Returns the decoded semantic value.
311    #[must_use]
312    pub const fn value(&self) -> &IndexValue {
313        &self.value
314    }
315
316    /// Returns the zero-based, half-open range occupied in the complete suffix.
317    ///
318    /// The range includes a length prefix when the component uses one.
319    #[must_use]
320    pub fn arc_range(&self) -> Range<usize> {
321        self.arc_range.clone()
322    }
323
324    /// Returns the exact raw arcs, including a length prefix when present.
325    #[must_use]
326    pub const fn raw_arcs(&self) -> &'suffix [u32] {
327        self.raw_arcs
328    }
329}
330
331/// Stable reason exact decoding failed.
332#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
333pub enum IndexDecodeErrorKind {
334    /// The complete suffix exceeds the operation's arc limit.
335    #[error("suffix has {actual} arcs, exceeding the operation limit of {maximum}")]
336    SuffixTooLong {
337        /// Contains the supplied suffix length in OID arcs.
338        actual: usize,
339        /// Contains the configured suffix limit in OID arcs.
340        maximum: usize,
341    },
342    /// The schema exceeds the operation's component limit.
343    #[error("schema has {actual} components, exceeding the operation limit of {maximum}")]
344    TooManyComponents {
345        /// Contains the schema's component count.
346        actual: usize,
347        /// Contains the configured component limit.
348        maximum: usize,
349    },
350    /// The remaining suffix cannot contain the complete component.
351    ///
352    /// `needed` and `available` count arcs from the component's starting offset.
353    #[error("component needs {needed} arcs but only {available} remain")]
354    Truncated {
355        /// Contains the complete component width in OID arcs.
356        needed: usize,
357        /// Contains the available component width in OID arcs.
358        available: usize,
359    },
360    /// A length prefix exceeds the per-value arc limit or cannot fit in `usize`.
361    #[error("declared length {declared} exceeds the value limit of {maximum}")]
362    LengthPrefixTooLarge {
363        /// Contains the length declared by the prefix in OID arcs.
364        declared: u32,
365        /// Contains the configured per-value limit in OID arcs.
366        maximum: usize,
367    },
368    /// A fixed or implied value exceeds the per-value arc limit.
369    #[error("value has {actual} arcs, exceeding the value limit of {maximum}")]
370    ValueTooLong {
371        /// Contains the supplied value length in OID arcs.
372        actual: usize,
373        /// Contains the configured per-value limit in OID arcs.
374        maximum: usize,
375    },
376    /// An octet-valued component contains an arc greater than 255.
377    #[error("arc value {value} is not an octet")]
378    InvalidOctet {
379        /// Contains the rejected OID arc.
380        value: u32,
381    },
382    /// An `Integer32` component contains an arc greater than `i32::MAX`.
383    #[error("arc value {value} is outside the non-negative Integer32 index domain")]
384    Integer32OutOfDomain {
385        /// Contains the rejected OID arc.
386        value: u32,
387    },
388    /// The complete schema decoded successfully but did not consume the suffix.
389    #[error("{count} trailing arcs remain")]
390    TrailingArcs {
391        /// Contains the unconsumed suffix length in OID arcs.
392        count: usize,
393    },
394    /// A decoded value conflicts with known effective MIB constraints.
395    #[error("{0}")]
396    ConstraintViolation(IndexConstraintViolation),
397}
398
399/// Exact-decode failure with typed context and successfully decoded prefix.
400#[derive(Clone, Debug, PartialEq, Eq)]
401pub struct IndexDecodeError<'suffix> {
402    kind: IndexDecodeErrorKind,
403    component_position: Option<usize>,
404    component_name: Option<String>,
405    arc_offset: usize,
406    decoded_prefix: Box<[DecodedPrefixComponent<'suffix>]>,
407    remaining: &'suffix [u32],
408}
409
410impl<'suffix> IndexDecodeError<'suffix> {
411    /// Returns the stable failure reason.
412    #[must_use]
413    pub const fn kind(&self) -> &IndexDecodeErrorKind {
414        &self.kind
415    }
416
417    /// Returns the zero-based component position for a component failure.
418    ///
419    /// Whole-suffix failures return `None`. This value is present exactly when
420    /// [`Self::component_name`] is present.
421    #[must_use]
422    pub const fn component_position(&self) -> Option<usize> {
423        self.component_position
424    }
425
426    /// Returns the component name for a component failure.
427    ///
428    /// Whole-suffix failures return `None`. This value is present exactly when
429    /// [`Self::component_position`] is present.
430    #[must_use]
431    pub fn component_name(&self) -> Option<&str> {
432        self.component_name.as_deref()
433    }
434
435    /// Returns the zero-based arc offset where the failure was detected.
436    ///
437    /// The offset is relative to the complete supplied suffix. For a trailing
438    /// arc error, it is the first unconsumed arc.
439    #[must_use]
440    pub const fn arc_offset(&self) -> usize {
441        self.arc_offset
442    }
443
444    /// Returns every component decoded before the failing component or suffix check.
445    #[must_use]
446    pub const fn decoded_prefix(&self) -> &[DecodedPrefixComponent<'suffix>] {
447        &self.decoded_prefix
448    }
449
450    /// Returns the input arcs retained from the failing component or suffix check.
451    ///
452    /// Component failures retain arcs from that component's start, even when
453    /// [`Self::arc_offset`] identifies a later invalid arc. Whole-suffix failures
454    /// retain arcs from `arc_offset`.
455    #[must_use]
456    pub const fn remaining_arcs(&self) -> &'suffix [u32] {
457        self.remaining
458    }
459}
460
461impl fmt::Display for IndexDecodeError<'_> {
462    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463        if let (Some(position), Some(name)) = (self.component_position, &self.component_name) {
464            write!(
465                f,
466                "failed to decode index component {position} ({name}) at suffix arc {}: {}",
467                self.arc_offset, self.kind
468            )
469        } else {
470            write!(
471                f,
472                "failed to decode index suffix at arc {}: {}",
473                self.arc_offset, self.kind
474            )
475        }
476    }
477}
478
479impl std::error::Error for IndexDecodeError<'_> {}
480
481/// Immutable canonical index suffix.
482#[derive(Clone, Debug, PartialEq, Eq, Hash)]
483pub struct IndexSuffix(Box<[u32]>);
484
485impl IndexSuffix {
486    /// Returns the suffix length in OID arcs.
487    #[must_use]
488    pub const fn len(&self) -> usize {
489        self.0.len()
490    }
491
492    /// Returns whether the suffix contains no OID arcs.
493    #[must_use]
494    pub const fn is_empty(&self) -> bool {
495        self.0.is_empty()
496    }
497}
498
499impl AsRef<[u32]> for IndexSuffix {
500    fn as_ref(&self) -> &[u32] {
501        &self.0
502    }
503}
504
505impl std::ops::Deref for IndexSuffix {
506    type Target = [u32];
507
508    fn deref(&self) -> &Self::Target {
509        &self.0
510    }
511}
512
513impl From<IndexSuffix> for Oid {
514    fn from(value: IndexSuffix) -> Self {
515        Oid::from(value.0.into_vec())
516    }
517}
518
519impl From<&IndexSuffix> for Oid {
520    fn from(value: &IndexSuffix) -> Self {
521        Oid::from(value.as_ref())
522    }
523}
524
525/// Stable reason canonical encoding failed.
526#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
527pub enum IndexEncodeErrorKind {
528    /// The value sequence ended before every schema component received a value.
529    #[error("too few values: expected {expected}, received {actual}")]
530    TooFewValues {
531        /// Contains the schema's component count.
532        expected: usize,
533        /// Contains the supplied value count.
534        actual: usize,
535    },
536    /// The value sequence contains data after every schema component was encoded.
537    #[error("too many values: expected {expected}")]
538    TooManyValues {
539        /// Contains the schema's component count.
540        expected: usize,
541    },
542    /// A value's semantic kind does not match its schema component.
543    #[error("wrong value kind: expected {expected}, received {actual}")]
544    WrongValueKind {
545        /// Contains the semantic kind required by the schema component.
546        expected: IndexValueKind,
547        /// Contains the supplied semantic kind.
548        actual: IndexValueKind,
549    },
550    /// A negative `Integer32` cannot be represented by an unsigned OID arc.
551    #[error("negative Integer32 value {value} cannot be encoded as an OID arc")]
552    NegativeInteger32 {
553        /// Contains the rejected integer.
554        value: i32,
555    },
556    /// A fixed-width value has the wrong number of arcs.
557    #[error("fixed component needs {expected} value arcs, received {actual}")]
558    FixedLength {
559        /// Contains the fixed component width in OID arcs.
560        expected: usize,
561        /// Contains the supplied value width in OID arcs.
562        actual: usize,
563    },
564    /// One semantic value exceeds the per-value arc limit.
565    #[error("value has {actual} arcs, exceeding the value limit of {maximum}")]
566    ValueTooLong {
567        /// Contains the supplied value length in OID arcs.
568        actual: usize,
569        /// Contains the configured per-value limit in OID arcs.
570        maximum: usize,
571    },
572    /// A value length cannot be represented by the required `u32` length arc.
573    #[error("value length {length} cannot be represented by one OID arc")]
574    LengthPrefixOverflow {
575        /// Contains the unrepresentable value length in OID arcs.
576        length: usize,
577    },
578    /// Adding a component would exceed the complete suffix arc limit.
579    #[error("encoded suffix would have {actual} arcs, exceeding the limit of {maximum}")]
580    SuffixTooLong {
581        /// Contains the resulting suffix length in OID arcs.
582        actual: usize,
583        /// Contains the configured suffix limit in OID arcs.
584        maximum: usize,
585    },
586    /// A value conflicts with known effective MIB constraints.
587    #[error("{0}")]
588    ConstraintViolation(IndexConstraintViolation),
589    /// Unresolved metadata prevents the codec from proving the value valid.
590    #[error("constraint validity is indeterminate because metadata is unresolved")]
591    IndeterminateConstraint,
592    /// Computing an encoded arc count overflowed `usize`.
593    #[error("arithmetic overflow while encoding the suffix")]
594    ArithmeticOverflow,
595}
596
597/// Canonical-encode failure with component context.
598#[derive(Clone, Debug, PartialEq, Eq)]
599pub struct IndexEncodeError {
600    kind: IndexEncodeErrorKind,
601    component_position: Option<usize>,
602    component_name: Option<String>,
603}
604
605impl IndexEncodeError {
606    /// Returns the stable failure reason.
607    #[must_use]
608    pub const fn kind(&self) -> &IndexEncodeErrorKind {
609        &self.kind
610    }
611
612    /// Returns the zero-based component position for a component failure.
613    ///
614    /// Whole-sequence failures return `None`. This value is present exactly
615    /// when [`Self::component_name`] is present.
616    #[must_use]
617    pub const fn component_position(&self) -> Option<usize> {
618        self.component_position
619    }
620
621    /// Returns the component name for a component failure.
622    ///
623    /// Whole-sequence failures return `None`. This value is present exactly
624    /// when [`Self::component_position`] is present.
625    #[must_use]
626    pub fn component_name(&self) -> Option<&str> {
627        self.component_name.as_deref()
628    }
629}
630
631impl fmt::Display for IndexEncodeError {
632    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
633        if let (Some(position), Some(name)) = (self.component_position, &self.component_name) {
634            write!(
635                f,
636                "failed to encode index component {position} ({name}): {}",
637                self.kind
638            )
639        } else {
640            write!(f, "failed to encode index suffix: {}", self.kind)
641        }
642    }
643}
644
645impl std::error::Error for IndexEncodeError {}
646
647impl IndexSchema {
648    /// Decodes one complete suffix exactly under this owned schema.
649    ///
650    /// The operation succeeds only when every schema component consumes its
651    /// canonical share of the suffix and no trailing arcs remain.
652    pub fn decode_exact<'schema, 'suffix>(
653        &'schema self,
654        suffix: &'suffix [u32],
655        options: DecodeOptions,
656    ) -> Result<DecodedRowIndex<'schema, 'suffix>, IndexDecodeError<'suffix>> {
657        if suffix.len() > options.max_suffix_arcs {
658            return Err(IndexDecodeError {
659                kind: IndexDecodeErrorKind::SuffixTooLong {
660                    actual: suffix.len(),
661                    maximum: options.max_suffix_arcs,
662                },
663                component_position: None,
664                component_name: None,
665                arc_offset: 0,
666                decoded_prefix: Box::new([]),
667                remaining: suffix,
668            });
669        }
670        if self.len() > options.max_components {
671            return Err(IndexDecodeError {
672                kind: IndexDecodeErrorKind::TooManyComponents {
673                    actual: self.len(),
674                    maximum: options.max_components,
675                },
676                component_position: None,
677                component_name: None,
678                arc_offset: 0,
679                decoded_prefix: Box::new([]),
680                remaining: suffix,
681            });
682        }
683
684        let mut values = Vec::with_capacity(self.len());
685        let mut ranges = Vec::with_capacity(self.len());
686        let mut violations = Vec::new();
687        let mut position = 0usize;
688
689        for (component_position, component) in self.components().iter().enumerate() {
690            let start = position;
691            let value = match component.wire_type() {
692                IndexWireType::Integer { kind, allowed } => {
693                    let Some(&arc) = suffix.get(position) else {
694                        return Err(decode_error(
695                            IndexDecodeErrorKind::Truncated {
696                                needed: 1,
697                                available: 0,
698                            },
699                            component_position,
700                            component,
701                            start,
702                            &values,
703                            &ranges,
704                            suffix,
705                        ));
706                    };
707                    position += 1;
708                    let value = decode_integer(*kind, arc).map_err(|kind| {
709                        decode_error(
710                            kind,
711                            component_position,
712                            component,
713                            start,
714                            &values,
715                            &ranges,
716                            suffix,
717                        )
718                    })?;
719                    if let Some(violation) = integer_violation(allowed, i64::from(arc)) {
720                        handle_decode_violation(
721                            options.constraint_mode,
722                            component_position,
723                            component,
724                            start,
725                            violation,
726                            &mut violations,
727                            &values,
728                            &ranges,
729                            suffix,
730                        )?;
731                    }
732                    value
733                }
734                IndexWireType::IpAddress => {
735                    let data = take_value_arcs(
736                        suffix,
737                        position,
738                        4,
739                        options.max_value_arcs,
740                        component_position,
741                        component,
742                        &values,
743                        &ranges,
744                    )?;
745                    if let Some((offset, value)) = invalid_octet(data) {
746                        return Err(decode_error_at(
747                            IndexDecodeErrorKind::InvalidOctet { value },
748                            component_position,
749                            component,
750                            start + offset,
751                            start,
752                            &values,
753                            &ranges,
754                            suffix,
755                        ));
756                    }
757                    position += 4;
758                    IndexValue::IpAddress(std::array::from_fn(|offset| data[offset] as u8))
759                }
760                IndexWireType::Octets {
761                    kind,
762                    framing,
763                    lengths,
764                } => {
765                    let (data_start, length) = framed_value(
766                        *framing,
767                        suffix,
768                        position,
769                        options.max_value_arcs,
770                        component_position,
771                        component,
772                        &values,
773                        &ranges,
774                    )?;
775                    let data = &suffix[data_start..data_start + length];
776                    if let Some((offset, value)) = invalid_octet(data) {
777                        return Err(decode_error_at(
778                            IndexDecodeErrorKind::InvalidOctet { value },
779                            component_position,
780                            component,
781                            data_start + offset,
782                            start,
783                            &values,
784                            &ranges,
785                            suffix,
786                        ));
787                    }
788                    if let Some(violation) = length_violation(lengths, length) {
789                        handle_decode_violation(
790                            options.constraint_mode,
791                            component_position,
792                            component,
793                            start,
794                            violation,
795                            &mut violations,
796                            &values,
797                            &ranges,
798                            suffix,
799                        )?;
800                    }
801                    position = data_start + length;
802                    let bytes: Vec<u8> = data.iter().map(|arc| *arc as u8).collect();
803                    match kind {
804                        super::schema::OctetIndexKind::OctetString => {
805                            IndexValue::OctetString(bytes)
806                        }
807                        super::schema::OctetIndexKind::Bits => IndexValue::Bits(bytes),
808                        super::schema::OctetIndexKind::Opaque => IndexValue::Opaque(bytes),
809                    }
810                }
811                IndexWireType::ObjectIdentifier { framing, lengths } => {
812                    let (data_start, length) = framed_value(
813                        *framing,
814                        suffix,
815                        position,
816                        options.max_value_arcs,
817                        component_position,
818                        component,
819                        &values,
820                        &ranges,
821                    )?;
822                    if let Some(violation) = length_violation(lengths, length) {
823                        handle_decode_violation(
824                            options.constraint_mode,
825                            component_position,
826                            component,
827                            start,
828                            violation,
829                            &mut violations,
830                            &values,
831                            &ranges,
832                            suffix,
833                        )?;
834                    }
835                    position = data_start + length;
836                    IndexValue::ObjectIdentifier(Oid::from(&suffix[data_start..position]))
837                }
838            };
839            values.push(value);
840            ranges.push(start..position);
841        }
842
843        if position != suffix.len() {
844            return Err(decode_whole_error(
845                IndexDecodeErrorKind::TrailingArcs {
846                    count: suffix.len() - position,
847                },
848                position,
849                &values,
850                &ranges,
851                suffix,
852            ));
853        }
854
855        Ok(DecodedRowIndex {
856            schema: self,
857            suffix,
858            values: values.into_boxed_slice(),
859            ranges: ranges.into_boxed_slice(),
860            violations: violations.into_boxed_slice(),
861        })
862    }
863
864    /// Canonically encodes a complete value sequence under this owned schema.
865    ///
866    /// The value count and semantic kinds must exactly match the schema.
867    pub fn encode_canonical<'a>(
868        &self,
869        values: impl IntoIterator<Item = IndexValueRef<'a>>,
870        options: EncodeOptions,
871    ) -> Result<IndexSuffix, IndexEncodeError> {
872        let mut values = values.into_iter();
873        let mut suffix =
874            Vec::with_capacity(self.minimum_suffix_arcs().min(options.max_suffix_arcs));
875
876        for (position, component) in self.components().iter().enumerate() {
877            let Some(value) = values.next() else {
878                return Err(encode_whole_error(IndexEncodeErrorKind::TooFewValues {
879                    expected: self.len(),
880                    actual: position,
881                }));
882            };
883            if value.kind() != component.value_kind() {
884                return Err(encode_error(
885                    IndexEncodeErrorKind::WrongValueKind {
886                        expected: component.value_kind(),
887                        actual: value.kind(),
888                    },
889                    position,
890                    component,
891                ));
892            }
893
894            match (component.wire_type(), value) {
895                (IndexWireType::Integer { allowed, .. }, value) => {
896                    let integer = integer_ref(value)
897                        .map_err(|kind| encode_error(kind, position, component))?;
898                    validate_integer_encode(
899                        allowed,
900                        integer,
901                        options.incomplete_constraints,
902                        position,
903                        component,
904                    )?;
905                    push_component(&suffix, 1, options.max_suffix_arcs, position, component)?;
906                    suffix.push(integer as u32);
907                }
908                (IndexWireType::IpAddress, IndexValueRef::IpAddress(address)) => {
909                    push_component(&suffix, 4, options.max_suffix_arcs, position, component)?;
910                    suffix.extend(address.map(u32::from));
911                }
912                (
913                    IndexWireType::Octets {
914                        framing, lengths, ..
915                    },
916                    IndexValueRef::OctetString(bytes)
917                    | IndexValueRef::Bits(bytes)
918                    | IndexValueRef::Opaque(bytes),
919                ) => encode_variable(
920                    &mut suffix,
921                    bytes.iter().copied().map(u32::from),
922                    bytes.len(),
923                    *framing,
924                    lengths,
925                    options,
926                    position,
927                    component,
928                )?,
929                (
930                    IndexWireType::ObjectIdentifier { framing, lengths },
931                    IndexValueRef::ObjectIdentifier(arcs),
932                ) => encode_variable(
933                    &mut suffix,
934                    arcs.iter().copied(),
935                    arcs.len(),
936                    *framing,
937                    lengths,
938                    options,
939                    position,
940                    component,
941                )?,
942                _ => unreachable!("value kind checked before encoding"),
943            }
944        }
945
946        if values.next().is_some() {
947            return Err(encode_whole_error(IndexEncodeErrorKind::TooManyValues {
948                expected: self.len(),
949            }));
950        }
951        Ok(IndexSuffix(suffix.into_boxed_slice()))
952    }
953}
954
955/// Object-specific binding of a reusable row schema and suffix budget.
956#[derive(Clone, Debug)]
957pub struct BoundIndexCodec {
958    schema: Arc<IndexSchema>,
959    max_suffix_arcs: usize,
960}
961
962impl BoundIndexCodec {
963    /// Binds a schema to an explicit suffix budget measured in OID arcs.
964    pub fn new(schema: Arc<IndexSchema>, max_suffix_arcs: usize) -> Result<Self, IndexBindError> {
965        if schema.minimum_suffix_arcs() > max_suffix_arcs {
966            return Err(IndexBindError::MinimumSuffixTooLong {
967                minimum: schema.minimum_suffix_arcs(),
968                maximum: max_suffix_arcs,
969            });
970        }
971        Ok(Self {
972            schema,
973            max_suffix_arcs,
974        })
975    }
976
977    /// Binds using the 128-arc complete instance-OID limit.
978    pub fn for_object_oid(
979        schema: Arc<IndexSchema>,
980        object_oid: &Oid,
981    ) -> Result<Self, IndexBindError> {
982        let Some(maximum) = MAX_INSTANCE_OID_ARCS.checked_sub(object_oid.len()) else {
983            return Err(IndexBindError::ObjectOidTooLong {
984                actual: object_oid.len(),
985                maximum: MAX_INSTANCE_OID_ARCS,
986            });
987        };
988        Self::new(schema, maximum)
989    }
990
991    /// Returns the shared schema used by this binding.
992    #[must_use]
993    pub fn schema(&self) -> &Arc<IndexSchema> {
994        &self.schema
995    }
996
997    /// Returns the maximum complete suffix length in OID arcs.
998    #[must_use]
999    pub const fn max_suffix_arcs(&self) -> usize {
1000        self.max_suffix_arcs
1001    }
1002
1003    /// Decodes with this binding's complete suffix limit.
1004    pub fn decode_exact<'schema, 'suffix>(
1005        &'schema self,
1006        suffix: &'suffix [u32],
1007        mode: ConstraintMode,
1008    ) -> Result<DecodedRowIndex<'schema, 'suffix>, IndexDecodeError<'suffix>> {
1009        self.schema.decode_exact(
1010            suffix,
1011            DecodeOptions::new(self.max_suffix_arcs).with_constraint_mode(mode),
1012        )
1013    }
1014
1015    /// Encodes with this binding's complete suffix limit and strict incomplete
1016    /// constraint handling.
1017    pub fn encode_canonical<'a>(
1018        &self,
1019        values: impl IntoIterator<Item = IndexValueRef<'a>>,
1020    ) -> Result<IndexSuffix, IndexEncodeError> {
1021        self.encode_canonical_with_incomplete_constraints(values, IncompleteConstraintMode::Reject)
1022    }
1023
1024    /// Encodes with this binding's suffix limit and an explicit policy for
1025    /// values whose validity depends on unresolved constraint metadata.
1026    pub fn encode_canonical_with_incomplete_constraints<'a>(
1027        &self,
1028        values: impl IntoIterator<Item = IndexValueRef<'a>>,
1029        mode: IncompleteConstraintMode,
1030    ) -> Result<IndexSuffix, IndexEncodeError> {
1031        self.schema.encode_canonical(
1032            values,
1033            EncodeOptions::new(self.max_suffix_arcs).with_incomplete_constraints(mode),
1034        )
1035    }
1036}
1037
1038/// Failure to bind a row schema to an object or explicit operation limit.
1039#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
1040pub enum IndexBindError {
1041    /// The object's OID already exceeds the complete instance-OID arc limit.
1042    #[error("object OID has {actual} arcs, exceeding the complete limit of {maximum}")]
1043    ObjectOidTooLong {
1044        /// Contains the object OID length in arcs.
1045        actual: usize,
1046        /// Contains the complete instance-OID limit in arcs.
1047        maximum: usize,
1048    },
1049    /// The schema's minimum suffix width exceeds the requested arc budget.
1050    #[error("schema needs at least {minimum} suffix arcs, exceeding the limit of {maximum}")]
1051    MinimumSuffixTooLong {
1052        /// Contains the schema's minimum suffix width in arcs.
1053        minimum: usize,
1054        /// Contains the available suffix budget in arcs.
1055        maximum: usize,
1056    },
1057}
1058
1059fn decode_integer(kind: IntegerIndexKind, arc: u32) -> Result<IndexValue, IndexDecodeErrorKind> {
1060    Ok(match kind {
1061        IntegerIndexKind::Integer32 => IndexValue::Integer32(
1062            i32::try_from(arc)
1063                .map_err(|_| IndexDecodeErrorKind::Integer32OutOfDomain { value: arc })?,
1064        ),
1065        IntegerIndexKind::Unsigned32 => IndexValue::Unsigned32(arc),
1066        IntegerIndexKind::Gauge32 => IndexValue::Gauge32(arc),
1067        IntegerIndexKind::TimeTicks => IndexValue::TimeTicks(arc),
1068        IntegerIndexKind::Counter32 => IndexValue::Counter32(arc),
1069    })
1070}
1071
1072fn integer_violation(allowed: &IntegerConstraint, value: i64) -> Option<IndexConstraintViolation> {
1073    if allowed.ranges().check(&value) == ConstraintCheck::Violation {
1074        Some(IndexConstraintViolation::IntegerRange { value })
1075    } else if allowed
1076        .enumeration()
1077        .is_some_and(|enumeration| enumeration.binary_search(&value).is_err())
1078    {
1079        Some(IndexConstraintViolation::IntegerEnumeration { value })
1080    } else {
1081        None
1082    }
1083}
1084
1085fn length_violation(allowed: &LengthConstraint, length: usize) -> Option<IndexConstraintViolation> {
1086    (allowed.check(&length) == ConstraintCheck::Violation)
1087        .then_some(IndexConstraintViolation::Length { length })
1088}
1089
1090#[allow(clippy::too_many_arguments)]
1091fn handle_decode_violation<'suffix>(
1092    mode: ConstraintMode,
1093    position: usize,
1094    component: &IndexComponentSchema,
1095    start: usize,
1096    violation: IndexConstraintViolation,
1097    violations: &mut Vec<ReportedIndexViolation>,
1098    values: &[IndexValue],
1099    ranges: &[Range<usize>],
1100    suffix: &'suffix [u32],
1101) -> Result<(), IndexDecodeError<'suffix>> {
1102    match mode {
1103        ConstraintMode::Enforce => Err(decode_error(
1104            IndexDecodeErrorKind::ConstraintViolation(violation),
1105            position,
1106            component,
1107            start,
1108            values,
1109            ranges,
1110            suffix,
1111        )),
1112        ConstraintMode::Report => {
1113            violations.push(ReportedIndexViolation {
1114                component_position: position,
1115                violation,
1116            });
1117            Ok(())
1118        }
1119    }
1120}
1121
1122#[allow(clippy::too_many_arguments)]
1123fn take_value_arcs<'suffix>(
1124    suffix: &'suffix [u32],
1125    start: usize,
1126    length: usize,
1127    maximum: usize,
1128    position: usize,
1129    component: &IndexComponentSchema,
1130    values: &[IndexValue],
1131    ranges: &[Range<usize>],
1132) -> Result<&'suffix [u32], IndexDecodeError<'suffix>> {
1133    if length > maximum {
1134        return Err(decode_error(
1135            IndexDecodeErrorKind::ValueTooLong {
1136                actual: length,
1137                maximum,
1138            },
1139            position,
1140            component,
1141            start,
1142            values,
1143            ranges,
1144            suffix,
1145        ));
1146    }
1147    let available = suffix.len().saturating_sub(start);
1148    if available < length {
1149        return Err(decode_error(
1150            IndexDecodeErrorKind::Truncated {
1151                needed: length,
1152                available,
1153            },
1154            position,
1155            component,
1156            start,
1157            values,
1158            ranges,
1159            suffix,
1160        ));
1161    }
1162    Ok(&suffix[start..start + length])
1163}
1164
1165#[allow(clippy::too_many_arguments)]
1166fn framed_value<'suffix>(
1167    framing: VariableFraming,
1168    suffix: &'suffix [u32],
1169    start: usize,
1170    maximum: usize,
1171    position: usize,
1172    component: &IndexComponentSchema,
1173    values: &[IndexValue],
1174    ranges: &[Range<usize>],
1175) -> Result<(usize, usize), IndexDecodeError<'suffix>> {
1176    match framing {
1177        VariableFraming::Fixed(length) => {
1178            take_value_arcs(
1179                suffix, start, length, maximum, position, component, values, ranges,
1180            )?;
1181            Ok((start, length))
1182        }
1183        VariableFraming::LengthPrefixed => {
1184            let Some(&declared) = suffix.get(start) else {
1185                return Err(decode_error(
1186                    IndexDecodeErrorKind::Truncated {
1187                        needed: 1,
1188                        available: 0,
1189                    },
1190                    position,
1191                    component,
1192                    start,
1193                    values,
1194                    ranges,
1195                    suffix,
1196                ));
1197            };
1198            let Ok(length) = usize::try_from(declared) else {
1199                return Err(decode_error(
1200                    IndexDecodeErrorKind::LengthPrefixTooLarge { declared, maximum },
1201                    position,
1202                    component,
1203                    start,
1204                    values,
1205                    ranges,
1206                    suffix,
1207                ));
1208            };
1209            if length > maximum {
1210                return Err(decode_error(
1211                    IndexDecodeErrorKind::LengthPrefixTooLarge { declared, maximum },
1212                    position,
1213                    component,
1214                    start,
1215                    values,
1216                    ranges,
1217                    suffix,
1218                ));
1219            }
1220            let data_start = start + 1;
1221            let available = suffix.len().saturating_sub(data_start);
1222            if available < length {
1223                return Err(decode_error(
1224                    IndexDecodeErrorKind::Truncated {
1225                        needed: length + 1,
1226                        available: available + 1,
1227                    },
1228                    position,
1229                    component,
1230                    start,
1231                    values,
1232                    ranges,
1233                    suffix,
1234                ));
1235            }
1236            Ok((data_start, length))
1237        }
1238        VariableFraming::Implied => {
1239            let length = suffix.len().saturating_sub(start);
1240            if length > maximum {
1241                return Err(decode_error(
1242                    IndexDecodeErrorKind::ValueTooLong {
1243                        actual: length,
1244                        maximum,
1245                    },
1246                    position,
1247                    component,
1248                    start,
1249                    values,
1250                    ranges,
1251                    suffix,
1252                ));
1253            }
1254            Ok((start, length))
1255        }
1256    }
1257}
1258
1259fn invalid_octet(arcs: &[u32]) -> Option<(usize, u32)> {
1260    arcs.iter()
1261        .copied()
1262        .enumerate()
1263        .find(|(_, arc)| *arc > u32::from(u8::MAX))
1264}
1265
1266fn decode_error<'suffix>(
1267    kind: IndexDecodeErrorKind,
1268    position: usize,
1269    component: &IndexComponentSchema,
1270    start: usize,
1271    values: &[IndexValue],
1272    ranges: &[Range<usize>],
1273    suffix: &'suffix [u32],
1274) -> IndexDecodeError<'suffix> {
1275    decode_error_at(
1276        kind, position, component, start, start, values, ranges, suffix,
1277    )
1278}
1279
1280#[allow(clippy::too_many_arguments)]
1281fn decode_error_at<'suffix>(
1282    kind: IndexDecodeErrorKind,
1283    position: usize,
1284    component: &IndexComponentSchema,
1285    offset: usize,
1286    remaining_start: usize,
1287    values: &[IndexValue],
1288    ranges: &[Range<usize>],
1289    suffix: &'suffix [u32],
1290) -> IndexDecodeError<'suffix> {
1291    IndexDecodeError {
1292        kind,
1293        component_position: Some(position),
1294        component_name: Some(component.name().to_string()),
1295        arc_offset: offset,
1296        decoded_prefix: make_decoded_prefix(values, ranges, suffix),
1297        remaining: &suffix[remaining_start..],
1298    }
1299}
1300
1301fn decode_whole_error<'suffix>(
1302    kind: IndexDecodeErrorKind,
1303    offset: usize,
1304    values: &[IndexValue],
1305    ranges: &[Range<usize>],
1306    suffix: &'suffix [u32],
1307) -> IndexDecodeError<'suffix> {
1308    IndexDecodeError {
1309        kind,
1310        component_position: None,
1311        component_name: None,
1312        arc_offset: offset,
1313        decoded_prefix: make_decoded_prefix(values, ranges, suffix),
1314        remaining: &suffix[offset..],
1315    }
1316}
1317
1318fn make_decoded_prefix<'suffix>(
1319    values: &[IndexValue],
1320    ranges: &[Range<usize>],
1321    suffix: &'suffix [u32],
1322) -> Box<[DecodedPrefixComponent<'suffix>]> {
1323    values
1324        .iter()
1325        .cloned()
1326        .zip(ranges.iter().cloned())
1327        .enumerate()
1328        .map(|(position, (value, arc_range))| DecodedPrefixComponent {
1329            position,
1330            value,
1331            raw_arcs: &suffix[arc_range.clone()],
1332            arc_range,
1333        })
1334        .collect()
1335}
1336
1337fn integer_ref(value: IndexValueRef<'_>) -> Result<i64, IndexEncodeErrorKind> {
1338    match value {
1339        IndexValueRef::Integer32(value) if value < 0 => {
1340            Err(IndexEncodeErrorKind::NegativeInteger32 { value })
1341        }
1342        IndexValueRef::Integer32(value) => Ok(i64::from(value)),
1343        IndexValueRef::Unsigned32(value)
1344        | IndexValueRef::Gauge32(value)
1345        | IndexValueRef::TimeTicks(value)
1346        | IndexValueRef::Counter32(value) => Ok(i64::from(value)),
1347        _ => unreachable!("value kind checked before integer conversion"),
1348    }
1349}
1350
1351fn validate_integer_encode(
1352    allowed: &IntegerConstraint,
1353    value: i64,
1354    incomplete: IncompleteConstraintMode,
1355    position: usize,
1356    component: &IndexComponentSchema,
1357) -> Result<(), IndexEncodeError> {
1358    if let Some(violation) = integer_violation(allowed, value) {
1359        return Err(encode_error(
1360            IndexEncodeErrorKind::ConstraintViolation(violation),
1361            position,
1362            component,
1363        ));
1364    }
1365    if allowed.check(value) == ConstraintCheck::Indeterminate
1366        && incomplete == IncompleteConstraintMode::Reject
1367    {
1368        return Err(encode_error(
1369            IndexEncodeErrorKind::IndeterminateConstraint,
1370            position,
1371            component,
1372        ));
1373    }
1374    Ok(())
1375}
1376
1377#[allow(clippy::too_many_arguments)]
1378fn encode_variable(
1379    suffix: &mut Vec<u32>,
1380    arcs: impl IntoIterator<Item = u32>,
1381    length: usize,
1382    framing: VariableFraming,
1383    lengths: &LengthConstraint,
1384    options: EncodeOptions,
1385    position: usize,
1386    component: &IndexComponentSchema,
1387) -> Result<(), IndexEncodeError> {
1388    if length > options.max_value_arcs {
1389        return Err(encode_error(
1390            IndexEncodeErrorKind::ValueTooLong {
1391                actual: length,
1392                maximum: options.max_value_arcs,
1393            },
1394            position,
1395            component,
1396        ));
1397    }
1398    if let VariableFraming::Fixed(expected) = framing
1399        && length != expected
1400    {
1401        return Err(encode_error(
1402            IndexEncodeErrorKind::FixedLength {
1403                expected,
1404                actual: length,
1405            },
1406            position,
1407            component,
1408        ));
1409    }
1410    match lengths.check(&length) {
1411        ConstraintCheck::Violation => {
1412            return Err(encode_error(
1413                IndexEncodeErrorKind::ConstraintViolation(IndexConstraintViolation::Length {
1414                    length,
1415                }),
1416                position,
1417                component,
1418            ));
1419        }
1420        ConstraintCheck::Indeterminate
1421            if options.incomplete_constraints == IncompleteConstraintMode::Reject =>
1422        {
1423            return Err(encode_error(
1424                IndexEncodeErrorKind::IndeterminateConstraint,
1425                position,
1426                component,
1427            ));
1428        }
1429        ConstraintCheck::Allowed | ConstraintCheck::Indeterminate => {}
1430    }
1431
1432    let prefix = usize::from(matches!(framing, VariableFraming::LengthPrefixed));
1433    let component_length = prefix.checked_add(length).ok_or_else(|| {
1434        encode_error(
1435            IndexEncodeErrorKind::ArithmeticOverflow,
1436            position,
1437            component,
1438        )
1439    })?;
1440    push_component(
1441        suffix,
1442        component_length,
1443        options.max_suffix_arcs,
1444        position,
1445        component,
1446    )?;
1447    if prefix != 0 {
1448        suffix.push(u32::try_from(length).map_err(|_| {
1449            encode_error(
1450                IndexEncodeErrorKind::LengthPrefixOverflow { length },
1451                position,
1452                component,
1453            )
1454        })?);
1455    }
1456    suffix.extend(arcs);
1457    Ok(())
1458}
1459
1460fn push_component(
1461    suffix: &[u32],
1462    component_length: usize,
1463    maximum: usize,
1464    position: usize,
1465    component: &IndexComponentSchema,
1466) -> Result<(), IndexEncodeError> {
1467    let actual = suffix.len().checked_add(component_length).ok_or_else(|| {
1468        encode_error(
1469            IndexEncodeErrorKind::ArithmeticOverflow,
1470            position,
1471            component,
1472        )
1473    })?;
1474    if actual > maximum {
1475        return Err(encode_error(
1476            IndexEncodeErrorKind::SuffixTooLong { actual, maximum },
1477            position,
1478            component,
1479        ));
1480    }
1481    Ok(())
1482}
1483
1484fn encode_error(
1485    kind: IndexEncodeErrorKind,
1486    position: usize,
1487    component: &IndexComponentSchema,
1488) -> IndexEncodeError {
1489    IndexEncodeError {
1490        kind,
1491        component_position: Some(position),
1492        component_name: Some(component.name().to_string()),
1493    }
1494}
1495
1496fn encode_whole_error(kind: IndexEncodeErrorKind) -> IndexEncodeError {
1497    IndexEncodeError {
1498        kind,
1499        component_position: None,
1500        component_name: None,
1501    }
1502}