@@ -641,6 +641,128 @@ impl fmt::Display for ScanResult {
641641 }
642642}
643643
644+ /// rkyv-compatible serialization format for ScanResult
645+ ///
646+ /// This type is optimized for zero-copy deserialization using rkyv.
647+ /// It stores all data in a format that can be directly interpreted from
648+ /// memory-mapped files without allocation.
649+ ///
650+ /// # Alignment Requirements
651+ ///
652+ /// This structure must maintain proper alignment for rkyv's zero-copy
653+ /// deserialization. The fixed-size entry buffer (512 bytes) provides
654+ /// adequate alignment for typical rkyv requirements.
655+ #[ derive( Debug , Clone , rkyv:: Archive , rkyv:: Serialize , rkyv:: Deserialize ) ]
656+ #[ rkyv( derive( Debug ) ) ]
657+ pub struct ScanResultRkyv {
658+ /// Target IP address (16 bytes for IPv6 compatibility)
659+ pub target_ip_bytes : [ u8 ; 16 ] ,
660+ /// Whether the IP is IPv4 (true) or IPv6 (false)
661+ pub is_ipv4 : bool ,
662+ /// Port number
663+ pub port : u16 ,
664+ /// Port state as u8 (Open=0, Closed=1, Filtered=2, Unknown=3)
665+ pub state : u8 ,
666+ /// Response time in nanoseconds (u64 to avoid truncation)
667+ pub response_time_nanos : u64 ,
668+ /// Timestamp in nanoseconds since Unix epoch
669+ pub timestamp_nanos : i64 ,
670+ /// Optional banner (max 128 bytes)
671+ pub banner : Option < String > ,
672+ /// Optional service name (max 32 bytes)
673+ pub service : Option < String > ,
674+ /// Optional service version (max 64 bytes)
675+ pub version : Option < String > ,
676+ /// Optional raw response (limited to 256 bytes to fit in entry)
677+ pub raw_response : Option < Vec < u8 > > ,
678+ }
679+
680+ impl From < & ScanResult > for ScanResultRkyv {
681+ fn from ( result : & ScanResult ) -> Self {
682+ // Convert IpAddr to bytes
683+ let ( target_ip_bytes, is_ipv4) = match result. target_ip {
684+ IpAddr :: V4 ( ipv4) => {
685+ let mut bytes = [ 0u8 ; 16 ] ;
686+ bytes[ ..4 ] . copy_from_slice ( & ipv4. octets ( ) ) ;
687+ ( bytes, true )
688+ }
689+ IpAddr :: V6 ( ipv6) => ( ipv6. octets ( ) , false ) ,
690+ } ;
691+
692+ // Convert PortState to u8
693+ let state = match result. state {
694+ PortState :: Open => 0 ,
695+ PortState :: Closed => 1 ,
696+ PortState :: Filtered => 2 ,
697+ PortState :: Unknown => 3 ,
698+ } ;
699+
700+ // Convert response time to u64 nanoseconds (avoid truncation issues)
701+ // Note: u64 can represent up to ~584 years, which is more than sufficient
702+ // for network response times. We clamp to u64::MAX to avoid overflow.
703+ let response_time_nanos = result. response_time . as_nanos ( ) . min ( u64:: MAX as u128 ) as u64 ;
704+
705+ // Convert timestamp with proper error handling
706+ let timestamp_nanos = result
707+ . timestamp
708+ . timestamp_nanos_opt ( )
709+ . expect ( "timestamp out of range for nanosecond representation" ) ;
710+
711+ Self {
712+ target_ip_bytes,
713+ is_ipv4,
714+ port : result. port ,
715+ state,
716+ response_time_nanos,
717+ timestamp_nanos,
718+ banner : result. banner . clone ( ) ,
719+ service : result. service . clone ( ) ,
720+ version : result. version . clone ( ) ,
721+ raw_response : result. raw_response . clone ( ) ,
722+ }
723+ }
724+ }
725+
726+ impl From < ScanResultRkyv > for ScanResult {
727+ fn from ( rkyv : ScanResultRkyv ) -> Self {
728+ // Convert bytes back to IpAddr
729+ let target_ip = if rkyv. is_ipv4 {
730+ let mut octets = [ 0u8 ; 4 ] ;
731+ octets. copy_from_slice ( & rkyv. target_ip_bytes [ ..4 ] ) ;
732+ IpAddr :: V4 ( std:: net:: Ipv4Addr :: from ( octets) )
733+ } else {
734+ IpAddr :: V6 ( std:: net:: Ipv6Addr :: from ( rkyv. target_ip_bytes ) )
735+ } ;
736+
737+ // Convert u8 back to PortState
738+ let state = match rkyv. state {
739+ 0 => PortState :: Open ,
740+ 1 => PortState :: Closed ,
741+ 2 => PortState :: Filtered ,
742+ _ => PortState :: Unknown ,
743+ } ;
744+
745+ // Convert u64 nanoseconds back to Duration
746+ // Safe: u64::MAX nanoseconds fits within Duration's range
747+ let response_time = Duration :: from_nanos ( rkyv. response_time_nanos ) ;
748+
749+ // Convert i64 nanoseconds back to DateTime
750+ let timestamp = DateTime :: from_timestamp_nanos ( rkyv. timestamp_nanos ) ;
751+
752+ Self {
753+ target_ip,
754+ port : rkyv. port ,
755+ state,
756+ response_time,
757+ timestamp,
758+ banner : rkyv. banner ,
759+ service : rkyv. service ,
760+ version : rkyv. version ,
761+ raw_response : rkyv. raw_response ,
762+ }
763+ }
764+ }
765+
644766/// Port filtering for exclusion/inclusion lists
645767///
646768/// Provides efficient port filtering using hash sets for O(1) lookups.
0 commit comments