@@ -316,7 +316,20 @@ impl Iterator for PortRangeIterator {
316316}
317317
318318/// State of a scanned port
319- #[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord , Serialize , Deserialize , Archive , RkyvSerialize , RkyvDeserialize ) ]
319+ #[ derive(
320+ Debug ,
321+ Clone ,
322+ Copy ,
323+ PartialEq ,
324+ Eq ,
325+ PartialOrd ,
326+ Ord ,
327+ Serialize ,
328+ Deserialize ,
329+ Archive ,
330+ RkyvSerialize ,
331+ RkyvDeserialize ,
332+ ) ]
320333#[ rkyv( derive( Debug ) ) ]
321334pub enum PortState {
322335 /// Port is open and accepting connections
@@ -477,29 +490,85 @@ impl fmt::Display for TimingTemplate {
477490 }
478491}
479492
480- /// Serializable representation of ScanResult for rkyv
481- #[ derive( Debug , Clone , Archive , RkyvSerialize , RkyvDeserialize ) ]
493+ /// rkyv-compatible serialization format for ScanResult
494+ ///
495+ /// This type is optimized for zero-copy deserialization using rkyv.
496+ /// It stores all data in a format that can be directly interpreted from
497+ /// memory-mapped files without allocation.
498+ ///
499+ /// # Manual Serialization for IpAddr
500+ ///
501+ /// std::net::IpAddr does not implement rkyv traits, so we manually serialize
502+ /// to bytes and convert between IpAddr and byte arrays in the From implementations.
503+ ///
504+ /// # Alignment Requirements
505+ ///
506+ /// This structure must maintain proper alignment for rkyv's zero-copy
507+ /// deserialization. The fixed-size entry buffer (512 bytes) provides
508+ /// adequate alignment for typical rkyv requirements.
509+ #[ derive( Debug , Clone , rkyv:: Archive , rkyv:: Serialize , rkyv:: Deserialize ) ]
482510#[ rkyv( derive( Debug ) ) ]
483511pub struct ScanResultRkyv {
484- target_ip : IpAddr ,
485- port : u16 ,
486- state : PortState ,
487- response_time_nanos : u128 ,
488- timestamp_nanos : i64 ,
489- banner : Option < String > ,
490- service : Option < String > ,
491- version : Option < String > ,
492- raw_response : Option < Vec < u8 > > ,
512+ /// Target IP address (16 bytes for IPv6 compatibility)
513+ pub target_ip_bytes : [ u8 ; 16 ] ,
514+ /// Whether the IP is IPv4 (true) or IPv6 (false)
515+ pub is_ipv4 : bool ,
516+ /// Port number
517+ pub port : u16 ,
518+ /// Port state as u8 (Open=0, Closed=1, Filtered=2, Unknown=3)
519+ pub state : u8 ,
520+ /// Response time in nanoseconds (u64 to avoid truncation)
521+ pub response_time_nanos : u64 ,
522+ /// Timestamp in nanoseconds since Unix epoch
523+ pub timestamp_nanos : i64 ,
524+ /// Optional banner (max 128 bytes)
525+ pub banner : Option < String > ,
526+ /// Optional service name (max 32 bytes)
527+ pub service : Option < String > ,
528+ /// Optional service version (max 64 bytes)
529+ pub version : Option < String > ,
530+ /// Optional raw response (limited to 256 bytes to fit in entry)
531+ pub raw_response : Option < Vec < u8 > > ,
493532}
494533
495534impl From < & ScanResult > for ScanResultRkyv {
496535 fn from ( result : & ScanResult ) -> Self {
536+ // Convert IpAddr to bytes
537+ let ( target_ip_bytes, is_ipv4) = match result. target_ip {
538+ IpAddr :: V4 ( ipv4) => {
539+ let mut bytes = [ 0u8 ; 16 ] ;
540+ bytes[ ..4 ] . copy_from_slice ( & ipv4. octets ( ) ) ;
541+ ( bytes, true )
542+ }
543+ IpAddr :: V6 ( ipv6) => ( ipv6. octets ( ) , false ) ,
544+ } ;
545+
546+ // Convert PortState to u8
547+ let state = match result. state {
548+ PortState :: Open => 0 ,
549+ PortState :: Closed => 1 ,
550+ PortState :: Filtered => 2 ,
551+ PortState :: Unknown => 3 ,
552+ } ;
553+
554+ // Convert response time to u64 nanoseconds (avoid truncation issues)
555+ // Note: u64 can represent up to ~584 years, which is more than sufficient
556+ // for network response times. We clamp to u64::MAX to avoid overflow.
557+ let response_time_nanos = result. response_time . as_nanos ( ) . min ( u64:: MAX as u128 ) as u64 ;
558+
559+ // Convert timestamp with proper error handling
560+ let timestamp_nanos = result
561+ . timestamp
562+ . timestamp_nanos_opt ( )
563+ . expect ( "timestamp out of range for nanosecond representation" ) ;
564+
497565 Self {
498- target_ip : result. target_ip ,
566+ target_ip_bytes,
567+ is_ipv4,
499568 port : result. port ,
500- state : result . state ,
501- response_time_nanos : result . response_time . as_nanos ( ) ,
502- timestamp_nanos : result . timestamp . timestamp_nanos_opt ( ) . unwrap_or ( 0 ) ,
569+ state,
570+ response_time_nanos,
571+ timestamp_nanos,
503572 banner : result. banner . clone ( ) ,
504573 service : result. service . clone ( ) ,
505574 version : result. version . clone ( ) ,
@@ -510,12 +579,36 @@ impl From<&ScanResult> for ScanResultRkyv {
510579
511580impl From < ScanResultRkyv > for ScanResult {
512581 fn from ( rkyv : ScanResultRkyv ) -> Self {
582+ // Convert bytes back to IpAddr
583+ let target_ip = if rkyv. is_ipv4 {
584+ let mut octets = [ 0u8 ; 4 ] ;
585+ octets. copy_from_slice ( & rkyv. target_ip_bytes [ ..4 ] ) ;
586+ IpAddr :: V4 ( std:: net:: Ipv4Addr :: from ( octets) )
587+ } else {
588+ IpAddr :: V6 ( std:: net:: Ipv6Addr :: from ( rkyv. target_ip_bytes ) )
589+ } ;
590+
591+ // Convert u8 back to PortState
592+ let state = match rkyv. state {
593+ 0 => PortState :: Open ,
594+ 1 => PortState :: Closed ,
595+ 2 => PortState :: Filtered ,
596+ _ => PortState :: Unknown ,
597+ } ;
598+
599+ // Convert u64 nanoseconds back to Duration
600+ // Safe: u64::MAX nanoseconds fits within Duration's range
601+ let response_time = Duration :: from_nanos ( rkyv. response_time_nanos ) ;
602+
603+ // Convert i64 nanoseconds back to DateTime
604+ let timestamp = DateTime :: from_timestamp_nanos ( rkyv. timestamp_nanos ) ;
605+
513606 Self {
514- target_ip : rkyv . target_ip ,
607+ target_ip,
515608 port : rkyv. port ,
516- state : rkyv . state ,
517- response_time : Duration :: from_nanos ( rkyv . response_time_nanos as u64 ) ,
518- timestamp : DateTime :: from_timestamp_nanos ( rkyv . timestamp_nanos ) ,
609+ state,
610+ response_time,
611+ timestamp,
519612 banner : rkyv. banner ,
520613 service : rkyv. service ,
521614 version : rkyv. version ,
@@ -641,128 +734,6 @@ impl fmt::Display for ScanResult {
641734 }
642735}
643736
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-
766737/// Port filtering for exclusion/inclusion lists
767738///
768739/// Provides efficient port filtering using hash sets for O(1) lookups.
0 commit comments