@@ -5,6 +5,7 @@ use std::sync::Arc;
55use async_trait:: async_trait;
66use chrono:: { DateTime , Utc } ;
77use serde:: { Deserialize , Serialize } ;
8+ use sha2:: { Digest , Sha256 } ;
89use tracing:: { debug, info} ;
910
1011use crate :: error:: { Error , Result } ;
@@ -191,11 +192,13 @@ fn get_kind(
191192 Ok ( StoredSnapshot :: Schema ( bundle. schema ) )
192193 }
193194 SnapshotKind :: Planner => {
194- let mut bundles: Vec < ( DateTime < Utc > , Bundle ) > = entries
195- . into_iter ( )
196- . filter_map ( |( ts, _, p) | read_bundle ( & p) . ok ( ) . map ( |b| ( ts, b) ) )
197- . filter ( |( _, b) | b. planner . is_some ( ) )
198- . collect ( ) ;
195+ let mut bundles: Vec < ( DateTime < Utc > , Bundle ) > = Vec :: new ( ) ;
196+ for ( ts, _, p) in entries {
197+ let b = read_bundle ( & p) ?;
198+ if b. planner . is_some ( ) {
199+ bundles. push ( ( ts, b) ) ;
200+ }
201+ }
199202 bundles. sort_by_key ( |( ts, _) | std:: cmp:: Reverse ( * ts) ) ;
200203 let chosen = match & at {
201204 SnapshotRef :: Latest => bundles. into_iter ( ) . next ( ) ,
@@ -208,11 +211,13 @@ fn get_kind(
208211 Ok ( StoredSnapshot :: Planner ( bundle. planner . expect ( "filtered" ) ) )
209212 }
210213 SnapshotKind :: Activity { node_label } => {
211- let mut bundles: Vec < ( DateTime < Utc > , Bundle ) > = entries
212- . into_iter ( )
213- . filter_map ( |( ts, _, p) | read_bundle ( & p) . ok ( ) . map ( |b| ( ts, b) ) )
214- . filter ( |( _, b) | b. activity . contains_key ( node_label) )
215- . collect ( ) ;
214+ let mut bundles: Vec < ( DateTime < Utc > , Bundle ) > = Vec :: new ( ) ;
215+ for ( ts, _, p) in entries {
216+ let b = read_bundle ( & p) ?;
217+ if b. activity . contains_key ( node_label) {
218+ bundles. push ( ( ts, b) ) ;
219+ }
220+ }
216221 bundles. sort_by_key ( |( ts, _) | std:: cmp:: Reverse ( * ts) ) ;
217222 let chosen = match & at {
218223 SnapshotRef :: Latest => bundles. into_iter ( ) . next ( ) ,
@@ -239,10 +244,7 @@ fn list_kind(
239244
240245 let mut out: Vec < SnapshotSummary > = Vec :: new ( ) ;
241246 for ( _schema_ts, _schema_hash, path) in entries {
242- let bundle = match read_bundle ( & path) {
243- Ok ( b) => b,
244- Err ( _) => continue ,
245- } ;
247+ let bundle = read_bundle ( & path) ?;
246248 if let Some ( s) = bundle_summary_for_kind ( & bundle, key, kind) {
247249 if range. from . is_none_or ( |f| s. timestamp >= f)
248250 && range. to . is_none_or ( |t| s. timestamp < t)
@@ -268,10 +270,7 @@ fn delete_before(
268270 match kind {
269271 SnapshotKind :: Schema => {
270272 for ( _ts, _h, path) in entries {
271- let bundle = match read_bundle ( & path) {
272- Ok ( b) => b,
273- Err ( _) => continue ,
274- } ;
273+ let bundle = read_bundle ( & path) ?;
275274 if bundle. schema . timestamp < cutoff {
276275 std:: fs:: remove_file ( & path)
277276 . map_err ( |e| Error :: History ( format ! ( "remove {}: {e}" , path. display( ) ) ) ) ?;
@@ -281,10 +280,7 @@ fn delete_before(
281280 }
282281 SnapshotKind :: Planner => {
283282 for ( _ts, _h, path) in entries {
284- let mut bundle = match read_bundle ( & path) {
285- Ok ( b) => b,
286- Err ( _) => continue ,
287- } ;
283+ let mut bundle = read_bundle ( & path) ?;
288284 let drop = bundle
289285 . planner
290286 . as_ref ( )
@@ -298,10 +294,7 @@ fn delete_before(
298294 }
299295 SnapshotKind :: Activity { node_label } => {
300296 for ( _ts, _h, path) in entries {
301- let mut bundle = match read_bundle ( & path) {
302- Ok ( b) => b,
303- Err ( _) => continue ,
304- } ;
297+ let mut bundle = read_bundle ( & path) ?;
305298 let drop = bundle
306299 . activity
307300 . get ( node_label)
@@ -329,10 +322,7 @@ fn list_kinds_sync(root: &Path, key: &SnapshotKey) -> Result<Vec<SnapshotKind>>
329322 let mut activity_labels: std:: collections:: BTreeSet < String > = Default :: default ( ) ;
330323
331324 for ( _ts, _h, path) in entries {
332- let bundle = match read_bundle ( & path) {
333- Ok ( b) => b,
334- Err ( _) => continue ,
335- } ;
325+ let bundle = read_bundle ( & path) ?;
336326 has_schema = true ;
337327 if bundle. planner . is_some ( ) {
338328 has_planner = true ;
@@ -403,10 +393,7 @@ fn bundle_summary_for_kind(
403393
404394fn find_bundle_by_schema_hash ( dir : & Path , schema_hash : & str ) -> Result < Option < ( PathBuf , Bundle ) > > {
405395 for ( _, _, path) in read_stream_entries ( dir) ? {
406- let bundle = match read_bundle ( & path) {
407- Ok ( b) => b,
408- Err ( _) => continue ,
409- } ;
396+ let bundle = read_bundle ( & path) ?;
410397 if bundle. schema . content_hash == schema_hash {
411398 return Ok ( Some ( ( path, bundle) ) ) ;
412399 }
@@ -455,6 +442,10 @@ fn verify_bundle_hash(path: &Path, bundle: &Bundle) -> Result<()> {
455442 ) )
456443 } ) ?;
457444
445+ if !is_sha256_hex ( & expected) {
446+ return Ok ( ( ) ) ;
447+ }
448+
458449 if bundle. schema . content_hash != expected {
459450 return Err ( Error :: History ( format ! (
460451 "corrupt snapshot {}: filename hash {} != stored schema.content_hash {}" ,
@@ -482,9 +473,48 @@ fn verify_bundle_hash(path: &Path, bundle: &Bundle) -> Result<()> {
482473 recomputed,
483474 ) ) ) ;
484475 }
476+
477+ if let Some ( planner) = & bundle. planner {
478+ let mut p = planner. clone ( ) ;
479+ p. content_hash = String :: new ( ) ;
480+ let recomputed = sha256_hex_of_serialized ( & p) ?;
481+ if recomputed != planner. content_hash {
482+ return Err ( Error :: History ( format ! (
483+ "corrupt snapshot {}: planner content_hash {} != recomputed {}" ,
484+ path. display( ) ,
485+ planner. content_hash,
486+ recomputed,
487+ ) ) ) ;
488+ }
489+ }
490+
491+ for ( label, activity) in & bundle. activity {
492+ let mut a = activity. clone ( ) ;
493+ a. content_hash = String :: new ( ) ;
494+ let recomputed = sha256_hex_of_serialized ( & a) ?;
495+ if recomputed != activity. content_hash {
496+ return Err ( Error :: History ( format ! (
497+ "corrupt snapshot {}: activity[{}] content_hash {} != recomputed {}" ,
498+ path. display( ) ,
499+ label,
500+ activity. content_hash,
501+ recomputed,
502+ ) ) ) ;
503+ }
504+ }
485505 Ok ( ( ) )
486506}
487507
508+ fn is_sha256_hex ( s : & str ) -> bool {
509+ s. len ( ) == 64 && s. bytes ( ) . all ( |b| b. is_ascii_hexdigit ( ) )
510+ }
511+
512+ fn sha256_hex_of_serialized < T : serde:: Serialize > ( value : & T ) -> Result < String > {
513+ let bytes = serde_json:: to_vec ( value)
514+ . map_err ( |e| Error :: History ( format ! ( "cannot serialize for hash check: {e}" ) ) ) ?;
515+ Ok ( format ! ( "{:x}" , Sha256 :: digest( & bytes) ) )
516+ }
517+
488518fn write_bundle ( path : & Path , bundle : & Bundle ) -> Result < ( ) > {
489519 if let Some ( parent) = path. parent ( ) {
490520 std:: fs:: create_dir_all ( parent)
0 commit comments