Skip to main content

vest_lib/asn1/
disjoint.rs

1//! Compositional disjointness proofs for complete ASN.1 formats.
2//!
3//! Each complete ASN.1 format exposes an over-approximation of
4//! what can occur at the start of an accepted input, and one
5//! generic theorem turns disjoint start domains into `disjoint_domains`.
6//! Adding another ASN.1 format therefore needs one start-domain proof
7//! rather than pairwise proofs against every existing format.
8use super::ber::*;
9use super::modifiers::{DefaultedFmt, ImplicitlyTaggedFmt, Retaggable};
10#[cfg(verus_only)]
11use super::tag::tag_num_to_uint;
12use super::tag::{tag_num_from_uint, TagNumber};
13use super::{ASN1Fmt, AnyFmt, Class, Tag, TagFmt};
14use crate::combinators::mapped::spec::{BiMap, SpecMap, SpecMapper};
15use crate::combinators::{Alt, Choice, Const, Eof, Mapped, Named, Optional, Pair, Ref, Refined};
16use crate::core::{proof::*, spec::*};
17use vstd::prelude::*;
18
19verus! {
20
21/// The 256 possible ASN.1 identifier leading octets, split into one 64-bit word per class.
22///
23/// Within each word, bits `0..31` represent primitive identifiers and bits `32..63` represent
24/// constructed identifiers. For tag numbers `0..30`, the low five bits identify the exact tag.
25/// Bit 31 in either half is deliberately conservative: it represents every high-tag-number form
26/// (`number >= 31`) having that class and constructed bit. Consequently this common-path
27/// certificate cannot prove two different high tag numbers disjoint when they otherwise share
28/// their first identifier octet.
29#[verifier::ext_equal]
30pub ghost struct Asn1TagLeadMask {
31    pub universal: u64,
32    pub application: u64,
33    pub context_specific: u64,
34    pub private: u64,
35}
36
37/// A conservative FIRST certificate for an ASN.1 parser.
38///
39/// `accepts_empty` covers EOF-like formats. Non-empty accepted inputs must begin with an ASN.1
40/// identifier represented by `tags`.
41#[verifier::ext_equal]
42pub ghost struct Asn1StartDomain {
43    pub accepts_empty: bool,
44    pub tags: Asn1TagLeadMask,
45}
46
47#[verifier::inline]
48pub open spec fn empty_tag_lead_mask() -> Asn1TagLeadMask {
49    Asn1TagLeadMask { universal: 0, application: 0, context_specific: 0, private: 0 }
50}
51
52/// Construct a canonical, fixed-size ASN.1 FIRST certificate from its four bitmap words.
53#[verifier::inline]
54pub open spec fn asn1_start_mask(
55    accepts_empty: bool,
56    universal: u64,
57    application: u64,
58    context_specific: u64,
59    private: u64,
60) -> Asn1StartDomain {
61    Asn1StartDomain {
62        accepts_empty,
63        tags: Asn1TagLeadMask { universal, application, context_specific, private },
64    }
65}
66
67#[verifier::inline]
68pub open spec fn tag_lead_low(number: TagNumber) -> u64 {
69    let value = tag_num_to_uint(number);
70    if value < 31u64 {
71        value
72    } else {
73        31u64
74    }
75}
76
77#[verifier::inline]
78pub open spec fn tag_lead_index(tag: Tag) -> u64 {
79    let low = tag_lead_low(tag.number);
80    if tag.constructed {
81        low | 32u64
82    } else {
83        low
84    }
85}
86
87#[verifier::inline]
88pub open spec fn tag_lead_bit(tag: Tag) -> u64 {
89    1u64 << tag_lead_index(tag)
90}
91
92#[verifier::inline]
93pub open spec fn tag_lead_mask(tag: Tag) -> Asn1TagLeadMask {
94    let bit = tag_lead_bit(tag);
95    match tag.class {
96        Class::Universal => Asn1TagLeadMask {
97            universal: bit,
98            application: 0,
99            context_specific: 0,
100            private: 0,
101        },
102        Class::Application => Asn1TagLeadMask {
103            universal: 0,
104            application: bit,
105            context_specific: 0,
106            private: 0,
107        },
108        Class::ContextSpecific => Asn1TagLeadMask {
109            universal: 0,
110            application: 0,
111            context_specific: bit,
112            private: 0,
113        },
114        Class::Private => Asn1TagLeadMask {
115            universal: 0,
116            application: 0,
117            context_specific: 0,
118            private: bit,
119        },
120    }
121}
122
123#[verifier::inline]
124pub open spec fn tag_lead_masks_union(
125    left: Asn1TagLeadMask,
126    right: Asn1TagLeadMask,
127) -> Asn1TagLeadMask {
128    Asn1TagLeadMask {
129        universal: left.universal | right.universal,
130        application: left.application | right.application,
131        context_specific: left.context_specific | right.context_specific,
132        private: left.private | right.private,
133    }
134}
135
136pub open spec fn tag_lead_mask_contains(mask: Asn1TagLeadMask, tag: Tag) -> bool {
137    let bit = tag_lead_bit(tag);
138    match tag.class {
139        Class::Universal => mask.universal & bit != 0,
140        Class::Application => mask.application & bit != 0,
141        Class::ContextSpecific => mask.context_specific & bit != 0,
142        Class::Private => mask.private & bit != 0,
143    }
144}
145
146#[verifier::inline]
147pub open spec fn tag_lead_masks_disjoint(left: Asn1TagLeadMask, right: Asn1TagLeadMask) -> bool {
148    &&& left.universal & right.universal == 0
149    &&& left.application & right.application == 0
150    &&& left.context_specific & right.context_specific == 0
151    &&& left.private & right.private == 0
152}
153
154pub open spec fn asn1_start_exact(tag: Tag) -> Asn1StartDomain {
155    asn1_start_exact_uint(tag.class, tag.constructed, tag_num_to_uint(tag.number))
156}
157
158/// FIRST certificate for a tag whose number is kept in its numeric representation.
159///
160/// Generated nominal formats store their retaggable tag as `(Class, u64)`.  Keeping their
161/// public certificate in that same representation avoids repeatedly normalizing through the
162/// `TagNumber` enum merely to select one bitmap bit.
163pub open spec fn asn1_start_exact_uint(
164    class: Class,
165    constructed: bool,
166    number: u64,
167) -> Asn1StartDomain {
168    let low = if number < 31u64 {
169        number
170    } else {
171        31u64
172    };
173    let index = if constructed {
174        low | 32u64
175    } else {
176        low
177    };
178    let bit = 1u64 << index;
179    Asn1StartDomain {
180        accepts_empty: false,
181        tags: match class {
182            Class::Universal => Asn1TagLeadMask {
183                universal: bit,
184                application: 0,
185                context_specific: 0,
186                private: 0,
187            },
188            Class::Application => Asn1TagLeadMask {
189                universal: 0,
190                application: bit,
191                context_specific: 0,
192                private: 0,
193            },
194            Class::ContextSpecific => Asn1TagLeadMask {
195                universal: 0,
196                application: 0,
197                context_specific: bit,
198                private: 0,
199            },
200            Class::Private => Asn1TagLeadMask {
201                universal: 0,
202                application: 0,
203                context_specific: 0,
204                private: bit,
205            },
206        },
207    }
208}
209
210/// Numeric and `TagNumber` singleton certificates denote the same identifier lead octet.
211pub proof fn lemma_asn1_start_exact_uint(class: Class, constructed: bool, number: u64)
212    ensures
213        asn1_start_exact(Tag { class, constructed, number: tag_num_from_uint(number) })
214            == asn1_start_exact_uint(class, constructed, number),
215{
216    lemma_tag_number_roundtrip(number);
217}
218
219/// Whether two numeric tags have different identifier leading octets.
220pub open spec fn tag_leads_distinct_uint(
221    left_class: Class,
222    left_constructed: bool,
223    left_number: u64,
224    right_class: Class,
225    right_constructed: bool,
226    right_number: u64,
227) -> bool {
228    ||| left_class != right_class
229    ||| left_constructed != right_constructed
230    ||| (if left_number < 31u64 {
231        left_number
232    } else {
233        31u64
234    }) != (if right_number < 31u64 {
235        right_number
236    } else {
237        31u64
238    })
239}
240
241/// Numeric singleton certificates are disjoint exactly when their lead octets differ.
242pub broadcast proof fn lemma_asn1_starts_disjoint_exact_uint(
243    left_class: Class,
244    left_constructed: bool,
245    left_number: u64,
246    right_class: Class,
247    right_constructed: bool,
248    right_number: u64,
249)
250    ensures
251        #[trigger] asn1_starts_disjoint(
252            asn1_start_exact_uint(left_class, left_constructed, left_number),
253            asn1_start_exact_uint(right_class, right_constructed, right_number),
254        ) <==> tag_leads_distinct_uint(
255            left_class,
256            left_constructed,
257            left_number,
258            right_class,
259            right_constructed,
260            right_number,
261        ),
262{
263    let left = Tag {
264        class: left_class,
265        constructed: left_constructed,
266        number: tag_num_from_uint(left_number),
267    };
268    let right = Tag {
269        class: right_class,
270        constructed: right_constructed,
271        number: tag_num_from_uint(right_number),
272    };
273    lemma_tag_number_roundtrip(left_number);
274    lemma_tag_number_roundtrip(right_number);
275    lemma_asn1_start_exact_uint(left_class, left_constructed, left_number);
276    lemma_asn1_start_exact_uint(right_class, right_constructed, right_number);
277    lemma_asn1_starts_disjoint_exact(left, right);
278}
279
280pub open spec fn asn1_start_identity(class: Class, number: TagNumber) -> Asn1StartDomain {
281    asn1_start_identity_uint(class, tag_num_to_uint(number))
282}
283
284/// FIRST certificate for either constructed bit of a numeric tag.
285#[verifier::inline]
286pub open spec fn asn1_start_identity_uint(class: Class, number: u64) -> Asn1StartDomain {
287    asn1_start_union(
288        asn1_start_exact_uint(class, false, number),
289        asn1_start_exact_uint(class, true, number),
290    )
291}
292
293/// Numeric and `TagNumber` identity certificates denote the same two lead octets.
294pub proof fn lemma_asn1_start_identity_uint(class: Class, number: u64)
295    ensures
296        asn1_start_identity(class, tag_num_from_uint(number)) == asn1_start_identity_uint(
297            class,
298            number,
299        ),
300{
301    lemma_asn1_start_exact_uint(class, false, number);
302    lemma_asn1_start_exact_uint(class, true, number);
303}
304
305pub open spec fn asn1_start_any_non_eoc() -> Asn1StartDomain {
306    Asn1StartDomain {
307        accepts_empty: false,
308        tags: Asn1TagLeadMask {
309            universal: 0xffff_ffff_ffff_fffeu64,
310            application: 0xffff_ffff_ffff_ffffu64,
311            context_specific: 0xffff_ffff_ffff_ffffu64,
312            private: 0xffff_ffff_ffff_ffffu64,
313        },
314    }
315}
316
317pub open spec fn asn1_start_ber_boundary() -> Asn1StartDomain {
318    Asn1StartDomain { accepts_empty: true, tags: tag_lead_mask(TagFmt::EOC) }
319}
320
321pub open spec fn asn1_start_empty() -> Asn1StartDomain {
322    Asn1StartDomain { accepts_empty: true, tags: empty_tag_lead_mask() }
323}
324
325pub open spec fn asn1_start_union(
326    left: Asn1StartDomain,
327    right: Asn1StartDomain,
328) -> Asn1StartDomain {
329    Asn1StartDomain {
330        accepts_empty: left.accepts_empty || right.accepts_empty,
331        tags: tag_lead_masks_union(left.tags, right.tags),
332    }
333}
334
335/// Whether `input` has a start represented by `domain`.
336pub open spec fn input_starts_with(input: Seq<u8>, domain: Asn1StartDomain) -> bool {
337    ||| input.len() == 0 && domain.accepts_empty
338    ||| match TagFmt.spec_parse(input) {
339        Some((_n, tag)) => tag_lead_mask_contains(domain.tags, tag),
340        None => false,
341    }
342}
343
344/// A constant-size, quantifier-free sufficient test for disjoint ASN.1 FIRST domains.
345pub open spec fn asn1_starts_disjoint(left: Asn1StartDomain, right: Asn1StartDomain) -> bool {
346    &&& !(left.accepts_empty && right.accepts_empty)
347    &&& tag_lead_masks_disjoint(left.tags, right.tags)
348}
349
350/// Whether two tags have different identifier leading octets.
351///
352/// All high tag numbers deliberately have index 31 within their primitive/constructed half, so
353/// this predicate preserves the documented conservative high-tag behavior.
354pub open spec fn tag_leads_distinct(left: Tag, right: Tag) -> bool {
355    ||| left.class != right.class
356    ||| left.constructed != right.constructed
357    ||| tag_lead_low(left.number) != tag_lead_low(right.number)
358}
359
360/// Converting a numeric tag number to its canonical enum representation preserves its value.
361pub broadcast proof fn lemma_tag_number_roundtrip(number: u64)
362    ensures
363        #[trigger] tag_num_to_uint(super::tag::uint_to_tag_num(number)) == number,
364        #[trigger] tag_num_to_uint(tag_num_from_uint(number)) == number,
365{
366}
367
368proof fn lemma_word_union_contains(left: u64, right: u64, bit: u64)
369    by (bit_vector)
370    requires
371        left & bit != 0 || right & bit != 0,
372    ensures
373        (left | right) & bit != 0,
374{
375}
376
377proof fn lemma_tag_lead_union_contains(left: Asn1TagLeadMask, right: Asn1TagLeadMask, tag: Tag)
378    requires
379        tag_lead_mask_contains(left, tag) || tag_lead_mask_contains(right, tag),
380    ensures
381        tag_lead_mask_contains(tag_lead_masks_union(left, right), tag),
382{
383    let bit = tag_lead_bit(tag);
384    match tag.class {
385        Class::Universal => lemma_word_union_contains(left.universal, right.universal, bit),
386        Class::Application => lemma_word_union_contains(left.application, right.application, bit),
387        Class::ContextSpecific => {
388            lemma_word_union_contains(left.context_specific, right.context_specific, bit)
389        },
390        Class::Private => lemma_word_union_contains(left.private, right.private, bit),
391    }
392}
393
394pub proof fn lemma_input_starts_with_union(
395    input: Seq<u8>,
396    left: Asn1StartDomain,
397    right: Asn1StartDomain,
398)
399    requires
400        input_starts_with(input, left) || input_starts_with(input, right),
401    ensures
402        input_starts_with(input, asn1_start_union(left, right)),
403{
404    if input.len() != 0 {
405        if let Some((_n, tag)) = TagFmt.spec_parse(input) {
406            lemma_tag_lead_union_contains(left.tags, right.tags, tag);
407        }
408    }
409}
410
411proof fn lemma_disjoint_words_cannot_contain_same_bit(left: u64, right: u64, bit_index: u64)
412    by (bit_vector)
413    requires
414        bit_index < 64,
415        left & right == 0,
416        left & (1u64 << bit_index) != 0,
417        right & (1u64 << bit_index) != 0,
418    ensures
419        false,
420{
421}
422
423proof fn lemma_single_bits_disjoint(left_index: u64, right_index: u64)
424    by (bit_vector)
425    requires
426        left_index < 64,
427        right_index < 64,
428    ensures
429        ((1u64 << left_index) & (1u64 << right_index) == 0) <==> left_index != right_index,
430{
431}
432
433proof fn lemma_tag_lead_low_bound(number: TagNumber)
434    ensures
435        tag_lead_low(number) < 32,
436{
437}
438
439proof fn lemma_tag_lead_index_bound(tag: Tag)
440    ensures
441        tag_lead_index(tag) < 64,
442{
443    let low = tag_lead_low(tag.number);
444    lemma_tag_lead_low_bound(tag.number);
445    assert((low | 32u64) < 64u64) by (bit_vector)
446        requires
447            low < 32,
448    ;
449}
450
451proof fn lemma_tag_lead_indices_distinct(left: Tag, right: Tag)
452    ensures
453        tag_lead_index(left) != tag_lead_index(right) <==> left.constructed != right.constructed
454            || tag_lead_low(left.number) != tag_lead_low(right.number),
455{
456    let left_low = tag_lead_low(left.number);
457    let right_low = tag_lead_low(right.number);
458    lemma_tag_lead_low_bound(left.number);
459    lemma_tag_lead_low_bound(right.number);
460    if left.constructed {
461        if right.constructed {
462            assert((left_low | 32u64) != (right_low | 32u64) <==> left_low != right_low)
463                by (bit_vector)
464                requires
465                    left_low < 32,
466                    right_low < 32,
467            ;
468        } else {
469            assert((left_low | 32u64) != right_low) by (bit_vector)
470                requires
471                    left_low < 32,
472                    right_low < 32,
473            ;
474        }
475    } else if right.constructed {
476        assert(left_low != (right_low | 32u64)) by (bit_vector)
477            requires
478                left_low < 32,
479                right_low < 32,
480        ;
481    }
482}
483
484proof fn lemma_exact_tag_lead_masks_disjoint(left: Tag, right: Tag)
485    ensures
486        tag_lead_masks_disjoint(tag_lead_mask(left), tag_lead_mask(right)) <==> left.class
487            != right.class || tag_lead_bit(left) & tag_lead_bit(right) == 0,
488{
489    let left_bit = tag_lead_bit(left);
490    let right_bit = tag_lead_bit(right);
491    assert(left_bit & 0u64 == 0) by (bit_vector);
492    assert(0u64 & right_bit == 0) by (bit_vector);
493    assert(0u64 & 0u64 == 0) by (bit_vector);
494}
495
496/// Exact one-octet FIRST domains are disjoint exactly when their leading octets differ.
497pub broadcast proof fn lemma_asn1_starts_disjoint_exact(left: Tag, right: Tag)
498    ensures
499        #[trigger] asn1_starts_disjoint(asn1_start_exact(left), asn1_start_exact(right))
500            <==> tag_leads_distinct(left, right),
501{
502    lemma_tag_lead_index_bound(left);
503    lemma_tag_lead_index_bound(right);
504    lemma_tag_lead_indices_distinct(left, right);
505    lemma_single_bits_disjoint(tag_lead_index(left), tag_lead_index(right));
506    lemma_exact_tag_lead_masks_disjoint(left, right);
507}
508
509proof fn lemma_tag_lead_mask_contains_self(tag: Tag)
510    ensures
511        tag_lead_mask_contains(tag_lead_mask(tag), tag),
512{
513    lemma_tag_lead_index_bound(tag);
514    let index = tag_lead_index(tag);
515    assert((1u64 << index) & (1u64 << index) != 0) by (bit_vector)
516        requires
517            index < 64,
518    ;
519}
520
521proof fn lemma_identity_mask_contains(class: Class, number: TagNumber, tag: Tag)
522    requires
523        tag.class == class,
524        tag.number == number,
525    ensures
526        tag_lead_mask_contains(asn1_start_identity(class, number).tags, tag),
527{
528    lemma_tag_lead_mask_contains_self(tag);
529    let primitive = Tag { class, constructed: false, number };
530    let constructed = Tag { class, constructed: true, number };
531    lemma_tag_lead_union_contains(tag_lead_mask(primitive), tag_lead_mask(constructed), tag);
532}
533
534proof fn lemma_exact_input_starts_with_bitmap(input: Seq<u8>, tag: Tag)
535    requires
536        exists|n: int| TagFmt.spec_parse(input) == Some((n, tag)),
537    ensures
538        input_starts_with(input, asn1_start_exact(tag)),
539{
540    lemma_tag_lead_mask_contains_self(tag);
541}
542
543proof fn lemma_identity_input_starts_with_bitmap(input: Seq<u8>, class: Class, number: TagNumber)
544    requires
545        exists|n: int, tag: Tag|
546            TagFmt.spec_parse(input) == Some((n, tag)) && tag.class == class && tag.number
547                == number,
548    ensures
549        input_starts_with(input, asn1_start_identity(class, number)),
550{
551    let parsed = choose|parsed: (int, Tag)|
552        #![auto]
553        TagFmt.spec_parse(input) == Some(parsed) && parsed.1.class == class && parsed.1.number
554            == number;
555    lemma_identity_mask_contains(class, number, parsed.1);
556}
557
558proof fn lemma_wf_zero_tag_number_is_eoc(number: TagNumber)
559    requires
560        super::tag::tag_number_wf(number),
561        tag_num_to_uint(number) == 0,
562    ensures
563        number == TagNumber::EOC,
564{
565}
566
567proof fn lemma_any_non_eoc_mask_contains(tag: Tag)
568    requires
569        super::tag::tag_number_wf(tag.number),
570        tag != TagFmt::EOC,
571    ensures
572        tag_lead_mask_contains(asn1_start_any_non_eoc().tags, tag),
573{
574    lemma_tag_lead_index_bound(tag);
575    let index = tag_lead_index(tag);
576    if tag.class == Class::Universal {
577        if index == 0 {
578            let number = tag_num_to_uint(tag.number);
579            let low = tag_lead_low(tag.number);
580            if tag.constructed {
581                assert(index == (low | 32u64));
582                assert((low | 32u64) != 0) by (bit_vector);
583            } else if number < 31u64 {
584                assert(low == number);
585                assert(index == number);
586                lemma_wf_zero_tag_number_is_eoc(tag.number);
587                assert(tag == TagFmt::EOC);
588            } else {
589                assert(low == 31u64);
590                assert(index == 31u64);
591            }
592        }
593        assert(0xffff_ffff_ffff_fffeu64 & (1u64 << index) != 0) by (bit_vector)
594            requires
595                index < 64,
596                index != 0,
597        ;
598    } else {
599        assert(0xffff_ffff_ffff_ffffu64 & (1u64 << index) != 0) by (bit_vector)
600            requires
601                index < 64,
602        ;
603    }
604}
605
606proof fn lemma_disjoint_masks_cannot_contain_same_tag(
607    left: Asn1TagLeadMask,
608    right: Asn1TagLeadMask,
609    tag: Tag,
610)
611    requires
612        tag_lead_masks_disjoint(left, right),
613        tag_lead_mask_contains(left, tag),
614        tag_lead_mask_contains(right, tag),
615    ensures
616        false,
617{
618    lemma_tag_lead_index_bound(tag);
619    let index = tag_lead_index(tag);
620    match tag.class {
621        Class::Universal => lemma_disjoint_words_cannot_contain_same_bit(
622            left.universal,
623            right.universal,
624            index,
625        ),
626        Class::Application => lemma_disjoint_words_cannot_contain_same_bit(
627            left.application,
628            right.application,
629            index,
630        ),
631        Class::ContextSpecific => lemma_disjoint_words_cannot_contain_same_bit(
632            left.context_specific,
633            right.context_specific,
634            index,
635        ),
636        Class::Private => lemma_disjoint_words_cannot_contain_same_bit(
637            left.private,
638            right.private,
639            index,
640        ),
641    }
642}
643
644proof fn lemma_disjoint_starts_cannot_both_hold(
645    input: Seq<u8>,
646    left: Asn1StartDomain,
647    right: Asn1StartDomain,
648)
649    requires
650        asn1_starts_disjoint(left, right),
651        input_starts_with(input, left),
652        input_starts_with(input, right),
653    ensures
654        false,
655{
656    if input.len() == 0 {
657    } else if let Some((_n, tag)) = TagFmt.spec_parse(input) {
658        lemma_disjoint_masks_cannot_contain_same_tag(left.tags, right.tags, tag);
659    }
660}
661
662/// Parsers whose accepted inputs have a compositional ASN.1 start-domain description.
663pub trait HasAsn1Start: SpecParser {
664    spec fn asn1_start(&self) -> Asn1StartDomain;
665
666    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>)
667        ensures
668            self.spec_parse(input) is Some ==> input_starts_with(input, self.asn1_start()),
669    ;
670}
671
672/// A constant ASN.1 tag has the exact start domain of its required tag value.
673impl HasAsn1Start for Const<TagFmt, Tag> {
674    #[verifier::inline]
675    open spec fn asn1_start(&self) -> Asn1StartDomain {
676        asn1_start_exact_uint(self.1.class, self.1.constructed, tag_num_to_uint(self.1.number))
677    }
678
679    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
680        if self.spec_parse(input) is Some {
681            lemma_exact_input_starts_with_bitmap(input, self.1);
682        }
683    }
684}
685
686/// Ordinary definite-length TLVs have one exact outer tag.
687impl<Content: SpecCombinator, const DER: bool> HasAsn1Start for ASN1Fmt<Content, DER> {
688    #[verifier::inline]
689    open spec fn asn1_start(&self) -> Asn1StartDomain {
690        asn1_start_exact_uint(self.0.class, self.0.constructed, tag_num_to_uint(self.0.number))
691    }
692
693    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
694        if self.spec_parse(input) is Some {
695            lemma_exact_input_starts_with_bitmap(input, self.0);
696        }
697    }
698}
699
700/// BER SEQUENCE has one exact, necessarily constructed outer tag.
701impl<Content: SpecCombinator> HasAsn1Start for BerSequenceFmt<Content> {
702    #[verifier::inline]
703    open spec fn asn1_start(&self) -> Asn1StartDomain {
704        asn1_start_exact_uint(self.0.class, self.0.constructed, tag_num_to_uint(self.0.number))
705    }
706
707    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
708        if self.spec_parse(input) is Some {
709            lemma_exact_input_starts_with_bitmap(input, self.0);
710        }
711    }
712}
713
714/// BER SEQUENCE OF has one exact, necessarily constructed outer tag.
715impl<Content: SpecCombinator> HasAsn1Start for BerSequenceOfFmt<Content> {
716    #[verifier::inline]
717    open spec fn asn1_start(&self) -> Asn1StartDomain {
718        asn1_start_exact_uint(self.0.class, self.0.constructed, tag_num_to_uint(self.0.number))
719    }
720
721    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
722        if self.spec_parse(input) is Some {
723            lemma_exact_input_starts_with_bitmap(input, self.0);
724        }
725    }
726}
727
728/// Recursive BER OCTET STRING accepts primitive and constructed forms of one tag identity.
729impl<const LIMIT: usize> HasAsn1Start for BerOctetStringFmt<LIMIT> {
730    #[verifier::inline]
731    open spec fn asn1_start(&self) -> Asn1StartDomain {
732        asn1_start_identity_uint(self.0.class, tag_num_to_uint(self.0.number))
733    }
734
735    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
736        if self.spec_parse(input) is Some {
737            lemma_identity_input_starts_with_bitmap(input, self.0.class, self.0.number);
738        }
739    }
740}
741
742/// Recursive BER BIT STRING accepts primitive and constructed forms of one tag identity.
743impl<const LIMIT: usize> HasAsn1Start for BerBitStringFmt<LIMIT> {
744    #[verifier::inline]
745    open spec fn asn1_start(&self) -> Asn1StartDomain {
746        asn1_start_identity_uint(self.0.class, tag_num_to_uint(self.0.number))
747    }
748
749    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
750        if self.spec_parse(input) is Some {
751            lemma_identity_input_starts_with_bitmap(input, self.0.class, self.0.number);
752        }
753    }
754}
755
756/// BER restricted character strings inherit the primitive/constructed identity of their
757/// underlying recursive OCTET STRING.
758impl<Content: SpecCombinator, const LIMIT: usize> HasAsn1Start for BerCharStringFmt<
759    Content,
760    LIMIT,
761> {
762    #[verifier::inline]
763    open spec fn asn1_start(&self) -> Asn1StartDomain {
764        asn1_start_identity_uint(self.0.class, tag_num_to_uint(self.0.number))
765    }
766
767    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
768        if self.spec_parse(input) is Some {
769            lemma_identity_input_starts_with_bitmap(input, self.0.class, self.0.number);
770        }
771    }
772}
773
774/// Definite-length ANY accepts every complete tag except EOC.
775impl<const DER: bool> HasAsn1Start for AnyFmt<DER> {
776    #[verifier::inline]
777    open spec fn asn1_start(&self) -> Asn1StartDomain {
778        asn1_start_any_non_eoc()
779    }
780
781    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
782        if self.spec_parse(input) is Some {
783            let parsed = choose|parsed: (int, Tag)|
784                #![auto]
785                TagFmt.spec_parse(input) == Some(parsed) && parsed.1 != TagFmt::EOC;
786            TagFmt.lemma_parse_sound_value(input);
787            lemma_any_non_eoc_mask_contains(parsed.1);
788        }
789    }
790}
791
792/// Recursive BER ANY accepts every complete tag except EOC.
793impl<const LIMIT: usize> HasAsn1Start for BerAnyFmt<LIMIT> {
794    #[verifier::inline]
795    open spec fn asn1_start(&self) -> Asn1StartDomain {
796        asn1_start_any_non_eoc()
797    }
798
799    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
800        if self.spec_parse(input) is Some {
801            let parsed = choose|parsed: (int, Tag)|
802                #![auto]
803                TagFmt.spec_parse(input) == Some(parsed) && parsed.1 != TagFmt::EOC;
804            TagFmt.lemma_parse_sound_value(input);
805            lemma_any_non_eoc_mask_contains(parsed.1);
806        }
807    }
808}
809
810/// BER_END recognizes either EOF or an EOC prefix without consuming it.
811impl HasAsn1Start for BerEndFmt {
812    #[verifier::inline]
813    open spec fn asn1_start(&self) -> Asn1StartDomain {
814        asn1_start_ber_boundary()
815    }
816
817    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
818        if self.spec_parse(input) is Some && input.len() != 0 {
819            lemma_exact_input_starts_with_bitmap(input, TagFmt::EOC);
820        }
821    }
822}
823
824/// EOF accepts only the empty input.
825impl HasAsn1Start for Eof {
826    #[verifier::inline]
827    open spec fn asn1_start(&self) -> Asn1StartDomain {
828        asn1_start_empty()
829    }
830
831    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
832    }
833}
834
835/// Refinement can only narrow an accepted input domain.
836impl<Inner: HasAsn1Start, Predicate: SpecPred<Inner::PVal>> HasAsn1Start for Refined<
837    Inner,
838    Predicate,
839> {
840    #[verifier::inline]
841    open spec fn asn1_start(&self) -> Asn1StartDomain {
842        self.0.asn1_start()
843    }
844
845    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
846        self.0.lemma_parse_implies_asn1_start(input);
847    }
848}
849
850/// Semantic mapping does not change the accepted byte domain.
851impl<Inner, Mapper> HasAsn1Start for Mapped<Inner, Mapper> where
852    Inner: HasAsn1Start,
853    Mapper: SpecMapper<In = Inner::PVal>,
854 {
855    #[verifier::inline]
856    open spec fn asn1_start(&self) -> Asn1StartDomain {
857        self.inner.asn1_start()
858    }
859
860    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
861        self.inner.lemma_parse_implies_asn1_start(input);
862    }
863}
864
865/// `BiMap` mapping does not change the accepted byte domain.
866impl<Inner, Mapper, Reverse> HasAsn1Start for Mapped<Inner, BiMap<Mapper, Reverse>> where
867    Inner: HasAsn1Start,
868    Mapper: SpecMap<Input = Inner::PVal>,
869    Reverse: SpecMap<Input = Mapper::Output, Output = Mapper::Input>,
870 {
871    #[verifier::inline]
872    open spec fn asn1_start(&self) -> Asn1StartDomain {
873        self.inner.asn1_start()
874    }
875
876    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
877        self.inner.lemma_parse_implies_asn1_start(input);
878    }
879}
880
881/// Borrowing adaptation does not change the accepted byte domain.
882impl<Inner: HasAsn1Start> HasAsn1Start for Ref<Inner> {
883    #[verifier::inline]
884    open spec fn asn1_start(&self) -> Asn1StartDomain {
885        self.0.asn1_start()
886    }
887
888    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
889        self.0.lemma_parse_implies_asn1_start(input);
890    }
891}
892
893/// Diagnostic naming does not change the accepted byte domain.
894impl<Inner: HasAsn1Start> HasAsn1Start for Named<Inner> {
895    #[verifier::inline]
896    open spec fn asn1_start(&self) -> Asn1StartDomain {
897        self.1.asn1_start()
898    }
899
900    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
901        self.1.lemma_parse_implies_asn1_start(input);
902    }
903}
904
905/// IMPLICIT tagging delegates parsing and its start domain to the concretely retagged format.
906impl<Format> HasAsn1Start for ImplicitlyTaggedFmt<Format> where Format: Retaggable + HasAsn1Start {
907    #[verifier::inline]
908    open spec fn asn1_start(&self) -> Asn1StartDomain {
909        self.1.spec_retagged(self.0).asn1_start()
910    }
911
912    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
913        self.1.spec_retagged(self.0).lemma_parse_implies_asn1_start(input);
914    }
915}
916
917/// A required pair starts wherever its required left component starts.
918impl<Left: HasAsn1Start, Right: SpecParser> HasAsn1Start for Pair<Left, Right> {
919    #[verifier::inline]
920    open spec fn asn1_start(&self) -> Asn1StartDomain {
921        self.0.asn1_start()
922    }
923
924    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
925        self.0.lemma_parse_implies_asn1_start(input);
926    }
927}
928
929/// An optional field starts with either the present field or its continuation.
930impl<Field: HasAsn1Start, Rest: HasAsn1Start> HasAsn1Start for Optional<Field, Rest> {
931    #[verifier::inline]
932    open spec fn asn1_start(&self) -> Asn1StartDomain {
933        asn1_start_union(self.0.asn1_start(), self.1.asn1_start())
934    }
935
936    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
937        self.0.lemma_parse_implies_asn1_start(input);
938        self.1.lemma_parse_implies_asn1_start(input);
939        if self.spec_parse(input) is Some {
940            if self.0.spec_parse(input) is None {
941                assert(input.skip(0) == input);
942            }
943            lemma_input_starts_with_union(input, self.0.asn1_start(), self.1.asn1_start());
944        }
945    }
946}
947
948/// ASN.1 DEFAULT has the same possible starts as OPTIONAL: the field or its continuation.
949impl<Field, Rest, const DER: bool> HasAsn1Start for DefaultedFmt<
950    Field,
951    Field::PVal,
952    Rest,
953    DER,
954> where
955    Field: SpecByteLen + HasAsn1Start<PVal = Field::T>,
956    Rest: SpecByteLen + HasAsn1Start<PVal = Rest::T>,
957 {
958    #[verifier::inline]
959    open spec fn asn1_start(&self) -> Asn1StartDomain {
960        asn1_start_union(self.0.asn1_start(), self.2.asn1_start())
961    }
962
963    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
964        let fmt = super::modifiers::defaulted_fmt::<Field, Rest, DER>(self.0, self.1, self.2);
965        fmt.lemma_parse_implies_asn1_start(input);
966    }
967}
968
969/// A structural choice accepts the union of the starts accepted by either branch.
970impl<Left: HasAsn1Start, Right: HasAsn1Start> HasAsn1Start for Choice<Left, Right> {
971    #[verifier::inline]
972    open spec fn asn1_start(&self) -> Asn1StartDomain {
973        asn1_start_union(self.0.asn1_start(), self.1.asn1_start())
974    }
975
976    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
977        self.0.lemma_parse_implies_asn1_start(input);
978        self.1.lemma_parse_implies_asn1_start(input);
979        if self.spec_parse(input) is Some {
980            lemma_input_starts_with_union(input, self.0.asn1_start(), self.1.asn1_start());
981        }
982    }
983}
984
985/// Ordered alternatives have the same accepted start union as structural choices.
986impl<
987    const NONDETERMINISTIC: bool,
988    Left: HasAsn1Start,
989    Right: HasAsn1Start<PVal = Left::PVal>,
990> HasAsn1Start for Alt<Left, Right, NONDETERMINISTIC> {
991    #[verifier::inline]
992    open spec fn asn1_start(&self) -> Asn1StartDomain {
993        asn1_start_union(self.0.asn1_start(), self.1.asn1_start())
994    }
995
996    proof fn lemma_parse_implies_asn1_start(&self, input: Seq<u8>) {
997        self.0.lemma_parse_implies_asn1_start(input);
998        self.1.lemma_parse_implies_asn1_start(input);
999        if self.spec_parse(input) is Some {
1000            lemma_input_starts_with_union(input, self.0.asn1_start(), self.1.asn1_start());
1001        }
1002    }
1003}
1004
1005/// Key lemma: Disjoint ASN.1 start domains imply disjoint parser domains.
1006///
1007/// The theorem remains directly callable so generated code need not depend on quantifier-trigger
1008/// discovery. Its single directional trigger is also useful for small hand-written formats.
1009pub broadcast proof fn lemma_disjoint_asn1_starts<Left: HasAsn1Start, Right: HasAsn1Start>(
1010    left: Left,
1011    right: Right,
1012)
1013    requires
1014        asn1_starts_disjoint(left.asn1_start(), right.asn1_start()),
1015    ensures
1016        #[trigger] disjoint_domains(left, right),
1017{
1018    reveal(disjoint_domains);
1019    assert forall|input: Seq<u8>|
1020        left.spec_parse(input) is Some && right.spec_parse(input) is Some implies false by {
1021        left.lemma_parse_implies_asn1_start(input);
1022        right.lemma_parse_implies_asn1_start(input);
1023        lemma_disjoint_starts_cannot_both_hold(input, left.asn1_start(), right.asn1_start());
1024    }
1025}
1026
1027/// A defaulted field can start either at the field itself or at its continuation.
1028///
1029/// This structural rule complements the bitmap leaf rule: it lets ordinary combinator
1030/// automation reduce a DEFAULT chain without asking the SMT solver to evaluate bitwise
1031/// operations itself.
1032pub broadcast proof fn lemma_disjoint_defaulted<Parser, Field, Rest, const DER: bool>(
1033    parser: Parser,
1034    defaulted: DefaultedFmt<Field, Field::PVal, Rest, DER>,
1035) where
1036    Parser: SpecParser,
1037    Field: SpecByteLen + SpecParser<PVal = Field::T>,
1038    Rest: SpecByteLen + SpecParser<PVal = Rest::T>,
1039
1040    requires
1041        disjoint_domains(parser, defaulted.0),
1042        disjoint_domains(parser, defaulted.2),
1043    ensures
1044        #[trigger] disjoint_domains(parser, defaulted),
1045{
1046    reveal(disjoint_domains);
1047    broadcast use vstd::seq_lib::lemma_seq_skip_nothing;
1048
1049}
1050
1051/// Small ASN.1-specific automation group for handwritten backend formats. Generated schemas use
1052/// explicit local FIRST-set certificates instead of relying on global quantifier saturation.
1053pub broadcast group asn1_disjointness_lemmas {
1054    lemma_tag_number_roundtrip,
1055    lemma_asn1_starts_disjoint_exact,
1056    lemma_asn1_starts_disjoint_exact_uint,
1057    lemma_disjoint_asn1_starts,
1058    lemma_disjoint_defaulted,
1059}
1060
1061} // verus!