@@ -914,7 +914,8 @@ fn push_four_digits(dest: &mut WriteBuf, val: u16) -> WriteResult {
914
914
}
915
915
916
916
/// A structure representing a (UTC timezone) date and time.
917
- /// Wrapped by `UtcTime` and `X509GeneralizedTime`.
917
+ /// Wrapped by `UtcTime` and `X509GeneralizedTime` and used in
918
+ /// `GeneralizedTime`.
918
919
#[ derive( Debug , Clone , PartialEq , Hash , Eq , PartialOrd ) ]
919
920
pub struct DateTime {
920
921
year : u16 ,
@@ -1088,6 +1089,125 @@ impl SimpleAsn1Writable for X509GeneralizedTime {
1088
1089
}
1089
1090
}
1090
1091
1092
+ /// Used for parsing and writing ASN.1 `GENERALIZED TIME` values,
1093
+ /// including values with fractional seconds of up to nanosecond
1094
+ /// precision.
1095
+ #[ derive( Debug , Clone , PartialEq , PartialOrd , Hash , Eq ) ]
1096
+ pub struct GeneralizedTime {
1097
+ datetime : DateTime ,
1098
+ nanoseconds : Option < u32 > ,
1099
+ }
1100
+
1101
+ impl GeneralizedTime {
1102
+ pub fn new ( dt : DateTime , nanoseconds : Option < u32 > ) -> ParseResult < GeneralizedTime > {
1103
+ if let Some ( val) = nanoseconds {
1104
+ if val < 1 || val >= 1e9 as u32 {
1105
+ return Err ( ParseError :: new ( ParseErrorKind :: InvalidValue ) ) ;
1106
+ }
1107
+ }
1108
+
1109
+ Ok ( GeneralizedTime {
1110
+ datetime : dt,
1111
+ nanoseconds,
1112
+ } )
1113
+ }
1114
+
1115
+ pub fn as_datetime ( & self ) -> & DateTime {
1116
+ & self . datetime
1117
+ }
1118
+
1119
+ pub fn nanoseconds ( & self ) -> Option < u32 > {
1120
+ self . nanoseconds
1121
+ }
1122
+ }
1123
+
1124
+ fn read_fractional_time ( data : & mut & [ u8 ] ) -> ParseResult < Option < u32 > > {
1125
+ // We cannot use read_byte here because it will advance the pointer
1126
+ // However, we know that the is suffixed by 'Z' so reading into an empty
1127
+ // data should lead to an error.
1128
+ if data. first ( ) == Some ( & b'.' ) {
1129
+ * data = & data[ 1 ..] ;
1130
+
1131
+ let mut fraction = 0u32 ;
1132
+ let mut digits = 0 ;
1133
+ // Read up to 9 digits
1134
+ for b in data. iter ( ) . take ( 9 ) {
1135
+ if !b. is_ascii_digit ( ) {
1136
+ if digits == 0 {
1137
+ // We must have at least one digit
1138
+ return Err ( ParseError :: new ( ParseErrorKind :: InvalidValue ) ) ;
1139
+ }
1140
+ break ;
1141
+ }
1142
+ fraction = fraction * 10 + ( b - b'0' ) as u32 ;
1143
+ digits += 1 ;
1144
+ }
1145
+ * data = & data[ digits..] ;
1146
+
1147
+ // No trailing zero
1148
+ if fraction % 10 == 0 {
1149
+ return Err ( ParseError :: new ( ParseErrorKind :: InvalidValue ) ) ;
1150
+ }
1151
+
1152
+ // Now let scale up in nanoseconds
1153
+ let nanoseconds: u32 = fraction * 10u32 . pow ( 9 - digits as u32 ) ;
1154
+ Ok ( Some ( nanoseconds) )
1155
+ } else {
1156
+ Ok ( None )
1157
+ }
1158
+ }
1159
+
1160
+ impl SimpleAsn1Readable < ' _ > for GeneralizedTime {
1161
+ const TAG : Tag = Tag :: primitive ( 0x18 ) ;
1162
+ fn parse_data ( mut data : & [ u8 ] ) -> ParseResult < GeneralizedTime > {
1163
+ let year = read_4_digits ( & mut data) ?;
1164
+ let month = read_2_digits ( & mut data) ?;
1165
+ let day = read_2_digits ( & mut data) ?;
1166
+ let hour = read_2_digits ( & mut data) ?;
1167
+ let minute = read_2_digits ( & mut data) ?;
1168
+ let second = read_2_digits ( & mut data) ?;
1169
+
1170
+ let fraction = read_fractional_time ( & mut data) ?;
1171
+ read_tz_and_finish ( & mut data) ?;
1172
+
1173
+ GeneralizedTime :: new (
1174
+ DateTime :: new ( year, month, day, hour, minute, second) ?,
1175
+ fraction,
1176
+ )
1177
+ }
1178
+ }
1179
+
1180
+ impl SimpleAsn1Writable for GeneralizedTime {
1181
+ const TAG : Tag = Tag :: primitive ( 0x18 ) ;
1182
+ fn write_data ( & self , dest : & mut WriteBuf ) -> WriteResult {
1183
+ let dt = self . as_datetime ( ) ;
1184
+ push_four_digits ( dest, dt. year ( ) ) ?;
1185
+ push_two_digits ( dest, dt. month ( ) ) ?;
1186
+ push_two_digits ( dest, dt. day ( ) ) ?;
1187
+
1188
+ push_two_digits ( dest, dt. hour ( ) ) ?;
1189
+ push_two_digits ( dest, dt. minute ( ) ) ?;
1190
+ push_two_digits ( dest, dt. second ( ) ) ?;
1191
+
1192
+ if let Some ( nanoseconds) = self . nanoseconds ( ) {
1193
+ dest. push_byte ( b'.' ) ?;
1194
+
1195
+ let mut buf = itoa:: Buffer :: new ( ) ;
1196
+ let nanos = buf. format ( nanoseconds) ;
1197
+ let pad = 9 - nanos. len ( ) ;
1198
+ let nanos = nanos. trim_end_matches ( '0' ) ;
1199
+
1200
+ for _ in 0 ..pad {
1201
+ dest. push_byte ( b'0' ) ?;
1202
+ }
1203
+
1204
+ dest. push_slice ( nanos. as_bytes ( ) ) ?;
1205
+ }
1206
+
1207
+ dest. push_byte ( b'Z' )
1208
+ }
1209
+ }
1210
+
1091
1211
/// An ASN.1 `ENUMERATED` value.
1092
1212
#[ derive( Debug , PartialEq , Eq ) ]
1093
1213
pub struct Enumerated ( u32 ) ;
@@ -1725,8 +1845,8 @@ impl<T> DefinedByMarker<T> {
1725
1845
#[ cfg( test) ]
1726
1846
mod tests {
1727
1847
use crate :: {
1728
- parse_single, BigInt , BigUint , DateTime , DefinedByMarker , Enumerated , IA5String ,
1729
- ObjectIdentifier , OctetStringEncoded , OwnedBigInt , OwnedBigUint , ParseError ,
1848
+ parse_single, BigInt , BigUint , DateTime , DefinedByMarker , Enumerated , GeneralizedTime ,
1849
+ IA5String , ObjectIdentifier , OctetStringEncoded , OwnedBigInt , OwnedBigUint , ParseError ,
1730
1850
ParseErrorKind , PrintableString , SequenceOf , SequenceOfWriter , SetOf , SetOfWriter , Tag ,
1731
1851
Tlv , UtcTime , Utf8String , VisibleString , X509GeneralizedTime ,
1732
1852
} ;
@@ -2005,6 +2125,55 @@ mod tests {
2005
2125
assert ! ( X509GeneralizedTime :: new( DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) ) . is_ok( ) ) ;
2006
2126
}
2007
2127
2128
+ #[ test]
2129
+ fn test_generalized_time_new ( ) {
2130
+ assert ! (
2131
+ GeneralizedTime :: new( DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) , Some ( 1234 ) )
2132
+ . is_ok( )
2133
+ ) ;
2134
+ assert ! (
2135
+ GeneralizedTime :: new( DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) , None ) . is_ok( )
2136
+ ) ;
2137
+ // Maximum fractional time is 999,999,999 nanos.
2138
+ assert ! ( GeneralizedTime :: new(
2139
+ DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) ,
2140
+ Some ( 999_999_999_u32 )
2141
+ )
2142
+ . is_ok( ) ) ;
2143
+ assert ! ( GeneralizedTime :: new(
2144
+ DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) ,
2145
+ Some ( 1e9 as u32 )
2146
+ )
2147
+ . is_err( ) ) ;
2148
+ assert ! ( GeneralizedTime :: new(
2149
+ DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) ,
2150
+ Some ( 1e9 as u32 + 1 )
2151
+ )
2152
+ . is_err( ) ) ;
2153
+ }
2154
+
2155
+ #[ test]
2156
+ fn test_generalized_time_partial_ord ( ) {
2157
+ let point =
2158
+ GeneralizedTime :: new ( DateTime :: new ( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap ( ) , Some ( 1234 ) )
2159
+ . unwrap ( ) ;
2160
+ assert ! (
2161
+ point
2162
+ < GeneralizedTime :: new( DateTime :: new( 2023 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) , Some ( 1234 ) )
2163
+ . unwrap( )
2164
+ ) ;
2165
+ assert ! (
2166
+ point
2167
+ < GeneralizedTime :: new( DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) , Some ( 1235 ) )
2168
+ . unwrap( )
2169
+ ) ;
2170
+ assert ! (
2171
+ point
2172
+ > GeneralizedTime :: new( DateTime :: new( 2015 , 6 , 30 , 23 , 59 , 59 ) . unwrap( ) , None )
2173
+ . unwrap( )
2174
+ ) ;
2175
+ }
2176
+
2008
2177
#[ test]
2009
2178
fn test_enumerated_value ( ) {
2010
2179
assert_eq ! ( Enumerated :: new( 4 ) . value( ) , 4 ) ;
0 commit comments