@@ -592,6 +592,128 @@ impl fmt::Display for ScanResult {
592592 }
593593}
594594
595+ /// rkyv-compatible serialization format for ScanResult
596+ ///
597+ /// This type is optimized for zero-copy deserialization using rkyv.
598+ /// It stores all data in a format that can be directly interpreted from
599+ /// memory-mapped files without allocation.
600+ ///
601+ /// # Alignment Requirements
602+ ///
603+ /// This structure must maintain proper alignment for rkyv's zero-copy
604+ /// deserialization. The fixed-size entry buffer (512 bytes) provides
605+ /// adequate alignment for typical rkyv requirements.
606+ #[ derive( Debug , Clone , rkyv:: Archive , rkyv:: Serialize , rkyv:: Deserialize ) ]
607+ #[ rkyv( derive( Debug ) ) ]
608+ pub struct ScanResultRkyv {
609+ /// Target IP address (16 bytes for IPv6 compatibility)
610+ pub target_ip_bytes : [ u8 ; 16 ] ,
611+ /// Whether the IP is IPv4 (true) or IPv6 (false)
612+ pub is_ipv4 : bool ,
613+ /// Port number
614+ pub port : u16 ,
615+ /// Port state as u8 (Open=0, Closed=1, Filtered=2, Unknown=3)
616+ pub state : u8 ,
617+ /// Response time in nanoseconds (u64 to avoid truncation)
618+ pub response_time_nanos : u64 ,
619+ /// Timestamp in nanoseconds since Unix epoch
620+ pub timestamp_nanos : i64 ,
621+ /// Optional banner (max 128 bytes)
622+ pub banner : Option < String > ,
623+ /// Optional service name (max 32 bytes)
624+ pub service : Option < String > ,
625+ /// Optional service version (max 64 bytes)
626+ pub version : Option < String > ,
627+ /// Optional raw response (limited to 256 bytes to fit in entry)
628+ pub raw_response : Option < Vec < u8 > > ,
629+ }
630+
631+ impl From < & ScanResult > for ScanResultRkyv {
632+ fn from ( result : & ScanResult ) -> Self {
633+ // Convert IpAddr to bytes
634+ let ( target_ip_bytes, is_ipv4) = match result. target_ip {
635+ IpAddr :: V4 ( ipv4) => {
636+ let mut bytes = [ 0u8 ; 16 ] ;
637+ bytes[ ..4 ] . copy_from_slice ( & ipv4. octets ( ) ) ;
638+ ( bytes, true )
639+ }
640+ IpAddr :: V6 ( ipv6) => ( ipv6. octets ( ) , false ) ,
641+ } ;
642+
643+ // Convert PortState to u8
644+ let state = match result. state {
645+ PortState :: Open => 0 ,
646+ PortState :: Closed => 1 ,
647+ PortState :: Filtered => 2 ,
648+ PortState :: Unknown => 3 ,
649+ } ;
650+
651+ // Convert response time to u64 nanoseconds (avoid truncation issues)
652+ // Note: u64 can represent up to ~584 years, which is more than sufficient
653+ // for network response times. We clamp to u64::MAX to avoid overflow.
654+ let response_time_nanos = result. response_time . as_nanos ( ) . min ( u64:: MAX as u128 ) as u64 ;
655+
656+ // Convert timestamp with proper error handling
657+ let timestamp_nanos = result
658+ . timestamp
659+ . timestamp_nanos_opt ( )
660+ . expect ( "timestamp out of range for nanosecond representation" ) ;
661+
662+ Self {
663+ target_ip_bytes,
664+ is_ipv4,
665+ port : result. port ,
666+ state,
667+ response_time_nanos,
668+ timestamp_nanos,
669+ banner : result. banner . clone ( ) ,
670+ service : result. service . clone ( ) ,
671+ version : result. version . clone ( ) ,
672+ raw_response : result. raw_response . clone ( ) ,
673+ }
674+ }
675+ }
676+
677+ impl From < ScanResultRkyv > for ScanResult {
678+ fn from ( rkyv : ScanResultRkyv ) -> Self {
679+ // Convert bytes back to IpAddr
680+ let target_ip = if rkyv. is_ipv4 {
681+ let mut octets = [ 0u8 ; 4 ] ;
682+ octets. copy_from_slice ( & rkyv. target_ip_bytes [ ..4 ] ) ;
683+ IpAddr :: V4 ( std:: net:: Ipv4Addr :: from ( octets) )
684+ } else {
685+ IpAddr :: V6 ( std:: net:: Ipv6Addr :: from ( rkyv. target_ip_bytes ) )
686+ } ;
687+
688+ // Convert u8 back to PortState
689+ let state = match rkyv. state {
690+ 0 => PortState :: Open ,
691+ 1 => PortState :: Closed ,
692+ 2 => PortState :: Filtered ,
693+ _ => PortState :: Unknown ,
694+ } ;
695+
696+ // Convert u64 nanoseconds back to Duration
697+ // Safe: u64::MAX nanoseconds fits within Duration's range
698+ let response_time = Duration :: from_nanos ( rkyv. response_time_nanos ) ;
699+
700+ // Convert i64 nanoseconds back to DateTime
701+ let timestamp = DateTime :: from_timestamp_nanos ( rkyv. timestamp_nanos ) ;
702+
703+ Self {
704+ target_ip,
705+ port : rkyv. port ,
706+ state,
707+ response_time,
708+ timestamp,
709+ banner : rkyv. banner ,
710+ service : rkyv. service ,
711+ version : rkyv. version ,
712+ raw_response : rkyv. raw_response ,
713+ }
714+ }
715+ }
716+
595717/// Port filtering for exclusion/inclusion lists
596718///
597719/// Provides efficient port filtering using hash sets for O(1) lookups.
0 commit comments