4747
4848use crate :: CorimTemplate ;
4949use crate :: Error ;
50+ use crate :: IgvmDirectiveHeader ;
5051use crate :: IgvmFile ;
5152use crate :: IgvmInitializationHeader ;
5253use crate :: IgvmPlatformHeader ;
@@ -79,6 +80,7 @@ pub struct IgvmSerializer<'a> {
7980 file : & ' a IgvmFile ,
8081 measurements : Vec < IgvmPlatformMeasurement > ,
8182 extra_init_headers : Vec < IgvmInitializationHeader > ,
83+ extra_directive_headers : Vec < IgvmDirectiveHeader > ,
8284}
8385
8486impl < ' a > IgvmSerializer < ' a > {
@@ -101,6 +103,7 @@ impl<'a> IgvmSerializer<'a> {
101103 file,
102104 measurements : Vec :: new ( ) ,
103105 extra_init_headers : Vec :: new ( ) ,
106+ extra_directive_headers : Vec :: new ( ) ,
104107 } ;
105108
106109 // Eagerly compute the launch measurement for every supported
@@ -367,21 +370,40 @@ impl<'a> IgvmSerializer<'a> {
367370 . map_err ( |e| Error :: CorimGeneration ( e. to_string ( ) ) )
368371 }
369372
373+ /// Attach an extra directive header that will be appended when
374+ /// serializing.
375+ ///
376+ /// This allows callers to stage workflows that need the already-cached
377+ /// launch measurement from [`new`](Self::new), then add a directive
378+ /// derived from that measurement (for example,
379+ /// [`IgvmDirectiveHeader::SnpIdBlock`]) before the final serialize.
380+ ///
381+ /// Added directives are not included in the measurement cache computed
382+ /// during [`new`](Self::new).
383+ pub fn add_directive ( & mut self , directive : IgvmDirectiveHeader ) -> & IgvmDirectiveHeader {
384+ self . extra_directive_headers . push ( directive) ;
385+ self . extra_directive_headers
386+ . last ( )
387+ . expect ( "just pushed a directive" )
388+ }
389+
370390 /// Serialize the IGVM file to binary format, including any CoRIM
371391 /// documents that were added via [`add_corim`](Self::add_corim).
372392 ///
373393 /// This produces the same binary format as [`IgvmFile::serialize`],
374- /// but with additional initialization headers appended.
394+ /// but with additional initialization headers and directives appended.
375395 pub fn serialize ( & self , output : & mut Vec < u8 > ) -> Result < ( ) , Error > {
376- if self . extra_init_headers . is_empty ( ) {
396+ if self . extra_init_headers . is_empty ( ) && self . extra_directive_headers . is_empty ( ) {
377397 // Fast path: nothing added, delegate directly.
378398 self . file . serialize ( output)
379399 } else {
380- // Clone the file and append the extra init headers so that
400+ // Clone the file and append the extra headers so that
381401 // the original IgvmFile::serialize handles all the work.
382402 let mut file = self . file . clone ( ) ;
383403 file. initializations_mut ( )
384404 . extend ( self . extra_init_headers . iter ( ) . cloned ( ) ) ;
405+ file. directives_mut ( )
406+ . extend ( self . extra_directive_headers . iter ( ) . cloned ( ) ) ;
385407 file. serialize ( output)
386408 }
387409 }
@@ -400,6 +422,8 @@ mod tests {
400422 use igvm_defs:: IgvmPageDataFlags ;
401423 use igvm_defs:: IgvmPageDataType ;
402424 use igvm_defs:: IgvmPlatformType ;
425+ use igvm_defs:: IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY ;
426+ use igvm_defs:: IGVM_VHS_SNP_ID_BLOCK_SIGNATURE ;
403427 use igvm_defs:: IGVM_VHS_SUPPORTED_PLATFORM ;
404428 use igvm_defs:: PAGE_SIZE_4K ;
405429
@@ -476,6 +500,41 @@ mod tests {
476500 . unwrap ( )
477501 }
478502
503+ fn new_snp_id_block ( mask : u32 , ld : [ u8 ; 48 ] ) -> crate :: IgvmDirectiveHeader {
504+ crate :: IgvmDirectiveHeader :: SnpIdBlock {
505+ compatibility_mask : mask,
506+ author_key_enabled : 0 ,
507+ reserved : [ 0 ; 3 ] ,
508+ ld,
509+ family_id : [ 0 ; 16 ] ,
510+ image_id : [ 0 ; 16 ] ,
511+ version : 1 ,
512+ guest_svn : 1 ,
513+ id_key_algorithm : 0 ,
514+ author_key_algorithm : 0 ,
515+ id_key_signature : Box :: new ( IGVM_VHS_SNP_ID_BLOCK_SIGNATURE {
516+ r_comp : [ 0 ; 72 ] ,
517+ s_comp : [ 0 ; 72 ] ,
518+ } ) ,
519+ id_public_key : Box :: new ( IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY {
520+ curve : 0 ,
521+ reserved : 0 ,
522+ qx : [ 0 ; 72 ] ,
523+ qy : [ 0 ; 72 ] ,
524+ } ) ,
525+ author_key_signature : Box :: new ( IGVM_VHS_SNP_ID_BLOCK_SIGNATURE {
526+ r_comp : [ 0 ; 72 ] ,
527+ s_comp : [ 0 ; 72 ] ,
528+ } ) ,
529+ author_public_key : Box :: new ( IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY {
530+ curve : 0 ,
531+ reserved : 0 ,
532+ qx : [ 0 ; 72 ] ,
533+ qy : [ 0 ; 72 ] ,
534+ } ) ,
535+ }
536+ }
537+
479538 // -- Basic serializer tests --------------------------------------
480539
481540 #[ test]
@@ -688,6 +747,56 @@ mod tests {
688747 assert_eq ! ( file. initializations( ) . len( ) , init_count_before) ;
689748 }
690749
750+ #[ test]
751+ fn add_directive_snp_id_block_appended_and_measurement_unchanged ( ) {
752+ let file = make_snp_file ( ) ;
753+ let mut serializer = IgvmSerializer :: new ( & file) . unwrap ( ) ;
754+
755+ // Capture the eagerly-computed SNP launch digest (stage 1).
756+ let digest_before = serializer
757+ . measurement_for ( IgvmPlatformType :: SEV_SNP )
758+ . expect ( "SNP measurement should be present" )
759+ . digest
760+ . clone ( ) ;
761+ let mut ld = [ 0u8 ; 48 ] ;
762+ ld. copy_from_slice ( & digest_before) ;
763+
764+ // Stage 2: add a synthetic SNP ID block built from that digest.
765+ serializer. add_directive ( new_snp_id_block ( 0x1 , ld) ) ;
766+
767+ // Cached measurement must remain the one computed during `new`.
768+ let digest_after = serializer
769+ . measurement_for ( IgvmPlatformType :: SEV_SNP )
770+ . expect ( "SNP measurement should still be present" )
771+ . digest
772+ . clone ( ) ;
773+ assert_eq ! ( digest_before, digest_after) ;
774+
775+ // Final serialize should include the extra directive exactly once.
776+ let mut output = Vec :: new ( ) ;
777+ serializer. serialize ( & mut output) . unwrap ( ) ;
778+
779+ let deserialized = IgvmFile :: new_from_binary ( & output, None ) . unwrap ( ) ;
780+ let id_blocks = deserialized
781+ . directives ( )
782+ . iter ( )
783+ . filter ( |h| matches ! ( h, crate :: IgvmDirectiveHeader :: SnpIdBlock { .. } ) )
784+ . count ( ) ;
785+ assert_eq ! ( id_blocks, 1 ) ;
786+ }
787+
788+ #[ test]
789+ fn file_not_mutated_after_add_directive ( ) {
790+ let file = make_snp_file ( ) ;
791+ let directive_count_before = file. directives ( ) . len ( ) ;
792+
793+ let mut serializer = IgvmSerializer :: new ( & file) . unwrap ( ) ;
794+ serializer. add_directive ( new_snp_id_block ( 0x1 , [ 0x11 ; 48 ] ) ) ;
795+
796+ // The original file should not have been mutated.
797+ assert_eq ! ( file. directives( ) . len( ) , directive_count_before) ;
798+ }
799+
691800 // -- Two-stage builder tests -------------------------------------
692801
693802 #[ test]
0 commit comments