Skip to main content

vest_lib/asn1/
generalizedtime.rs

1//! ASN.1 GeneralizedTime values and contents format.
2use crate::core::exec::input::{InputBuf, InputSlice};
3use crate::core::exec::output::*;
4use crate::core::exec::{
5    parser::{PResult, Parser},
6    serializer::{ByteLen, PreSerializeError, Prepare, Serializer},
7    ParseError,
8};
9use crate::{
10    combinators::{
11        mapped::spec::{LosslessMapper, LossyMapper, SpecMapper},
12        Mapped, Refined, Tail,
13    },
14    core::{proof::*, spec::*},
15};
16use vstd::assert_seqs_equal;
17use vstd::prelude::*;
18use OutputBuf;
19
20use super::datetime::*;
21
22verus! {
23
24/// Logical specification of an ASN.1 GeneralizedTime value (X.680 clause 46).
25/// Consists of a calendar date (YYYYMMDD), local time of day, optional fractional
26/// seconds, and a timezone offset or Zulu UTC indicator.
27#[verifier::ext_equal]
28pub struct GeneralizedTimeSpec {
29    pub datetime: DateTime,
30    pub precision: TimePrecision,
31    pub fraction: Seq<u8>,
32    pub zone: TimeZone,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct GeneralizedTime<'a> {
37    pub datetime: DateTime,
38    pub precision: TimePrecision,
39    pub fraction: &'a [u8],
40    pub zone: TimeZone,
41}
42
43impl<'a> DeepView for GeneralizedTime<'a> {
44    type V = GeneralizedTimeSpec;
45
46    closed spec fn deep_view(&self) -> Self::V {
47        GeneralizedTimeSpec {
48            datetime: self.datetime,
49            precision: self.precision,
50            fraction: self.fraction@,
51            zone: self.zone,
52        }
53    }
54}
55
56impl GeneralizedTimeSpec {
57    /// Validates semantic well-formedness of the GeneralizedTime value.
58    /// Year must be represented using 4 digits (up to 9999).
59    pub open spec fn wf(&self) -> bool {
60        &&& datetime_wf(self.datetime)
61        &&& self.datetime.year <= 9999
62        &&& digits(self.fraction, 0, self.fraction.len() as int)
63        &&& (self.precision == TimePrecision::Hour || self.precision == TimePrecision::Minute
64            || self.precision == TimePrecision::Second)
65        &&& (self.precision != TimePrecision::Hour || (self.datetime.minute == 0
66            && self.datetime.second == 0))
67        &&& (self.precision != TimePrecision::Minute || self.datetime.second == 0)
68    }
69
70    /// Validates DER-specific restrictions for GeneralizedTime (X.690 §11.7):
71    /// - The encoding MUST terminate with 'Z' (meaning UTC only).
72    /// - The seconds element MUST always be present.
73    /// - Fractional seconds, if present, MUST omit all trailing zeros (and cannot end in '0').
74    pub open spec fn der_wf(&self) -> bool {
75        &&& self.wf()
76        &&& self.zone == TimeZone::Utc
77        &&& self.precision == TimePrecision::Second
78        &&& (self.fraction.len() == 0 || self.fraction.last() != 0x30)
79    }
80}
81
82impl<'a> GeneralizedTime<'a> {
83    pub fn new(
84        datetime: DateTime,
85        precision: TimePrecision,
86        fraction: &'a [u8],
87        zone: TimeZone,
88    ) -> (value: Self)
89        requires
90            (GeneralizedTimeSpec { datetime, precision, fraction: fraction@, zone }).wf(),
91        ensures
92            value.deep_view() == (GeneralizedTimeSpec {
93                datetime,
94                precision,
95                fraction: fraction@,
96                zone,
97            }),
98    {
99        GeneralizedTime { datetime, precision, fraction, zone }
100    }
101
102    pub fn fraction(&self) -> (fraction: &'a [u8])
103        ensures
104            fraction.deep_view() == self.deep_view().fraction,
105    {
106        self.fraction
107    }
108}
109
110/// Helper spec function to validate the prefix date-time fields (`YYYYMMDDhh[mm[ss]]`).
111pub open spec fn generalized_fields_wf(bytes: Seq<u8>, main_end: usize) -> bool {
112    &&& (main_end == 10 || main_end == 12 || main_end == 14)
113    &&& digits(bytes, 0, main_end as int)
114    &&& datetime_wf(
115        DateTime {
116            year: decimal4(bytes, 0),
117            month: decimal2(bytes, 4),
118            day: decimal2(bytes, 6),
119            hour: decimal2(bytes, 8),
120            minute: if main_end >= 12 {
121                decimal2(bytes, 10)
122            } else {
123                0
124            },
125            second: if main_end == 14 {
126                decimal2(bytes, 12)
127            } else {
128                0
129            },
130        },
131    )
132}
133
134/// Verified executable implementation of `generalized_fields_wf`.
135pub fn generalized_fields_valid(bytes: &[u8], main_end: usize) -> bool
136    requires
137        main_end <= bytes.len(),
138    returns
139        generalized_fields_wf(bytes@, main_end),
140{
141    if main_end != 10 && main_end != 12 && main_end != 14 {
142        return false;
143    }
144    if !is_digits(bytes, 0, main_end) {
145        return false;
146    }
147    let value = DateTime {
148        year: decimal_4(bytes, 0),
149        month: decimal_2(bytes, 4),
150        day: decimal_2(bytes, 6),
151        hour: decimal_2(bytes, 8),
152        minute: if main_end >= 12 {
153            decimal_2(bytes, 10)
154        } else {
155            0
156        },
157        second: if main_end == 14 {
158            decimal_2(bytes, 12)
159        } else {
160            0
161        },
162    };
163    datetime_wf(value)
164}
165
166/// Spec function validating the optional fractional-seconds element of GeneralizedTime.
167/// Under BER (X.680 46.3.a.2), either a comma (0x2c) or a full stop (0x2e) is allowed as the decimal separator.
168/// Under DER (X.690 §11.7.3–11.7.4):
169/// - The decimal separator MUST be a full stop (0x2e / '.').
170/// - Trailing zeros are forbidden in the fractional part (i.e. cannot end with ASCII '0').
171pub open spec fn generalized_fraction_wf<const DER: bool>(
172    bytes: Seq<u8>,
173    main_end: usize,
174    zone_start: usize,
175) -> bool {
176    if zone_start == main_end {
177        true
178    } else {
179        &&& main_end + 1 < zone_start
180        &&& (bytes[main_end as int] == 0x2e || (!DER && bytes[main_end as int] == 0x2c))
181        &&& digits(bytes, main_end as int + 1, zone_start as int)
182        &&& (!DER || bytes[zone_start as int - 1] != 0x30)
183    }
184}
185
186/// Verified executable implementation of `generalized_fraction_wf`.
187pub fn generalized_fraction_valid<const DER: bool>(
188    bytes: &[u8],
189    main_end: usize,
190    zone_start: usize,
191) -> bool
192    requires
193        main_end <= zone_start <= bytes.len(),
194    returns
195        generalized_fraction_wf::<DER>(bytes@, main_end, zone_start),
196{
197    if zone_start == main_end {
198        return true;
199    }
200    if main_end + 1 >= zone_start {
201        return false;
202    }
203    if bytes[main_end] != 0x2e && (DER || bytes[main_end] != 0x2c) {
204        return false;
205    }
206    is_digits(bytes, main_end + 1, zone_start) && (!DER || bytes[zone_start - 1] != 0x30)
207}
208
209/// Spec function validating the timezone suffix (Zulu or UTC offset).
210/// Under DER (X.690 §11.7.1), only Zulu ('Z') is allowed.
211/// Under BER, UTC offsets like `+hhmm`, `-hhmm`, `+hh`, or `-hh` (omitting minutes component) are allowed.
212pub open spec fn generalized_zone_wf<const DER: bool>(bytes: Seq<u8>, zone_start: usize) -> bool {
213    if DER {
214        zone_start + 1 == bytes.len() && bytes[zone_start as int] == 0x5a
215    } else {
216        ||| zone_start == bytes.len()
217        ||| zone_start + 1 == bytes.len() && bytes[zone_start as int] == 0x5a
218        ||| zone_start + 3 == bytes.len() && (bytes[zone_start as int] == 0x2b
219            || bytes[zone_start as int] == 0x2d) && digits(
220            bytes,
221            zone_start as int + 1,
222            zone_start as int + 3,
223        ) && decimal2(bytes, (zone_start as int + 1) as usize) <= 23
224        ||| zone_start + 5 == bytes.len() && (bytes[zone_start as int] == 0x2b
225            || bytes[zone_start as int] == 0x2d) && digits(
226            bytes,
227            zone_start as int + 1,
228            zone_start as int + 5,
229        ) && decimal2(bytes, (zone_start as int + 1) as usize) <= 23 && decimal2(
230            bytes,
231            (zone_start as int + 3) as usize,
232        ) <= 59
233    }
234}
235
236/// Verified executable implementation of `generalized_zone_wf`.
237pub fn generalized_zone_valid<const DER: bool>(bytes: &[u8], zone_start: usize) -> bool
238    requires
239        zone_start <= bytes.len(),
240    returns
241        generalized_zone_wf::<DER>(bytes@, zone_start),
242{
243    if DER {
244        return bytes.len() - zone_start == 1 && bytes[zone_start] == 0x5a;
245    }
246    if zone_start == bytes.len() {
247        return true;
248    }
249    if bytes.len() - zone_start == 1 {
250        return bytes[zone_start] == 0x5a;
251    }
252    if bytes[zone_start] != 0x2b && bytes[zone_start] != 0x2d {
253        return false;
254    }
255    if bytes.len() - zone_start == 3 {
256        return is_digits(bytes, zone_start + 1, zone_start + 3) && decimal_2(bytes, zone_start + 1)
257            <= 23;
258    }
259    bytes.len() - zone_start == 5 && is_digits(bytes, zone_start + 1, zone_start + 5) && decimal_2(
260        bytes,
261        zone_start + 1,
262    ) <= 23 && decimal_2(bytes, zone_start + 3) <= 59
263}
264
265pub open spec fn generalized_candidate_wf<const DER: bool>(
266    bytes: Seq<u8>,
267    main_end: usize,
268    zone_start: usize,
269) -> bool {
270    &&& main_end <= zone_start <= bytes.len()
271    &&& generalized_fields_wf(bytes, main_end)
272    &&& generalized_fraction_wf::<DER>(bytes, main_end, zone_start)
273    &&& generalized_zone_wf::<DER>(bytes, zone_start)
274}
275
276/// Verified executable implementation of `generalized_candidate_wf`.
277pub fn generalized_candidate_valid<const DER: bool>(
278    bytes: &[u8],
279    main_end: usize,
280    zone_start: usize,
281) -> bool
282    requires
283        zone_start <= bytes.len(),
284    returns
285        generalized_candidate_wf::<DER>(bytes@, main_end, zone_start),
286{
287    main_end <= zone_start && generalized_fields_valid(bytes, main_end)
288        && generalized_fraction_valid::<DER>(bytes, main_end, zone_start)
289        && generalized_zone_valid::<DER>(bytes, zone_start)
290}
291
292/// Spec function validating the overall structure of GeneralizedTime bytes.
293/// Under DER, it forces `main_end = 14` (seconds component must always be present).
294/// Under BER, it permits `main_end` to be 10 (hours), 12 (minutes), or 14 (seconds).
295pub open spec fn generalized_time_bytes_wf<const DER: bool>(bytes: Seq<u8>) -> bool {
296    let zone_start = generalized_zone_start(bytes);
297    if DER {
298        generalized_candidate_wf::<true>(bytes, 14, zone_start)
299    } else {
300        ||| generalized_candidate_wf::<false>(bytes, 10, zone_start)
301        ||| generalized_candidate_wf::<false>(bytes, 12, zone_start)
302        ||| generalized_candidate_wf::<false>(bytes, 14, zone_start)
303    }
304}
305
306/// Verified executable implementation of `generalized_time_bytes_wf`.
307pub fn generalized_time_bytes_valid<const DER: bool>(bytes: &[u8]) -> bool
308    returns
309        generalized_time_bytes_wf::<DER>(bytes@),
310{
311    let zone_start = generalized_zonestart(bytes);
312    if DER {
313        generalized_candidate_valid::<true>(bytes, 14, zone_start)
314    } else {
315        generalized_candidate_valid::<false>(bytes, 10, zone_start)
316            || generalized_candidate_valid::<false>(bytes, 12, zone_start)
317            || generalized_candidate_valid::<false>(bytes, 14, zone_start)
318    }
319}
320
321/// Spec function identifying the start index of the timezone suffix in GeneralizedTime bytes.
322/// The timezone suffix can be 'Z' (Zulu), '+hhmm', '-hhmm', '+hh', '-hh', or omitted (local time).
323pub open spec fn generalized_zone_start(bytes: Seq<u8>) -> usize {
324    let len = bytes.len();
325    if len > 0 && bytes[len - 1] == 0x5a {
326        (len - 1) as usize
327    } else if len >= 3 && (bytes[len - 3] == 0x2b || bytes[len - 3] == 0x2d) {
328        (len - 3) as usize
329    } else if len >= 5 && (bytes[len - 5] == 0x2b || bytes[len - 5] == 0x2d) {
330        (len - 5) as usize
331    } else {
332        len as usize
333    }
334}
335
336/// Verified executable implementation of `generalized_zone_start`.
337pub fn generalized_zonestart(bytes: &[u8]) -> (zone_start: usize)
338    ensures
339        zone_start <= bytes.len(),
340    returns
341        generalized_zone_start(bytes@),
342{
343    let len = bytes.len();
344    if len > 0 && bytes[len - 1] == 0x5a {
345        len - 1
346    } else if len >= 3 && (bytes[len - 3] == 0x2b || bytes[len - 3] == 0x2d) {
347        len - 3
348    } else if len >= 5 && (bytes[len - 5] == 0x2b || bytes[len - 5] == 0x2d) {
349        len - 5
350    } else {
351        len
352    }
353}
354
355/// Spec function identifying the end index of the main date-time prefix (`YYYYMMDDhh[mm[ss]]`).
356pub open spec fn generalized_main_end(bytes: Seq<u8>, zone_start: usize) -> usize {
357    if generalized_candidate_wf::<false>(bytes, 10, zone_start) {
358        10
359    } else if generalized_candidate_wf::<false>(bytes, 12, zone_start) {
360        12
361    } else {
362        14
363    }
364}
365
366/// Verified executable implementation of `generalized_main_end`.
367pub fn generalized_mainend(bytes: &[u8], zone_start: usize) -> usize
368    requires
369        generalized_time_bytes_wf::<false>(bytes@),
370        zone_start == generalized_zone_start(bytes@),
371    returns
372        generalized_main_end(bytes@, zone_start),
373{
374    if generalized_candidate_valid::<false>(bytes, 10, zone_start) {
375        10
376    } else if generalized_candidate_valid::<false>(bytes, 12, zone_start) {
377        12
378    } else {
379        14
380    }
381}
382
383/// Spec function parsing the parsed GeneralizedTime bytes into a structured `GeneralizedTimeSpec`.
384/// Handles timezone offset adjustment (UTC = local - offset) if present.
385pub open spec fn generalized_time_value(bytes: Seq<u8>) -> Option<GeneralizedTimeSpec> {
386    let zone_start = generalized_zone_start(bytes);
387    let main_end = generalized_main_end(bytes, zone_start);
388    let local = DateTime {
389        year: decimal4(bytes, 0),
390        month: decimal2(bytes, 4),
391        day: decimal2(bytes, 6),
392        hour: decimal2(bytes, 8),
393        minute: if main_end >= 12 {
394            decimal2(bytes, 10)
395        } else {
396            0
397        },
398        second: if main_end == 14 {
399            decimal2(bytes, 12)
400        } else {
401            0
402        },
403    };
404    let precision = if main_end == 10 {
405        TimePrecision::Hour
406    } else if main_end == 12 {
407        TimePrecision::Minute
408    } else {
409        TimePrecision::Second
410    };
411    let fraction = if main_end == zone_start {
412        Seq::empty()
413    } else {
414        bytes.subrange(main_end as int + 1, zone_start as int)
415    };
416    if zone_start == bytes.len() || bytes[zone_start as int] == 0x5a {
417        Some(
418            GeneralizedTimeSpec {
419                datetime: local,
420                precision,
421                fraction,
422                zone: if zone_start == bytes.len() {
423                    TimeZone::Local
424                } else {
425                    TimeZone::Utc
426                },
427            },
428        )
429    } else {
430        let offset_minute = if bytes.len() - zone_start == 5 {
431            decimal2(bytes, (zone_start as int + 3) as usize)
432        } else {
433            0
434        };
435        match normalize_offset(
436            local,
437            bytes[zone_start as int] == 0x2b,
438            decimal2(bytes, (zone_start as int + 1) as usize),
439            offset_minute,
440        ) {
441            Some(datetime) => Some(
442                GeneralizedTimeSpec { datetime, precision, fraction, zone: TimeZone::Utc },
443            ),
444            None => None,
445        }
446    }
447}
448
449/// Verified executable implementation of `generalized_time_value`.
450/// Decodes the byte sequence into a `GeneralizedTime`.
451#[verifier::rlimit(20)]
452pub fn generalized_timevalue<'a>(bytes: &'a [u8]) -> (res: Option<GeneralizedTime<'a>>)
453    requires
454        generalized_time_bytes_wf::<false>(bytes@),
455    ensures
456        res.deep_view() == generalized_time_value(bytes@),
457{
458    let zone_start = generalized_zonestart(bytes);
459    let main_end = generalized_mainend(bytes, zone_start);
460    let local = DateTime {
461        year: decimal_4(bytes, 0),
462        month: decimal_2(bytes, 4),
463        day: decimal_2(bytes, 6),
464        hour: decimal_2(bytes, 8),
465        minute: if main_end >= 12 {
466            decimal_2(bytes, 10)
467        } else {
468            0
469        },
470        second: if main_end == 14 {
471            decimal_2(bytes, 12)
472        } else {
473            0
474        },
475    };
476    let precision = if main_end == 10 {
477        TimePrecision::Hour
478    } else if main_end == 12 {
479        TimePrecision::Minute
480    } else {
481        TimePrecision::Second
482    };
483    let fraction = if main_end == zone_start {
484        &bytes[main_end..main_end]
485    } else {
486        &bytes[main_end + 1..zone_start]
487    };
488    if zone_start == bytes.len() || bytes[zone_start] == 0x5a {
489        Some(
490            GeneralizedTime {
491                datetime: local,
492                precision,
493                fraction,
494                zone: if zone_start == bytes.len() {
495                    TimeZone::Local
496                } else {
497                    TimeZone::Utc
498                },
499            },
500        )
501    } else {
502        let offset_minute = if bytes.len() - zone_start == 5 {
503            decimal_2(bytes, zone_start + 3)
504        } else {
505            0
506        };
507        match normalize_offset(
508            local,
509            bytes[zone_start] == 0x2b,
510            decimal_2(bytes, zone_start + 1),
511            offset_minute,
512        ) {
513            Some(datetime) => Some(
514                GeneralizedTime { datetime, precision, fraction, zone: TimeZone::Utc },
515            ),
516            None => None,
517        }
518    }
519}
520
521/// Spec function validating full well-formedness of GeneralizedTime bytes.
522pub open spec fn generalized_time_wf<const DER: bool>(bytes: Seq<u8>) -> bool {
523    &&& generalized_time_bytes_wf::<DER>(bytes)
524    &&& generalized_time_value(bytes) matches Some(value) ==> value.wf()
525    &&& generalized_time_value(bytes).is_some()
526    &&& bytes.len() <= usize::MAX
527}
528
529/// Verified executable implementation of `generalized_time_wf`.
530pub fn generalized_time_valid<const DER: bool>(bytes: &[u8]) -> bool
531    returns
532        generalized_time_wf::<DER>(bytes@),
533{
534    assert(bytes@.len() == bytes.len());
535    assert(bytes@.len() <= usize::MAX);
536    if !generalized_time_bytes_valid::<DER>(bytes) {
537        return false;
538    }
539    match generalized_timevalue(bytes) {
540        Some(value) => generalized_value_wf::<false>(&value),
541        None => false,
542    }
543}
544
545pub open spec fn generalized_time_prefix(value: GeneralizedTimeSpec) -> Seq<u8> {
546    decimal4_bytes(value.datetime.year)@ + decimal2_bytes(value.datetime.month)@ + decimal2_bytes(
547        value.datetime.day,
548    )@ + decimal2_bytes(value.datetime.hour)@ + if value.precision == TimePrecision::Hour {
549        Seq::empty()
550    } else {
551        decimal2_bytes(value.datetime.minute)@ + if value.precision == TimePrecision::Second {
552            decimal2_bytes(value.datetime.second)@
553        } else {
554            Seq::empty()
555        }
556    }
557}
558
559pub open spec fn generalized_time_fraction(value: GeneralizedTimeSpec) -> Seq<u8> {
560    if value.fraction.len() == 0 {
561        Seq::empty()
562    } else {
563        seq![0x2eu8] + value.fraction
564    }
565}
566
567pub open spec fn generalized_time_suffix(value: GeneralizedTimeSpec) -> Seq<u8> {
568    if value.zone == TimeZone::Utc {
569        seq![0x5au8]
570    } else {
571        Seq::empty()
572    }
573}
574
575pub open spec fn generalized_time_bytes(value: GeneralizedTimeSpec) -> Seq<u8> {
576    generalized_time_prefix(value) + generalized_time_fraction(value) + generalized_time_suffix(
577        value,
578    )
579}
580
581pub(crate) fn generalized_time_der_prefix_bytes<'a>(value: &GeneralizedTime<'a>) -> (bytes:
582    [u8; 14])
583    requires
584        value.deep_view().der_wf(),
585    ensures
586        bytes@ == generalized_time_prefix(value.deep_view()),
587{
588    let year = decimal4_bytes(value.datetime.year);
589    let month = decimal2_bytes(value.datetime.month);
590    let day = decimal2_bytes(value.datetime.day);
591    let hour = decimal2_bytes(value.datetime.hour);
592    let minute = decimal2_bytes(value.datetime.minute);
593    let second = decimal2_bytes(value.datetime.second);
594    [
595        year[0],
596        year[1],
597        year[2],
598        year[3],
599        month[0],
600        month[1],
601        day[0],
602        day[1],
603        hour[0],
604        hour[1],
605        minute[0],
606        minute[1],
607        second[0],
608        second[1],
609    ]
610}
611
612/// Writes a `GeneralizedTime` directly to an output buffer without allocating.
613pub fn generalized_time_to_bytes<'a, Output: OutputBuf>(
614    value: &GeneralizedTime<'a>,
615    obuf: &mut Output,
616)
617    requires
618        value.deep_view().wf(),
619        old(obuf).fits(generalized_time_bytes(value.deep_view()).len()),
620    ensures
621        final(obuf)@ == old(obuf)@ + generalized_time_bytes(value.deep_view()),
622        forall|n|
623            old(obuf).fits(generalized_time_bytes(value.deep_view()).len() + n)
624                <==> final(obuf).fits(n),
625        old(obuf).same_destination(final(obuf)),
626{
627    broadcast use crate::core::exec::output::outbuf_lemmas;
628
629    let year = decimal4_bytes(value.datetime.year);
630    let month = decimal2_bytes(value.datetime.month);
631    let day = decimal2_bytes(value.datetime.day);
632    let hour = decimal2_bytes(value.datetime.hour);
633    obuf.write_bytes(&year);
634    obuf.write_bytes(&month);
635    obuf.write_bytes(&day);
636    obuf.write_bytes(&hour);
637    if value.precision != TimePrecision::Hour {
638        let minute = decimal2_bytes(value.datetime.minute);
639        obuf.write_bytes(&minute);
640        if value.precision == TimePrecision::Second {
641            let second = decimal2_bytes(value.datetime.second);
642            obuf.write_bytes(&second);
643        }
644    }
645    if value.fraction.len() > 0 {
646        obuf.write_byte(0x2e);
647        obuf.write_bytes(value.fraction);
648    }
649    if value.zone == TimeZone::Utc {
650        obuf.write_byte(0x5a);
651    }
652}
653
654/// Executable view validation helper checking well-formedness of `GeneralizedTime`.
655#[verifier::allow_in_spec]
656pub fn generalized_value_wf<'a, const DER: bool>(value: &GeneralizedTime<'a>) -> bool
657    returns
658        if DER {
659            value.deep_view().der_wf()
660        } else {
661            value.deep_view().wf()
662        },
663{
664    let base = datetime_wf(value.datetime) && value.datetime.year <= 9999 && is_digits(
665        value.fraction,
666        0,
667        value.fraction.len(),
668    ) && (value.precision == TimePrecision::Hour || value.precision == TimePrecision::Minute
669        || value.precision == TimePrecision::Second) && (value.precision != TimePrecision::Hour || (
670    value.datetime.minute == 0 && value.datetime.second == 0)) && (value.precision
671        != TimePrecision::Minute || value.datetime.second == 0);
672    base && (!DER || (value.zone == TimeZone::Utc && value.precision == TimePrecision::Second && (
673    value.fraction.len() == 0 || value.fraction[value.fraction.len() - 1] != 0x30)))
674}
675
676pub open spec fn generalized_time_len(value: GeneralizedTimeSpec) -> nat {
677    10 as nat + if value.precision == TimePrecision::Hour {
678        0 as nat
679    } else {
680        2 as nat
681    } + if value.precision == TimePrecision::Second {
682        2 as nat
683    } else {
684        0 as nat
685    } + if value.fraction.len() == 0 {
686        0 as nat
687    } else {
688        1 + value.fraction.len()
689    } + if value.zone == TimeZone::Utc {
690        1 as nat
691    } else {
692        0 as nat
693    }
694}
695
696spec fn generalized_time_main_end(value: GeneralizedTimeSpec) -> usize {
697    if value.precision == TimePrecision::Hour {
698        10
699    } else if value.precision == TimePrecision::Minute {
700        12
701    } else {
702        14
703    }
704}
705
706spec fn generalized_time_zone_start(value: GeneralizedTimeSpec) -> usize {
707    (generalized_time_main_end(value) + generalized_time_fraction(value).len()) as usize
708}
709
710// Keep sequence-heavy reasoning in separate solver queries and export only decoded facts.
711#[verifier::rlimit(20)]
712proof fn lemma_generalized_time_encoded_prefix(value: GeneralizedTimeSpec)
713    requires
714        value.wf(),
715        generalized_time_len(value) <= usize::MAX,
716    ensures
717        generalized_time_prefix(value).len() == generalized_time_main_end(value),
718        generalized_fields_wf(generalized_time_bytes(value), generalized_time_main_end(value)),
719        decimal4(generalized_time_bytes(value), 0) == value.datetime.year,
720        decimal2(generalized_time_bytes(value), 4) == value.datetime.month,
721        decimal2(generalized_time_bytes(value), 6) == value.datetime.day,
722        decimal2(generalized_time_bytes(value), 8) == value.datetime.hour,
723        value.precision != TimePrecision::Hour ==> decimal2(generalized_time_bytes(value), 10)
724            == value.datetime.minute,
725        value.precision == TimePrecision::Second ==> decimal2(generalized_time_bytes(value), 12)
726            == value.datetime.second,
727{
728    lemma_decimal4_roundtrip(value.datetime.year);
729    lemma_decimal2_roundtrip(value.datetime.month);
730    lemma_decimal2_roundtrip(value.datetime.day);
731    lemma_decimal2_roundtrip(value.datetime.hour);
732    lemma_decimal2_roundtrip(value.datetime.minute);
733    lemma_decimal2_roundtrip(value.datetime.second);
734}
735
736#[verifier::rlimit(20)]
737proof fn lemma_generalized_time_encoded_fraction<const DER: bool>(value: GeneralizedTimeSpec)
738    requires
739        if DER {
740            value.der_wf()
741        } else {
742            value.wf()
743        },
744        generalized_time_len(value) <= usize::MAX,
745    ensures
746        generalized_fraction_wf::<DER>(
747            generalized_time_bytes(value),
748            generalized_time_main_end(value),
749            generalized_time_zone_start(value),
750        ),
751        if generalized_time_main_end(value) == generalized_time_zone_start(value) {
752            value.fraction.len() == 0
753        } else {
754            generalized_time_bytes(value).subrange(
755                generalized_time_main_end(value) as int + 1,
756                generalized_time_zone_start(value) as int,
757            ) == value.fraction
758        },
759{
760    let prefix = generalized_time_prefix(value);
761    let fraction = generalized_time_fraction(value);
762    let suffix = generalized_time_suffix(value);
763    let bytes = prefix + fraction + suffix;
764    let main_end = generalized_time_main_end(value);
765    let zone_start: usize = (main_end + fraction.len()) as usize;
766
767    if value.fraction.len() == 0 {
768    } else {
769        assert(fraction == seq![0x2eu8] + value.fraction);
770        assert_seqs_equal!(bytes.subrange(main_end as int + 1, zone_start as int) == value.fraction, i => {
771            assert(bytes[main_end as int + 1 + i] == fraction[1 + i]);
772            assert(fraction[1 + i] == value.fraction[i]);
773        });
774        assert(digits(bytes, main_end as int + 1, zone_start as int)) by {
775            assert forall|i: int| main_end <= i < zone_start - 1 implies ascii_digit(
776                #[trigger] bytes[i + 1],
777            ) by {
778                assert(bytes[i + 1] == value.fraction[i - main_end]);
779            }
780        }
781        assert(generalized_fraction_wf::<DER>(bytes, main_end, zone_start));
782    }
783}
784
785#[verifier::rlimit(20)]
786proof fn lemma_generalized_time_encoded_zone<const DER: bool>(value: GeneralizedTimeSpec)
787    requires
788        if DER {
789            value.der_wf()
790        } else {
791            value.wf()
792        },
793        generalized_time_len(value) <= usize::MAX,
794    ensures
795        generalized_zone_start(generalized_time_bytes(value)) == generalized_time_zone_start(value),
796        generalized_zone_wf::<DER>(
797            generalized_time_bytes(value),
798            generalized_time_zone_start(value),
799        ),
800        (generalized_time_zone_start(value) == generalized_time_bytes(value).len()) <==> value.zone
801            == TimeZone::Local,
802        value.zone == TimeZone::Utc ==> generalized_time_bytes(value)[generalized_time_zone_start(
803            value,
804        ) as int] == 0x5a,
805{
806}
807
808#[verifier::rlimit(20)]
809proof fn lemma_generalized_time_encoded_layout<const DER: bool>(value: GeneralizedTimeSpec)
810    requires
811        if DER {
812            value.der_wf()
813        } else {
814            value.wf()
815        },
816        generalized_time_len(value) <= usize::MAX,
817    ensures
818        generalized_time_bytes_wf::<DER>(generalized_time_bytes(value)),
819        generalized_main_end(generalized_time_bytes(value), generalized_time_zone_start(value))
820            == generalized_time_main_end(value),
821{
822    lemma_generalized_time_encoded_prefix(value);
823    lemma_generalized_time_encoded_fraction::<DER>(value);
824    lemma_generalized_time_encoded_zone::<DER>(value);
825}
826
827#[verifier::rlimit(20)]
828pub proof fn lemma_generalized_time_encode_roundtrip<const DER: bool>(value: GeneralizedTimeSpec)
829    requires
830        if DER {
831            value.der_wf()
832        } else {
833            value.wf()
834        },
835        generalized_time_len(value) <= usize::MAX,
836    ensures
837        generalized_time_wf::<DER>(generalized_time_bytes(value)),
838        generalized_time_value(generalized_time_bytes(value)) == Some(value),
839        generalized_time_bytes(value).len() == generalized_time_len(value),
840{
841    lemma_generalized_time_encoded_layout::<DER>(value);
842    lemma_generalized_time_encoded_prefix(value);
843    lemma_generalized_time_encoded_fraction::<DER>(value);
844    lemma_generalized_time_encoded_zone::<DER>(value);
845}
846
847#[verifier::rlimit(100)]
848pub proof fn lemma_der_generalized_time_canonical(bytes: Seq<u8>)
849    requires
850        generalized_time_wf::<true>(bytes),
851    ensures
852        generalized_time_bytes(generalized_time_value(bytes).unwrap()) == bytes,
853{
854    lemma_decimal4_canonical(bytes, 0);
855    lemma_decimal2_canonical(bytes, 4);
856    lemma_decimal2_canonical(bytes, 6);
857    lemma_decimal2_canonical(bytes, 8);
858    lemma_decimal2_canonical(bytes, 10);
859    lemma_decimal2_canonical(bytes, 12);
860}
861
862mod derived_specs {
863    use super::*;
864
865    impl<const DER: bool> SpecParser for super::super::GeneralizedTimeFmt<DER> {
866        type PVal = GeneralizedTimeSpec;
867
868        open spec fn spec_parse(&self, ibuf: Seq<u8>) -> Option<(int, Self::PVal)> {
869            if generalized_time_wf::<DER>(ibuf) {
870                Some((ibuf.len() as int, generalized_time_value(ibuf).unwrap()))
871            } else {
872                None
873            }
874        }
875    }
876
877    impl<const DER: bool> Consistency for super::super::GeneralizedTimeFmt<DER> {
878        type Val = GeneralizedTimeSpec;
879
880        open spec fn consistent(&self, value: Self::Val) -> bool {
881            (if DER {
882                value.der_wf()
883            } else {
884                value.wf()
885            }) && generalized_time_len(value) <= usize::MAX
886        }
887    }
888
889    impl<const DER: bool> SpecSerializerDps for super::super::GeneralizedTimeFmt<DER> {
890        type SValue = GeneralizedTimeSpec;
891
892        open spec fn spec_serialize_dps(&self, value: Self::SValue, obuf: Seq<u8>) -> Seq<u8> {
893            generalized_time_bytes(value)
894        }
895    }
896
897    impl<const DER: bool> SpecSerializer for super::super::GeneralizedTimeFmt<DER> {
898        type SVal = GeneralizedTimeSpec;
899
900        open spec fn spec_serialize(&self, value: Self::SVal) -> Seq<u8> {
901            generalized_time_bytes(value)
902        }
903    }
904
905    impl<const DER: bool> SpecByteLen for super::super::GeneralizedTimeFmt<DER> {
906        type T = GeneralizedTimeSpec;
907
908        open spec fn byte_len(&self, value: Self::T) -> nat {
909            generalized_time_len(value)
910        }
911    }
912
913}
914
915pub(crate) proof fn lemma_der_generalized_time_model(value: GeneralizedTimeSpec)
916    requires
917        super::GeneralizedTimeFmt::<true>.consistent(value),
918    ensures
919        value.der_wf(),
920        super::GeneralizedTimeFmt::<true>.spec_serialize(value) == generalized_time_prefix(value)
921            + generalized_time_fraction(value) + generalized_time_suffix(value),
922        super::GeneralizedTimeFmt::<true>.spec_serialize(value).len()
923            == super::GeneralizedTimeFmt::<true>.byte_len(value),
924        super::GeneralizedTimeFmt::<true>.spec_serialize(value).len() <= usize::MAX,
925        generalized_time_prefix(value).len() == 14,
926        generalized_time_suffix(value) == seq![0x5au8],
927{
928}
929
930pub(crate) proof fn lemma_der_generalized_time_layout(value: GeneralizedTimeSpec, pos: usize)
931    requires
932        super::GeneralizedTimeFmt::<true>.consistent(value),
933    ensures
934        super::GeneralizedTimeFmt::<true>.spec_serialize(value).len() == if value.fraction.len()
935            == 0 {
936            15
937        } else {
938            value.fraction.len() + 16
939        },
940        pos < super::GeneralizedTimeFmt::<true>.spec_serialize(value).len() ==> {
941            super::GeneralizedTimeFmt::<true>.spec_serialize(value)[pos as int] == if pos < 14 {
942                generalized_time_prefix(value)[pos as int]
943            } else if value.fraction.len() == 0 {
944                0x5au8
945            } else if pos == 14 {
946                0x2eu8
947            } else if (pos as nat) < value.fraction.len() + 15 {
948                value.fraction[pos as int - 15]
949            } else {
950                0x5au8
951            }
952        },
953{
954    lemma_der_generalized_time_model(value);
955}
956
957mod derived_proofs {
958    use super::*;
959
960    impl<const DER: bool> SafeParser for super::super::GeneralizedTimeFmt<DER> {
961        proof fn lemma_parse_safe(&self, ibuf: Seq<u8>) {
962        }
963    }
964
965    impl<const DER: bool> Productive for super::super::GeneralizedTimeFmt<DER> {
966        proof fn lemma_productive(&self, ibuf: Seq<u8>) {
967            if let Some((n, _)) = self.spec_parse(ibuf) {
968                assert(n == ibuf.len());
969                assert(n >= 10);
970            }
971        }
972    }
973
974    impl<const DER: bool> GoodSerializer for super::super::GeneralizedTimeFmt<DER> {
975        proof fn lemma_serialize_len(&self, value: Self::SVal) {
976        }
977    }
978
979    impl<const DER: bool> EquivSerializers for super::super::GeneralizedTimeFmt<DER> {
980        proof fn lemma_serialize_equiv_on_empty(&self, value: Self::SVal) {
981        }
982    }
983
984    impl<const DER: bool> SPRoundTripDps for super::super::GeneralizedTimeFmt<DER> {
985        proof fn theorem_serialize_dps_parse_roundtrip(&self, value: Self::T, obuf: Seq<u8>) {
986            lemma_generalized_time_encode_roundtrip::<DER>(value);
987        }
988    }
989
990    impl SoundParser for super::super::GeneralizedTimeFmt<true> {
991        proof fn lemma_parse_sound_consumption(&self, ibuf: Seq<u8>) {
992            if let Some((n, value)) = self.spec_parse(ibuf) {
993                lemma_der_generalized_time_canonical(ibuf);
994                assert(generalized_time_bytes(value) == ibuf);
995            }
996        }
997
998        proof fn lemma_parse_sound_value(&self, ibuf: Seq<u8>) {
999            if let Some((_, value)) = self.spec_parse(ibuf) {
1000                lemma_der_generalized_time_canonical(ibuf);
1001                assert(value.der_wf());
1002                assert(generalized_time_len(value) <= usize::MAX);
1003            }
1004        }
1005    }
1006
1007    impl NonMalleable for super::super::GeneralizedTimeFmt<true> {
1008        proof fn lemma_parse_non_malleable(&self, buf1: Seq<u8>, buf2: Seq<u8>) {
1009            if let (Some((n1, value1)), Some((n2, value2))) = (
1010                self.spec_parse(buf1),
1011                self.spec_parse(buf2),
1012            ) {
1013                lemma_der_generalized_time_canonical(buf1);
1014                lemma_der_generalized_time_canonical(buf2);
1015                if value1 == value2 {
1016                    assert(buf1 == generalized_time_bytes(value1));
1017                    assert(buf2 == generalized_time_bytes(value2));
1018                    assert(buf1.take(n1) == buf1);
1019                    assert(buf2.take(n2) == buf2);
1020                }
1021            }
1022        }
1023    }
1024
1025}
1026
1027impl<'i, const DER: bool> Parser<&'i [u8]> for super::GeneralizedTimeFmt<DER> {
1028    type PT = GeneralizedTime<'i>;
1029
1030    fn parse(&self, ibuf: &&'i [u8]) -> PResult<Self::PT> {
1031        let (n, bytes) = Tail.parse(ibuf)?;
1032        assert(bytes@ == bytes.deep_view());
1033        if !generalized_time_valid::<DER>(bytes) {
1034            return Err(ParseError::custom("Invalid GeneralizedTime"));
1035        }
1036        match generalized_timevalue(bytes) {
1037            Some(value) => Ok((n, value)),
1038            None => Err(ParseError::custom("GeneralizedTime offset out of range")),
1039        }
1040    }
1041}
1042
1043impl<Output: OutputBuf, 'i, const DER: bool> Serializer<
1044    Output,
1045    GeneralizedTime<'i>,
1046> for super::GeneralizedTimeFmt<DER> {
1047    fn serialize_into(&self, value: &GeneralizedTime<'i>, obuf: &mut Output) {
1048        generalized_time_to_bytes(value, obuf);
1049    }
1050}
1051
1052impl<'i, const DER: bool> Prepare<GeneralizedTime<'i>> for super::GeneralizedTimeFmt<DER> {
1053    fn prepare(&self, value: &GeneralizedTime<'i>) -> Result<usize, PreSerializeError> {
1054        if !generalized_value_wf::<DER>(value) {
1055            return Err(PreSerializeError::custom("Invalid GeneralizedTime value"));
1056        }
1057        if value.fraction.len() > usize::MAX - 16 {
1058            return Err(PreSerializeError::length_too_large());
1059        }
1060        Ok(
1061            10 + if value.precision == TimePrecision::Hour {
1062                0
1063            } else {
1064                2
1065            } + if value.precision == TimePrecision::Second {
1066                2
1067            } else {
1068                0
1069            } + if value.fraction.len() == 0 {
1070                0
1071            } else {
1072                1 + value.fraction.len()
1073            } + if value.zone == TimeZone::Utc {
1074                1
1075            } else {
1076                0
1077            },
1078        )
1079    }
1080}
1081
1082impl<'i, const DER: bool> ByteLen<GeneralizedTime<'i>> for super::GeneralizedTimeFmt<DER> {
1083    fn length(&self, value: &GeneralizedTime<'i>) -> usize {
1084        10 + if value.precision == TimePrecision::Hour {
1085            0
1086        } else {
1087            2
1088        } + if value.precision == TimePrecision::Second {
1089            2
1090        } else {
1091            0
1092        } + if value.fraction.len() == 0 {
1093            0
1094        } else {
1095            1 + value.fraction.len()
1096        } + if value.zone == TimeZone::Utc {
1097            1
1098        } else {
1099            0
1100        }
1101    }
1102}
1103
1104} // verus!