@@ -219,6 +219,7 @@ where
219
219
trusted_targets_keys : Vec < & ' a dyn PrivateKey > ,
220
220
trusted_snapshot_keys : Vec < & ' a dyn PrivateKey > ,
221
221
trusted_timestamp_keys : Vec < & ' a dyn PrivateKey > ,
222
+ time_version : Option < u32 > ,
222
223
root_expiration_duration : Duration ,
223
224
targets_expiration_duration : Duration ,
224
225
snapshot_expiration_duration : Duration ,
@@ -289,6 +290,48 @@ where
289
290
290
291
false
291
292
}
293
+
294
+ /// The initial version number for non-root metadata.
295
+ fn non_root_initial_version ( & self ) -> u32 {
296
+ if let Some ( time_version) = self . time_version {
297
+ time_version
298
+ } else {
299
+ 1
300
+ }
301
+ }
302
+
303
+ /// If time versioning is enabled, this updates the current time version to match the current
304
+ /// time. It will disable time versioning if the current timestamp is less than or equal to
305
+ /// zero, or it is greater than max u32.
306
+ fn update_time_version ( & mut self ) {
307
+ // We can use the time version if it is greater than zero and less than max u32. Otherwise
308
+ // fall back to default monontonic versioning.
309
+ let timestamp = self . current_time . timestamp ( ) ;
310
+ if timestamp > 0 {
311
+ self . time_version = timestamp. try_into ( ) . ok ( ) ;
312
+ } else {
313
+ self . time_version = None ;
314
+ }
315
+ }
316
+
317
+ /// The next version number for non-root metadata.
318
+ fn non_root_next_version (
319
+ & self ,
320
+ current_version : u32 ,
321
+ path : fn ( ) -> MetadataPath ,
322
+ ) -> Result < u32 > {
323
+ if let Some ( time_version) = self . time_version {
324
+ // We can only use the time version if it's larger than our current version. If not,
325
+ // then fall back to the next version.
326
+ if current_version < time_version {
327
+ return Ok ( time_version) ;
328
+ }
329
+ }
330
+
331
+ current_version
332
+ . checked_add ( 1 )
333
+ . ok_or_else ( || Error :: MetadataVersionMustBeSmallerThanMaxU32 ( path ( ) ) )
334
+ }
292
335
}
293
336
294
337
fn sign < ' a , D , I , M > ( meta : & M , keys : I ) -> Result < RawSignedMetadata < D , M > >
@@ -367,6 +410,7 @@ where
367
410
trusted_targets_keys : vec ! [ ] ,
368
411
trusted_snapshot_keys : vec ! [ ] ,
369
412
trusted_timestamp_keys : vec ! [ ] ,
413
+ time_version : None ,
370
414
root_expiration_duration : Duration :: days ( DEFAULT_ROOT_EXPIRATION_DAYS ) ,
371
415
targets_expiration_duration : Duration :: days ( DEFAULT_TARGETS_EXPIRATION_DAYS ) ,
372
416
snapshot_expiration_duration : Duration :: days ( DEFAULT_SNAPSHOT_EXPIRATION_DAYS ) ,
@@ -455,6 +499,7 @@ where
455
499
trusted_targets_keys : vec ! [ ] ,
456
500
trusted_snapshot_keys : vec ! [ ] ,
457
501
trusted_timestamp_keys : vec ! [ ] ,
502
+ time_version : None ,
458
503
root_expiration_duration : Duration :: days ( DEFAULT_ROOT_EXPIRATION_DAYS ) ,
459
504
targets_expiration_duration : Duration :: days ( DEFAULT_TARGETS_EXPIRATION_DAYS ) ,
460
505
snapshot_expiration_duration : Duration :: days ( DEFAULT_SNAPSHOT_EXPIRATION_DAYS ) ,
@@ -471,6 +516,23 @@ where
471
516
/// Default is the current wall clock time in UTC.
472
517
pub fn current_time ( mut self , current_time : DateTime < Utc > ) -> Self {
473
518
self . ctx . current_time = current_time;
519
+
520
+ // Update our time version if enabled.
521
+ if self . ctx . time_version . is_some ( ) {
522
+ self . ctx . update_time_version ( ) ;
523
+ }
524
+
525
+ self
526
+ }
527
+
528
+ /// Create Non-root metadata based off the current UTC timestamp, instead of a monotonic
529
+ /// increment.
530
+ pub fn time_versioning ( mut self , time_versioning : bool ) -> Self {
531
+ if time_versioning {
532
+ self . ctx . update_time_version ( ) ;
533
+ } else {
534
+ self . ctx . time_version = None ;
535
+ }
474
536
self
475
537
}
476
538
@@ -912,9 +974,9 @@ where
912
974
let mut delegations_builder = DelegationsBuilder :: new ( ) ;
913
975
914
976
if let Some ( trusted_targets) = self . ctx . db . and_then ( |db| db. trusted_targets ( ) ) {
915
- let next_version = trusted_targets . version ( ) . checked_add ( 1 ) . ok_or_else ( || {
916
- Error :: MetadataVersionMustBeSmallerThanMaxU32 ( MetadataPath :: targets ( ) )
917
- } ) ?;
977
+ let next_version = self
978
+ . ctx
979
+ . non_root_next_version ( trusted_targets . version ( ) , MetadataPath :: targets ) ?;
918
980
919
981
targets_builder = targets_builder. version ( next_version) ;
920
982
@@ -933,6 +995,8 @@ where
933
995
delegations_builder = delegations_builder. role ( role. clone ( ) ) ;
934
996
}
935
997
}
998
+ } else {
999
+ targets_builder = targets_builder. version ( self . ctx . non_root_initial_version ( ) ) ;
936
1000
}
937
1001
938
1002
// Overwrite any of the old targets with the new ones.
@@ -1092,9 +1156,9 @@ where
1092
1156
. expires ( self . ctx . current_time + self . ctx . snapshot_expiration_duration ) ;
1093
1157
1094
1158
if let Some ( trusted_snapshot) = self . ctx . db . and_then ( |db| db. trusted_snapshot ( ) ) {
1095
- let next_version = trusted_snapshot . version ( ) . checked_add ( 1 ) . ok_or_else ( || {
1096
- Error :: MetadataVersionMustBeSmallerThanMaxU32 ( MetadataPath :: snapshot ( ) )
1097
- } ) ?;
1159
+ let next_version = self
1160
+ . ctx
1161
+ . non_root_next_version ( trusted_snapshot . version ( ) , MetadataPath :: snapshot ) ?;
1098
1162
1099
1163
snapshot_builder = snapshot_builder. version ( next_version) ;
1100
1164
@@ -1105,6 +1169,8 @@ where
1105
1169
. insert_metadata_description ( path. clone ( ) , description. clone ( ) ) ;
1106
1170
}
1107
1171
}
1172
+ } else {
1173
+ snapshot_builder = snapshot_builder. version ( self . ctx . non_root_initial_version ( ) ) ;
1108
1174
}
1109
1175
1110
1176
// Overwrite the targets entry if specified.
@@ -1246,14 +1312,13 @@ where
1246
1312
{
1247
1313
let next_version = if let Some ( db) = self . ctx . db {
1248
1314
if let Some ( trusted_timestamp) = db. trusted_timestamp ( ) {
1249
- trusted_timestamp. version ( ) . checked_add ( 1 ) . ok_or_else ( || {
1250
- Error :: MetadataVersionMustBeSmallerThanMaxU32 ( MetadataPath :: timestamp ( ) )
1251
- } ) ?
1315
+ self . ctx
1316
+ . non_root_next_version ( trusted_timestamp. version ( ) , MetadataPath :: timestamp) ?
1252
1317
} else {
1253
- 1
1318
+ self . ctx . non_root_initial_version ( )
1254
1319
}
1255
1320
} else {
1256
- 1
1321
+ self . ctx . non_root_initial_version ( )
1257
1322
} ;
1258
1323
1259
1324
let description = if let Some ( description) = self . state . snapshot_description ( ) ? {
@@ -2975,4 +3040,134 @@ mod tests {
2975
3040
assert_eq ! ( db. trusted_timestamp( ) . unwrap( ) . version( ) , 2 ) ;
2976
3041
} )
2977
3042
}
3043
+
3044
+ #[ test]
3045
+ fn test_time_versioning ( ) {
3046
+ block_on ( async move {
3047
+ let mut repo = EphemeralRepository :: < Json > :: new ( ) ;
3048
+
3049
+ let current_time = Utc . timestamp ( 5 , 0 ) ;
3050
+ let metadata = RepoBuilder :: create ( & mut repo)
3051
+ . current_time ( current_time)
3052
+ . time_versioning ( true )
3053
+ . trusted_root_keys ( & [ & KEYS [ 0 ] ] )
3054
+ . trusted_targets_keys ( & [ & KEYS [ 0 ] ] )
3055
+ . trusted_snapshot_keys ( & [ & KEYS [ 0 ] ] )
3056
+ . trusted_timestamp_keys ( & [ & KEYS [ 0 ] ] )
3057
+ . commit ( )
3058
+ . await
3059
+ . unwrap ( ) ;
3060
+
3061
+ let mut db =
3062
+ Database :: from_trusted_metadata_with_start_time ( & metadata, & current_time) . unwrap ( ) ;
3063
+
3064
+ // The initial version should be the current time.
3065
+ assert_eq ! ( db. trusted_root( ) . version( ) , 1 ) ;
3066
+ assert_eq ! ( db. trusted_targets( ) . map( |m| m. version( ) ) , Some ( 5 ) ) ;
3067
+ assert_eq ! ( db. trusted_snapshot( ) . map( |m| m. version( ) ) , Some ( 5 ) ) ;
3068
+ assert_eq ! ( db. trusted_timestamp( ) . map( |m| m. version( ) ) , Some ( 5 ) ) ;
3069
+
3070
+ // Generating metadata for the same timestamp should advance it by 1.
3071
+ let metadata = RepoBuilder :: from_database ( & mut repo, & db)
3072
+ . current_time ( current_time)
3073
+ . time_versioning ( true )
3074
+ . trusted_root_keys ( & [ & KEYS [ 0 ] ] )
3075
+ . trusted_targets_keys ( & [ & KEYS [ 0 ] ] )
3076
+ . trusted_snapshot_keys ( & [ & KEYS [ 0 ] ] )
3077
+ . trusted_timestamp_keys ( & [ & KEYS [ 0 ] ] )
3078
+ . stage_root ( )
3079
+ . unwrap ( )
3080
+ . stage_targets ( )
3081
+ . unwrap ( )
3082
+ . commit ( )
3083
+ . await
3084
+ . unwrap ( ) ;
3085
+
3086
+ db. update_metadata_with_start_time ( & metadata, & current_time)
3087
+ . unwrap ( ) ;
3088
+
3089
+ assert_eq ! ( db. trusted_root( ) . version( ) , 2 ) ;
3090
+ assert_eq ! ( db. trusted_targets( ) . map( |m| m. version( ) ) , Some ( 6 ) ) ;
3091
+ assert_eq ! ( db. trusted_snapshot( ) . map( |m| m. version( ) ) , Some ( 6 ) ) ;
3092
+ assert_eq ! ( db. trusted_timestamp( ) . map( |m| m. version( ) ) , Some ( 6 ) ) ;
3093
+
3094
+ // Generating metadata for a new timestamp should advance the versions to that amount.
3095
+ let current_time = Utc . timestamp ( 10 , 0 ) ;
3096
+ let metadata = RepoBuilder :: from_database ( & mut repo, & db)
3097
+ . current_time ( current_time)
3098
+ . time_versioning ( true )
3099
+ . trusted_root_keys ( & [ & KEYS [ 0 ] ] )
3100
+ . trusted_targets_keys ( & [ & KEYS [ 0 ] ] )
3101
+ . trusted_snapshot_keys ( & [ & KEYS [ 0 ] ] )
3102
+ . trusted_timestamp_keys ( & [ & KEYS [ 0 ] ] )
3103
+ . stage_root ( )
3104
+ . unwrap ( )
3105
+ . stage_targets ( )
3106
+ . unwrap ( )
3107
+ . commit ( )
3108
+ . await
3109
+ . unwrap ( ) ;
3110
+
3111
+ db. update_metadata_with_start_time ( & metadata, & current_time)
3112
+ . unwrap ( ) ;
3113
+
3114
+ assert_eq ! ( db. trusted_root( ) . version( ) , 3 ) ;
3115
+ assert_eq ! ( db. trusted_targets( ) . map( |m| m. version( ) ) , Some ( 10 ) ) ;
3116
+ assert_eq ! ( db. trusted_snapshot( ) . map( |m| m. version( ) ) , Some ( 10 ) ) ;
3117
+ assert_eq ! ( db. trusted_timestamp( ) . map( |m| m. version( ) ) , Some ( 10 ) ) ;
3118
+ } )
3119
+ }
3120
+
3121
+ #[ test]
3122
+ fn test_time_versioning_falls_back_to_monotonic ( ) {
3123
+ block_on ( async move {
3124
+ let mut repo = EphemeralRepository :: < Json > :: new ( ) ;
3125
+
3126
+ // zero timestamp should initialize to 1.
3127
+ let current_time = Utc . timestamp ( 0 , 0 ) ;
3128
+ let metadata = RepoBuilder :: create ( & mut repo)
3129
+ . current_time ( current_time)
3130
+ . time_versioning ( true )
3131
+ . trusted_root_keys ( & [ & KEYS [ 0 ] ] )
3132
+ . trusted_targets_keys ( & [ & KEYS [ 0 ] ] )
3133
+ . trusted_snapshot_keys ( & [ & KEYS [ 0 ] ] )
3134
+ . trusted_timestamp_keys ( & [ & KEYS [ 0 ] ] )
3135
+ . commit ( )
3136
+ . await
3137
+ . unwrap ( ) ;
3138
+
3139
+ let mut db =
3140
+ Database :: from_trusted_metadata_with_start_time ( & metadata, & current_time) . unwrap ( ) ;
3141
+
3142
+ assert_eq ! ( db. trusted_root( ) . version( ) , 1 ) ;
3143
+ assert_eq ! ( db. trusted_targets( ) . map( |m| m. version( ) ) , Some ( 1 ) ) ;
3144
+ assert_eq ! ( db. trusted_snapshot( ) . map( |m| m. version( ) ) , Some ( 1 ) ) ;
3145
+ assert_eq ! ( db. trusted_timestamp( ) . map( |m| m. version( ) ) , Some ( 1 ) ) ;
3146
+
3147
+ // A sub-second timestamp should advance the version by 1.
3148
+ let current_time = Utc . timestamp ( 0 , 3 ) ;
3149
+ let metadata = RepoBuilder :: from_database ( & mut repo, & db)
3150
+ . current_time ( current_time)
3151
+ . time_versioning ( true )
3152
+ . trusted_root_keys ( & [ & KEYS [ 0 ] ] )
3153
+ . trusted_targets_keys ( & [ & KEYS [ 0 ] ] )
3154
+ . trusted_snapshot_keys ( & [ & KEYS [ 0 ] ] )
3155
+ . trusted_timestamp_keys ( & [ & KEYS [ 0 ] ] )
3156
+ . stage_root ( )
3157
+ . unwrap ( )
3158
+ . stage_targets ( )
3159
+ . unwrap ( )
3160
+ . commit ( )
3161
+ . await
3162
+ . unwrap ( ) ;
3163
+
3164
+ db. update_metadata_with_start_time ( & metadata, & current_time)
3165
+ . unwrap ( ) ;
3166
+
3167
+ assert_eq ! ( db. trusted_root( ) . version( ) , 2 ) ;
3168
+ assert_eq ! ( db. trusted_targets( ) . map( |m| m. version( ) ) , Some ( 2 ) ) ;
3169
+ assert_eq ! ( db. trusted_snapshot( ) . map( |m| m. version( ) ) , Some ( 2 ) ) ;
3170
+ assert_eq ! ( db. trusted_timestamp( ) . map( |m| m. version( ) ) , Some ( 2 ) ) ;
3171
+ } )
3172
+ }
2978
3173
}
0 commit comments