@@ -139,7 +139,7 @@ mod alloc_tracker {
139139
140140use std:: fmt;
141141use std:: fs:: { File , OpenOptions } ;
142- use std:: io:: Read ;
142+ use std:: io:: { Read , Write } ;
143143use std:: num:: NonZeroU32 ;
144144use std:: path:: { Path , PathBuf } ;
145145use std:: time:: Instant ;
@@ -150,7 +150,7 @@ use tokio::runtime::Builder;
150150use anyhow:: { Context , Result , anyhow} ;
151151use byte_unit:: { Byte , Unit , UnitType } ;
152152use clap:: Parser ;
153- use lading:: generator:: { self , http:: Method } ;
153+ use lading:: generator:: { self , file_gen , http:: Method } ;
154154use lading_payload:: block;
155155use rand:: { SeedableRng , rngs:: StdRng } ;
156156use sha2:: { Digest , Sha256 } ;
@@ -276,14 +276,17 @@ struct Args {
276276 /// Report memory allocation statistics at completion
277277 #[ clap( short = 'm' , long) ]
278278 memory_stats : bool ,
279+ /// Dump generated payload data to this file
280+ #[ clap( long) ]
281+ dump : Option < PathBuf > ,
279282}
280283
281284fn generate_and_check (
282285 config : & lading_payload:: Config ,
283286 seed : [ u8 ; 32 ] ,
284287 total_bytes : NonZeroU32 ,
285288 max_block_size : Byte ,
286- compute_fingerprint : bool ,
289+ args : & Args ,
287290) -> Result < Option < Fingerprint > > {
288291 let mut rng = StdRng :: from_seed ( seed) ;
289292 let start = Instant :: now ( ) ;
@@ -306,8 +309,23 @@ fn generate_and_check(
306309 info ! ( "Payload generation took {:?}" , start. elapsed( ) ) ;
307310 trace ! ( "Payload: {:#?}" , blocks) ;
308311
312+ if let Some ( dump_path) = args. dump . as_deref ( ) {
313+ let mut dump_file = OpenOptions :: new ( )
314+ . write ( true )
315+ . create ( true )
316+ . truncate ( true )
317+ . open ( dump_path)
318+ . with_context ( || format ! ( "Could not open dump file at: {}" , dump_path. display( ) ) ) ?;
319+ for block in & blocks {
320+ dump_file. write_all ( & block. bytes ) . with_context ( || {
321+ format ! ( "Failed to write to dump file: {}" , dump_path. display( ) )
322+ } ) ?;
323+ }
324+ info ! ( "Dumped payload to {}" , dump_path. display( ) ) ;
325+ }
326+
309327 // Compute fingerprint if requested: SHA256 hash and Shannon entropy.
310- let fingerprint = if compute_fingerprint {
328+ let fingerprint = args . fingerprint . then ( || {
311329 let mut hasher = Sha256 :: new ( ) ;
312330 let mut all_bytes = Vec :: new ( ) ;
313331 for block in & blocks {
@@ -317,10 +335,8 @@ fn generate_and_check(
317335 let result = hasher. finalize ( ) ;
318336 let hash = format ! ( "{result:x}" ) ;
319337 let entropy = shannon_entropy ( & all_bytes) ;
320- Some ( Fingerprint { hash, entropy } )
321- } else {
322- None
323- } ;
338+ Fingerprint { hash, entropy }
339+ } ) ;
324340
325341 let mut total_generated_bytes: u32 = 0 ;
326342 for block in & blocks {
@@ -348,55 +364,54 @@ fn generate_and_check(
348364}
349365
350366#[ expect( clippy:: too_many_lines) ]
351- fn check_generator (
352- config : & generator:: Config ,
353- compute_fingerprint : bool ,
354- ) -> Result < Option < Fingerprint > > {
367+ fn check_generator ( config : & generator:: Config , args : & Args ) -> Result < Option < Fingerprint > > {
355368 match & config. inner {
356- generator:: Inner :: FileGen ( _) => {
357- if compute_fingerprint {
358- warn ! ( "FileGen not supported for fingerprinting" ) ;
359- return Ok ( None ) ;
360- }
361- unimplemented ! ( "FileGen not supported" )
369+ generator:: Inner :: FileGen ( g) => {
370+ let ( cache_size, variant, seed, max_block_size) = match g {
371+ file_gen:: Config :: Traditional ( c) => (
372+ c. maximum_prebuild_cache_size_bytes ,
373+ & c. variant ,
374+ c. seed ,
375+ c. maximum_block_size ,
376+ ) ,
377+ file_gen:: Config :: Logrotate ( c) => (
378+ c. maximum_prebuild_cache_size_bytes ,
379+ & c. variant ,
380+ c. seed ,
381+ c. maximum_block_size ,
382+ ) ,
383+ #[ cfg( feature = "logrotate_fs" ) ]
384+ file_gen:: Config :: LogrotateFs ( c) => (
385+ c. maximum_prebuild_cache_size_bytes ,
386+ & c. variant ,
387+ c. seed ,
388+ c. maximum_block_size ,
389+ ) ,
390+ } ;
391+ #[ expect( clippy:: cast_possible_truncation) ]
392+ let total_bytes = NonZeroU32 :: new ( cache_size. as_u128 ( ) as u32 )
393+ . expect ( "Non-zero max prebuild cache size" ) ;
394+ generate_and_check ( variant, seed, total_bytes, max_block_size, args)
362395 }
363396 generator:: Inner :: UnixDatagram ( g) => {
364397 let max_block_size = UDP_PACKET_LIMIT_BYTES ;
365398 #[ expect( clippy:: cast_possible_truncation) ]
366399 let total_bytes = NonZeroU32 :: new ( g. maximum_prebuild_cache_size_bytes . as_u128 ( ) as u32 )
367400 . expect ( "Non-zero max prebuild cache size" ) ;
368- generate_and_check (
369- & g. variant ,
370- g. seed ,
371- total_bytes,
372- max_block_size,
373- compute_fingerprint,
374- )
401+ generate_and_check ( & g. variant , g. seed , total_bytes, max_block_size, args)
375402 }
376403 generator:: Inner :: Tcp ( g) => {
377404 #[ expect( clippy:: cast_possible_truncation) ]
378405 let total_bytes = NonZeroU32 :: new ( g. maximum_prebuild_cache_size_bytes . as_u128 ( ) as u32 )
379406 . expect ( "Non-zero max prebuild cache size" ) ;
380- generate_and_check (
381- & g. variant ,
382- g. seed ,
383- total_bytes,
384- g. maximum_block_size ,
385- compute_fingerprint,
386- )
407+ generate_and_check ( & g. variant , g. seed , total_bytes, g. maximum_block_size , args)
387408 }
388409 generator:: Inner :: Udp ( g) => {
389410 #[ expect( clippy:: cast_possible_truncation) ]
390411 let total_bytes = NonZeroU32 :: new ( g. maximum_prebuild_cache_size_bytes . as_u128 ( ) as u32 )
391412 . expect ( "Non-zero max prebuild cache size" ) ;
392413 let max_block_size = UDP_PACKET_LIMIT_BYTES ;
393- generate_and_check (
394- & g. variant ,
395- g. seed ,
396- total_bytes,
397- max_block_size,
398- compute_fingerprint,
399- )
414+ generate_and_check ( & g. variant , g. seed , total_bytes, max_block_size, args)
400415 }
401416 generator:: Inner :: Http ( g) => {
402417 let ( variant, max_prebuild_cache_size_bytes) = match & g. method {
@@ -409,23 +424,17 @@ fn check_generator(
409424 #[ expect( clippy:: cast_possible_truncation) ]
410425 let total_bytes = NonZeroU32 :: new ( max_prebuild_cache_size_bytes. as_u128 ( ) as u32 )
411426 . expect ( "Non-zero max prebuild cache size" ) ;
412- generate_and_check (
413- variant,
414- g. seed ,
415- total_bytes,
416- g. maximum_block_size ,
417- compute_fingerprint,
418- )
427+ generate_and_check ( variant, g. seed , total_bytes, g. maximum_block_size , args)
419428 }
420429 generator:: Inner :: SplunkHec ( _) => {
421- if compute_fingerprint {
430+ if args . fingerprint {
422431 warn ! ( "SplunkHec not supported for fingerprinting" ) ;
423432 return Ok ( None ) ;
424433 }
425434 unimplemented ! ( "SplunkHec not supported" )
426435 }
427436 generator:: Inner :: FileTree ( _) => {
428- if compute_fingerprint {
437+ if args . fingerprint {
429438 warn ! ( "FileTree not supported for fingerprinting" ) ;
430439 return Ok ( None ) ;
431440 }
@@ -435,61 +444,43 @@ fn check_generator(
435444 #[ expect( clippy:: cast_possible_truncation) ]
436445 let total_bytes = NonZeroU32 :: new ( g. maximum_prebuild_cache_size_bytes . as_u128 ( ) as u32 )
437446 . expect ( "Non-zero max prebuild cache size" ) ;
438- generate_and_check (
439- & g. variant ,
440- g. seed ,
441- total_bytes,
442- g. maximum_block_size ,
443- compute_fingerprint,
444- )
447+ generate_and_check ( & g. variant , g. seed , total_bytes, g. maximum_block_size , args)
445448 }
446449 generator:: Inner :: UnixStream ( g) => {
447450 #[ expect( clippy:: cast_possible_truncation) ]
448451 let total_bytes = NonZeroU32 :: new ( g. maximum_prebuild_cache_size_bytes . as_u128 ( ) as u32 )
449452 . expect ( "Non-zero max prebuild cache size" ) ;
450- generate_and_check (
451- & g. variant ,
452- g. seed ,
453- total_bytes,
454- g. maximum_block_size ,
455- compute_fingerprint,
456- )
453+ generate_and_check ( & g. variant , g. seed , total_bytes, g. maximum_block_size , args)
457454 }
458455 generator:: Inner :: PassthruFile ( g) => {
459456 #[ expect( clippy:: cast_possible_truncation) ]
460457 let total_bytes = NonZeroU32 :: new ( g. maximum_prebuild_cache_size_bytes . as_u128 ( ) as u32 )
461458 . expect ( "Non-zero max prebuild cache size" ) ;
462- generate_and_check (
463- & g. variant ,
464- g. seed ,
465- total_bytes,
466- g. maximum_block_size ,
467- compute_fingerprint,
468- )
459+ generate_and_check ( & g. variant , g. seed , total_bytes, g. maximum_block_size , args)
469460 }
470461 generator:: Inner :: ProcessTree ( _) => {
471- if compute_fingerprint {
462+ if args . fingerprint {
472463 warn ! ( "ProcessTree not supported for fingerprinting" ) ;
473464 return Ok ( None ) ;
474465 }
475466 unimplemented ! ( "ProcessTree not supported" )
476467 }
477468 generator:: Inner :: ProcFs ( _) => {
478- if compute_fingerprint {
469+ if args . fingerprint {
479470 warn ! ( "ProcFs not supported for fingerprinting" ) ;
480471 return Ok ( None ) ;
481472 }
482473 unimplemented ! ( "ProcFs not supported" )
483474 }
484475 generator:: Inner :: Container ( _) => {
485- if compute_fingerprint {
476+ if args . fingerprint {
486477 warn ! ( "Container not supported for fingerprinting" ) ;
487478 return Ok ( None ) ;
488479 }
489480 unimplemented ! ( "Container not supported" )
490481 }
491482 generator:: Inner :: Kubernetes ( _) => {
492- if compute_fingerprint {
483+ if args . fingerprint {
493484 warn ! ( "Kubernetes not supported for fingerprinting" ) ;
494485 return Ok ( None ) ;
495486 }
@@ -500,13 +491,7 @@ fn check_generator(
500491 generator:: trace_agent:: validate_cache_size ( g. maximum_prebuild_cache_size_bytes )
501492 . map_err ( |e| anyhow:: anyhow!( "Cache size validation failed: {e}" ) ) ?;
502493 let conf = lading_payload:: Config :: TraceAgent ( g. variant ) ;
503- generate_and_check (
504- & conf,
505- g. seed ,
506- total_bytes,
507- g. maximum_block_size ,
508- compute_fingerprint,
509- )
494+ generate_and_check ( & conf, g. seed , total_bytes, g. maximum_block_size , args)
510495 }
511496 }
512497}
@@ -548,24 +533,24 @@ async fn inner_main() -> Result<()> {
548533 config. generator. len( )
549534 ) ;
550535
551- if let Some ( generator_id) = args. generator_id {
536+ if let Some ( ref generator_id) = args. generator_id {
552537 let generator = config
553538 . generator
554539 . iter ( )
555540 . find ( |g| {
556541 let Some ( ref id) = g. general . id else {
557542 return false ;
558543 } ;
559- * id == generator_id
544+ id == generator_id
560545 } )
561546 . ok_or_else ( || anyhow ! ( "No generator found with id: {generator_id}" ) ) ?;
562- let fingerprint = check_generator ( generator, args. fingerprint ) ?;
547+ let fingerprint = check_generator ( generator, & args) ?;
563548 if args. fingerprint
564549 && let Some ( fp) = fingerprint
565550 {
566- if let Some ( verify_path) = args. verify {
551+ if let Some ( ref verify_path) = args. verify {
567552 let expected_content =
568- fs:: read_to_string ( & verify_path) . await . with_context ( || {
553+ fs:: read_to_string ( verify_path) . await . with_context ( || {
569554 format ! ( "Could not read verify file {}" , verify_path. display( ) )
570555 } ) ?;
571556
@@ -597,19 +582,17 @@ async fn inner_main() -> Result<()> {
597582 } else {
598583 let mut all_fingerprints = Vec :: new ( ) ;
599584 for generator in config. generator {
600- let fingerprint = check_generator ( & generator, args. fingerprint ) ?;
601- if args. fingerprint
602- && let Some ( fp) = fingerprint
603- {
585+ let fingerprint = check_generator ( & generator, & args) ?;
586+ if let Some ( fp) = fingerprint {
604587 let gen_id = generator. general . id . as_deref ( ) . unwrap_or ( "<unnamed>" ) ;
605588 all_fingerprints. push ( ( gen_id. to_string ( ) , fp) ) ;
606589 }
607590 }
608591 if args. fingerprint && !all_fingerprints. is_empty ( ) {
609- if let Some ( verify_path) = args. verify {
592+ if let Some ( ref verify_path) = args. verify {
610593 // Read expected fingerprints from file
611594 let expected_content =
612- fs:: read_to_string ( & verify_path) . await . with_context ( || {
595+ fs:: read_to_string ( verify_path) . await . with_context ( || {
613596 format ! ( "Could not read verify file {}" , verify_path. display( ) )
614597 } ) ?;
615598
0 commit comments