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[main_end + 1..zone_start]
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)[generalized_time_main_end(value)
755                + 1..generalized_time_zone_start(value)] == value.fraction
756        },
757{
758    let prefix = generalized_time_prefix(value);
759    let fraction = generalized_time_fraction(value);
760    let suffix = generalized_time_suffix(value);
761    let bytes = prefix + fraction + suffix;
762    let main_end = generalized_time_main_end(value);
763    let zone_start: usize = (main_end + fraction.len()) as usize;
764
765    if value.fraction.len() == 0 {
766    } else {
767        assert(fraction == seq![0x2eu8] + value.fraction);
768        assert_seqs_equal!(bytes[main_end + 1.. zone_start] == value.fraction, i => {
769            assert(bytes[main_end as int + 1 + i] == fraction[1 + i]);
770            assert(fraction[1 + i] == value.fraction[i]);
771        });
772        assert(digits(bytes, main_end as int + 1, zone_start as int)) by {
773            assert forall|i: int| main_end <= i < zone_start - 1 implies ascii_digit(
774                #[trigger] bytes[i + 1],
775            ) by {
776                assert(bytes[i + 1] == value.fraction[i - main_end]);
777            }
778        }
779        assert(generalized_fraction_wf::<DER>(bytes, main_end, zone_start));
780    }
781}
782
783#[verifier::rlimit(20)]
784proof fn lemma_generalized_time_encoded_zone<const DER: bool>(value: GeneralizedTimeSpec)
785    requires
786        if DER {
787            value.der_wf()
788        } else {
789            value.wf()
790        },
791        generalized_time_len(value) <= usize::MAX,
792    ensures
793        generalized_zone_start(generalized_time_bytes(value)) == generalized_time_zone_start(value),
794        generalized_zone_wf::<DER>(
795            generalized_time_bytes(value),
796            generalized_time_zone_start(value),
797        ),
798        (generalized_time_zone_start(value) == generalized_time_bytes(value).len()) <==> value.zone
799            == TimeZone::Local,
800        value.zone == TimeZone::Utc ==> generalized_time_bytes(value)[generalized_time_zone_start(
801            value,
802        ) as int] == 0x5a,
803{
804}
805
806#[verifier::rlimit(20)]
807proof fn lemma_generalized_time_encoded_layout<const DER: bool>(value: GeneralizedTimeSpec)
808    requires
809        if DER {
810            value.der_wf()
811        } else {
812            value.wf()
813        },
814        generalized_time_len(value) <= usize::MAX,
815    ensures
816        generalized_time_bytes_wf::<DER>(generalized_time_bytes(value)),
817        generalized_main_end(generalized_time_bytes(value), generalized_time_zone_start(value))
818            == generalized_time_main_end(value),
819{
820    lemma_generalized_time_encoded_prefix(value);
821    lemma_generalized_time_encoded_fraction::<DER>(value);
822    lemma_generalized_time_encoded_zone::<DER>(value);
823}
824
825#[verifier::rlimit(20)]
826pub proof fn lemma_generalized_time_encode_roundtrip<const DER: bool>(value: GeneralizedTimeSpec)
827    requires
828        if DER {
829            value.der_wf()
830        } else {
831            value.wf()
832        },
833        generalized_time_len(value) <= usize::MAX,
834    ensures
835        generalized_time_wf::<DER>(generalized_time_bytes(value)),
836        generalized_time_value(generalized_time_bytes(value)) == Some(value),
837        generalized_time_bytes(value).len() == generalized_time_len(value),
838{
839    lemma_generalized_time_encoded_layout::<DER>(value);
840    lemma_generalized_time_encoded_prefix(value);
841    lemma_generalized_time_encoded_fraction::<DER>(value);
842    lemma_generalized_time_encoded_zone::<DER>(value);
843}
844
845#[verifier::rlimit(100)]
846pub proof fn lemma_der_generalized_time_canonical(bytes: Seq<u8>)
847    requires
848        generalized_time_wf::<true>(bytes),
849    ensures
850        generalized_time_bytes(generalized_time_value(bytes).unwrap()) == bytes,
851{
852    lemma_decimal4_canonical(bytes, 0);
853    lemma_decimal2_canonical(bytes, 4);
854    lemma_decimal2_canonical(bytes, 6);
855    lemma_decimal2_canonical(bytes, 8);
856    lemma_decimal2_canonical(bytes, 10);
857    lemma_decimal2_canonical(bytes, 12);
858}
859
860mod derived_specs {
861    use super::*;
862
863    impl<const DER: bool> SpecParser for super::super::GeneralizedTimeFmt<DER> {
864        type PVal = GeneralizedTimeSpec;
865
866        open spec fn spec_parse(&self, ibuf: Seq<u8>) -> Option<(int, Self::PVal)> {
867            if generalized_time_wf::<DER>(ibuf) {
868                Some((ibuf.len() as int, generalized_time_value(ibuf).unwrap()))
869            } else {
870                None
871            }
872        }
873    }
874
875    impl<const DER: bool> Consistency for super::super::GeneralizedTimeFmt<DER> {
876        type Val = GeneralizedTimeSpec;
877
878        open spec fn consistent(&self, value: Self::Val) -> bool {
879            (if DER {
880                value.der_wf()
881            } else {
882                value.wf()
883            }) && generalized_time_len(value) <= usize::MAX
884        }
885    }
886
887    impl<const DER: bool> SpecSerializerDps for super::super::GeneralizedTimeFmt<DER> {
888        type SValue = GeneralizedTimeSpec;
889
890        open spec fn spec_serialize_dps(&self, value: Self::SValue, obuf: Seq<u8>) -> Seq<u8> {
891            generalized_time_bytes(value)
892        }
893    }
894
895    impl<const DER: bool> SpecSerializer for super::super::GeneralizedTimeFmt<DER> {
896        type SVal = GeneralizedTimeSpec;
897
898        open spec fn spec_serialize(&self, value: Self::SVal) -> Seq<u8> {
899            generalized_time_bytes(value)
900        }
901    }
902
903    impl<const DER: bool> SpecByteLen for super::super::GeneralizedTimeFmt<DER> {
904        type T = GeneralizedTimeSpec;
905
906        open spec fn byte_len(&self, value: Self::T) -> nat {
907            generalized_time_len(value)
908        }
909    }
910
911}
912
913pub(crate) proof fn lemma_der_generalized_time_model(value: GeneralizedTimeSpec)
914    requires
915        super::GeneralizedTimeFmt::<true>.consistent(value),
916    ensures
917        value.der_wf(),
918        super::GeneralizedTimeFmt::<true>.spec_serialize(value) == generalized_time_prefix(value)
919            + generalized_time_fraction(value) + generalized_time_suffix(value),
920        super::GeneralizedTimeFmt::<true>.spec_serialize(value).len()
921            == super::GeneralizedTimeFmt::<true>.byte_len(value),
922        super::GeneralizedTimeFmt::<true>.spec_serialize(value).len() <= usize::MAX,
923        generalized_time_prefix(value).len() == 14,
924        generalized_time_suffix(value) == seq![0x5au8],
925{
926}
927
928pub(crate) proof fn lemma_der_generalized_time_layout(value: GeneralizedTimeSpec, pos: usize)
929    requires
930        super::GeneralizedTimeFmt::<true>.consistent(value),
931    ensures
932        super::GeneralizedTimeFmt::<true>.spec_serialize(value).len() == if value.fraction.len()
933            == 0 {
934            15
935        } else {
936            value.fraction.len() + 16
937        },
938        pos < super::GeneralizedTimeFmt::<true>.spec_serialize(value).len() ==> {
939            super::GeneralizedTimeFmt::<true>.spec_serialize(value)[pos as int] == if pos < 14 {
940                generalized_time_prefix(value)[pos as int]
941            } else if value.fraction.len() == 0 {
942                0x5au8
943            } else if pos == 14 {
944                0x2eu8
945            } else if (pos as nat) < value.fraction.len() + 15 {
946                value.fraction[pos as int - 15]
947            } else {
948                0x5au8
949            }
950        },
951{
952    lemma_der_generalized_time_model(value);
953}
954
955mod derived_proofs {
956    use super::*;
957
958    impl<const DER: bool> SafeParser for super::super::GeneralizedTimeFmt<DER> {
959        proof fn lemma_parse_safe(&self, ibuf: Seq<u8>) {
960        }
961    }
962
963    impl<const DER: bool> Productive for super::super::GeneralizedTimeFmt<DER> {
964        proof fn lemma_productive(&self, ibuf: Seq<u8>) {
965            if let Some((n, _)) = self.spec_parse(ibuf) {
966                assert(n == ibuf.len());
967                assert(n >= 10);
968            }
969        }
970    }
971
972    impl<const DER: bool> GoodSerializer for super::super::GeneralizedTimeFmt<DER> {
973        proof fn lemma_serialize_len(&self, value: Self::SVal) {
974        }
975    }
976
977    impl<const DER: bool> EquivSerializers for super::super::GeneralizedTimeFmt<DER> {
978        proof fn lemma_serialize_equiv_on_empty(&self, value: Self::SVal) {
979        }
980    }
981
982    impl<const DER: bool> SPRoundTripDps for super::super::GeneralizedTimeFmt<DER> {
983        proof fn theorem_serialize_dps_parse_roundtrip(&self, value: Self::T, obuf: Seq<u8>) {
984            lemma_generalized_time_encode_roundtrip::<DER>(value);
985        }
986    }
987
988    impl SoundParser for super::super::GeneralizedTimeFmt<true> {
989        proof fn lemma_parse_sound_consumption(&self, ibuf: Seq<u8>) {
990            if let Some((n, value)) = self.spec_parse(ibuf) {
991                lemma_der_generalized_time_canonical(ibuf);
992                assert(generalized_time_bytes(value) == ibuf);
993            }
994        }
995
996        proof fn lemma_parse_sound_value(&self, ibuf: Seq<u8>) {
997            if let Some((_, value)) = self.spec_parse(ibuf) {
998                assert(self.consistent(value));
999            }
1000        }
1001    }
1002
1003    impl NonMalleable for super::super::GeneralizedTimeFmt<true> {
1004        proof fn lemma_parse_non_malleable(&self, buf1: Seq<u8>, buf2: Seq<u8>) {
1005            if let (Some((n1, value1)), Some((n2, value2))) = (
1006                self.spec_parse(buf1),
1007                self.spec_parse(buf2),
1008            ) {
1009                lemma_der_generalized_time_canonical(buf1);
1010                lemma_der_generalized_time_canonical(buf2);
1011                if value1 == value2 {
1012                    assert(buf1 == generalized_time_bytes(value1));
1013                    assert(buf2 == generalized_time_bytes(value2));
1014                    assert(buf1[0..n1] == buf1);
1015                    assert(buf2[0..n2] == buf2);
1016                }
1017            }
1018        }
1019    }
1020
1021}
1022
1023impl<'i, const DER: bool> Parser<&'i [u8]> for super::GeneralizedTimeFmt<DER> {
1024    type PT = GeneralizedTime<'i>;
1025
1026    fn parse(&self, ibuf: &&'i [u8]) -> PResult<Self::PT> {
1027        let (n, bytes) = Tail.parse(ibuf)?;
1028        assert(bytes@ == bytes.deep_view());
1029        if !generalized_time_valid::<DER>(bytes) {
1030            return Err(ParseError::custom("Invalid GeneralizedTime"));
1031        }
1032        match generalized_timevalue(bytes) {
1033            Some(value) => Ok((n, value)),
1034            None => Err(ParseError::custom("GeneralizedTime offset out of range")),
1035        }
1036    }
1037}
1038
1039impl<Output: OutputBuf, 'i, const DER: bool> Serializer<
1040    Output,
1041    GeneralizedTime<'i>,
1042> for super::GeneralizedTimeFmt<DER> {
1043    fn serialize_into(&self, value: &GeneralizedTime<'i>, obuf: &mut Output) {
1044        generalized_time_to_bytes(value, obuf);
1045    }
1046}
1047
1048impl<'i, const DER: bool> Prepare<GeneralizedTime<'i>> for super::GeneralizedTimeFmt<DER> {
1049    fn prepare(&self, value: &GeneralizedTime<'i>) -> Result<usize, PreSerializeError> {
1050        if !generalized_value_wf::<DER>(value) {
1051            return Err(PreSerializeError::custom("Invalid GeneralizedTime value"));
1052        }
1053        if value.fraction.len() > usize::MAX - 16 {
1054            return Err(PreSerializeError::length_too_large());
1055        }
1056        Ok(
1057            10 + if value.precision == TimePrecision::Hour {
1058                0
1059            } else {
1060                2
1061            } + if value.precision == TimePrecision::Second {
1062                2
1063            } else {
1064                0
1065            } + if value.fraction.len() == 0 {
1066                0
1067            } else {
1068                1 + value.fraction.len()
1069            } + if value.zone == TimeZone::Utc {
1070                1
1071            } else {
1072                0
1073            },
1074        )
1075    }
1076}
1077
1078impl<'i, const DER: bool> ByteLen<GeneralizedTime<'i>> for super::GeneralizedTimeFmt<DER> {
1079    fn length(&self, value: &GeneralizedTime<'i>) -> usize {
1080        10 + if value.precision == TimePrecision::Hour {
1081            0
1082        } else {
1083            2
1084        } + if value.precision == TimePrecision::Second {
1085            2
1086        } else {
1087            0
1088        } + if value.fraction.len() == 0 {
1089            0
1090        } else {
1091            1 + value.fraction.len()
1092        } + if value.zone == TimeZone::Utc {
1093            1
1094        } else {
1095            0
1096        }
1097    }
1098}
1099
1100} // verus!