@@ -193,7 +193,7 @@ pub fn local_init_td_info() -> Result<[u8; TD_INFO_SIZE], MigrationResult> {
193193/// Size of the fixed `MigtdMigrationInformation` header preceding the
194194/// optional `init_td_info` tail (per GHCI 1.5 StartMigration layout).
195195#[ cfg( feature = "policy_v2" ) ]
196- const MIGTD_MIGRATION_INFO_HEADER_SIZE : usize =
196+ pub ( crate ) const MIGTD_MIGRATION_INFO_HEADER_SIZE : usize =
197197 core:: mem:: size_of :: < MigtdMigrationInformation > ( ) - TD_INFO_SIZE ;
198198
199199#[ repr( C ) ]
@@ -309,9 +309,9 @@ impl MigtdMigrationInformation {
309309 if parsed. has_init_data > 1 {
310310 return Err ( MigrationResult :: InvalidParameter ) ;
311311 }
312- // has_init_data == 1 requires full-size payload; full-size payload with
313- // has_init_data == 0 is accepted (e.g. HOBs always serialize full struct) .
314- if parsed. has_init_data == 1 && data_length != full_size {
312+ // has_init_data and data_length must be consistent: the full-size
313+ // payload is required iff has_init_data == 1 .
314+ if ( parsed. has_init_data == 1 ) != ( data_length == full_size) {
315315 return Err ( MigrationResult :: InvalidParameter ) ;
316316 }
317317 Ok ( parsed)
@@ -548,3 +548,136 @@ impl From<TimeoutError> for MigrationResult {
548548 MigrationResult :: NetworkError
549549 }
550550}
551+
552+ #[ cfg( test) ]
553+ #[ cfg( feature = "policy_v2" ) ]
554+ mod test {
555+ use super :: * ;
556+ use alloc:: vec;
557+
558+ /// Create a 512-byte TDINFO_STRUCT with known mrowner and mrownerconfig.
559+ fn make_tdinfo ( mrowner : & [ u8 ; 48 ] , mrownerconfig : & [ u8 ; 48 ] ) -> [ u8 ; TD_INFO_SIZE ] {
560+ let mut tdinfo = [ 0u8 ; TD_INFO_SIZE ] ;
561+ // mrowner at offset 112..160
562+ tdinfo[ 112 ..160 ] . copy_from_slice ( mrowner) ;
563+ // mrownerconfig at offset 160..208
564+ tdinfo[ 160 ..208 ] . copy_from_slice ( mrownerconfig) ;
565+ tdinfo
566+ }
567+
568+ /// Build a MigtdMigrationInformation byte buffer matching the active
569+ /// feature layout. Under non-vmcall-raw, `mig_policy_id` and
570+ /// `communication_id` (zero-initialized) are inserted after
571+ /// `binding_handle`. When `init_tdinfo` is `Some`, the raw TDINFO bytes
572+ /// are appended directly (no envelope, per the unified wire contract).
573+ fn build_mig_info (
574+ mig_request_id : u64 ,
575+ migration_source : u8 ,
576+ has_init_data : u8 ,
577+ uuid : [ u64 ; 4 ] ,
578+ binding_handle : u64 ,
579+ init_tdinfo : Option < & [ u8 ] > ,
580+ ) -> Vec < u8 > {
581+ let mut buf = Vec :: new ( ) ;
582+ buf. extend_from_slice ( & mig_request_id. to_le_bytes ( ) ) ; // 0..8
583+ buf. push ( migration_source) ; // 8
584+ buf. push ( has_init_data) ; // 9
585+ buf. extend_from_slice ( & [ 0u8 ; 6 ] ) ; // 10..16 reserved
586+ for u in & uuid {
587+ buf. extend_from_slice ( & u. to_le_bytes ( ) ) ; // 16..48
588+ }
589+ buf. extend_from_slice ( & binding_handle. to_le_bytes ( ) ) ; // 48..56
590+ #[ cfg( not( feature = "vmcall-raw" ) ) ]
591+ {
592+ buf. extend_from_slice ( & 0u64 . to_le_bytes ( ) ) ; // mig_policy_id 56..64
593+ buf. extend_from_slice ( & 0u64 . to_le_bytes ( ) ) ; // communication_id 64..72
594+ }
595+ if let Some ( data) = init_tdinfo {
596+ buf. extend_from_slice ( data) ;
597+ }
598+ buf
599+ }
600+
601+ #[ test]
602+ fn test_mig_info_no_init_data ( ) {
603+ let buf = build_mig_info ( 42 , 1 , 0 , [ 1 , 2 , 3 , 4 ] , 99 , None ) ;
604+ let info = MigtdMigrationInformation :: read_from_bytes ( buf. len ( ) as u32 , & buf)
605+ . expect ( "should parse" ) ;
606+ assert_eq ! ( info. mig_request_id, 42 ) ;
607+ assert_eq ! ( info. migration_source, 1 ) ;
608+ assert_eq ! ( info. has_init_data, 0 ) ;
609+ assert_eq ! ( info. target_td_uuid, [ 1 , 2 , 3 , 4 ] ) ;
610+ assert_eq ! ( info. binding_handle, 99 ) ;
611+ assert ! ( info. init_td_info_if_present( ) . is_none( ) ) ;
612+ // Padded tail is zero.
613+ assert_eq ! ( info. init_td_info, [ 0u8 ; TD_INFO_SIZE ] ) ;
614+ }
615+
616+ #[ test]
617+ fn test_mig_info_with_init_data ( ) {
618+ let tdinfo = make_tdinfo ( & [ 0xCAu8 ; 48 ] , & [ 0xFEu8 ; 48 ] ) ;
619+ let buf = build_mig_info ( 7 , 0 , 1 , [ 10 , 20 , 30 , 40 ] , 55 , Some ( & tdinfo) ) ;
620+ let info = MigtdMigrationInformation :: read_from_bytes ( buf. len ( ) as u32 , & buf)
621+ . expect ( "should parse with init data" ) ;
622+ assert_eq ! ( info. mig_request_id, 7 ) ;
623+ assert_eq ! ( info. has_init_data, 1 ) ;
624+ let init = info. init_td_info_if_present ( ) . expect ( "should be present" ) ;
625+ assert_eq ! ( init, & tdinfo) ;
626+ assert_eq ! ( td_info_mrowner( init) , & [ 0xCAu8 ; 48 ] ) ;
627+ assert_eq ! ( td_info_mrownerconfig( init) , & [ 0xFEu8 ; 48 ] ) ;
628+ }
629+
630+ #[ test]
631+ fn test_mig_info_rejects_short_buffer ( ) {
632+ // Anything shorter than the layout's short-form header must be rejected.
633+ let too_short = MIGTD_MIGRATION_INFO_HEADER_SIZE - 1 ;
634+ assert ! ( MigtdMigrationInformation :: read_from_bytes( 10 , & [ 0u8 ; 10 ] ) . is_err( ) ) ;
635+ assert ! ( MigtdMigrationInformation :: read_from_bytes(
636+ too_short as u32 ,
637+ & vec![ 0u8 ; too_short]
638+ )
639+ . is_err( ) ) ;
640+ }
641+
642+ #[ test]
643+ fn test_mig_info_rejects_nonzero_reserved ( ) {
644+ let mut buf = build_mig_info ( 1 , 0 , 0 , [ 0 ; 4 ] , 0 , None ) ;
645+ buf[ 10 ] = 0xFF ; // reserved byte not zero
646+ assert ! ( MigtdMigrationInformation :: read_from_bytes( buf. len( ) as u32 , & buf) . is_err( ) ) ;
647+ }
648+
649+ #[ test]
650+ fn test_mig_info_rejects_has_init_data_without_tail ( ) {
651+ // has_init_data=1 but no tail bytes following → flag/length mismatch
652+ let buf = build_mig_info ( 1 , 0 , 1 , [ 0 ; 4 ] , 0 , None ) ;
653+ assert ! ( MigtdMigrationInformation :: read_from_bytes( buf. len( ) as u32 , & buf) . is_err( ) ) ;
654+ }
655+
656+ #[ test]
657+ fn test_mig_info_rejects_full_form_without_flag ( ) {
658+ // has_init_data=0 but full-size buffer (with init_td_info tail) → flag/length mismatch
659+ let tdinfo = [ 0u8 ; TD_INFO_SIZE ] ;
660+ let buf = build_mig_info ( 1 , 0 , 0 , [ 0 ; 4 ] , 0 , Some ( & tdinfo) ) ;
661+ assert ! ( MigtdMigrationInformation :: read_from_bytes( buf. len( ) as u32 , & buf) . is_err( ) ) ;
662+ }
663+
664+ #[ test]
665+ fn test_mig_info_rejects_invalid_has_init_data ( ) {
666+ // has_init_data must be 0 or 1
667+ let mut buf = build_mig_info ( 1 , 0 , 0 , [ 0 ; 4 ] , 0 , None ) ;
668+ buf[ 9 ] = 2 ;
669+ assert ! ( MigtdMigrationInformation :: read_from_bytes( buf. len( ) as u32 , & buf) . is_err( ) ) ;
670+ buf[ 9 ] = 0xFF ;
671+ assert ! ( MigtdMigrationInformation :: read_from_bytes( buf. len( ) as u32 , & buf) . is_err( ) ) ;
672+ }
673+
674+ #[ test]
675+ fn test_mig_info_rejects_unexpected_length ( ) {
676+ // Only short or full lengths are accepted; mid-sized buffers are rejected.
677+ let mid = MIGTD_MIGRATION_INFO_HEADER_SIZE + 1 ;
678+ let mid_buf = vec ! [ 0u8 ; mid] ;
679+ assert ! (
680+ MigtdMigrationInformation :: read_from_bytes( mid_buf. len( ) as u32 , & mid_buf) . is_err( )
681+ ) ;
682+ }
683+ }
0 commit comments