Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 119 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ rusqlite = { version = "0.31", features = ["bundled"] }

# Memory-mapped I/O (Sprint 6.6)
memmap2 = "0.9"
bincode = "1.3"

# Zero-copy serialization (rkyv migration)
rkyv = { version = "0.8.14", features = ["std", "alloc"] }

# CSV export (promote to workspace)
csv = "1.3"
Expand Down
1 change: 1 addition & 0 deletions crates/prtip-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ rand = { workspace = true }
sysinfo = { workspace = true }
flate2 = "1.0"
dirs = "5.0"
rkyv = { workspace = true }

[dev-dependencies]
tokio = { workspace = true }
Expand Down
5 changes: 4 additions & 1 deletion crates/prtip-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,7 @@ pub use resource_monitor::{
pub use retry::{retry_with_backoff, RetryConfig};
pub use service_db::{ServiceMatch, ServiceProbe, ServiceProbeDb};
pub use templates::{ScanTemplate, TemplateManager};
pub use types::{PortRange, PortState, Protocol, ScanResult, ScanTarget, ScanType, TimingTemplate};
pub use types::{
PortRange, PortState, Protocol, ScanResult, ScanResultRkyv, ScanTarget, ScanType,
TimingTemplate,
};
122 changes: 122 additions & 0 deletions crates/prtip-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,128 @@
}
}

/// rkyv-compatible serialization format for ScanResult
///
/// This type is optimized for zero-copy deserialization using rkyv.
/// It stores all data in a format that can be directly interpreted from
/// memory-mapped files without allocation.
///
/// # Alignment Requirements
///
/// This structure must maintain proper alignment for rkyv's zero-copy
/// deserialization. The fixed-size entry buffer (512 bytes) provides
/// adequate alignment for typical rkyv requirements.
#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[rkyv(derive(Debug))]
pub struct ScanResultRkyv {
/// Target IP address (16 bytes for IPv6 compatibility)
pub target_ip_bytes: [u8; 16],
/// Whether the IP is IPv4 (true) or IPv6 (false)
pub is_ipv4: bool,
/// Port number
pub port: u16,
/// Port state as u8 (Open=0, Closed=1, Filtered=2, Unknown=3)
pub state: u8,
/// Response time in nanoseconds (u64 to avoid truncation)
pub response_time_nanos: u64,
/// Timestamp in nanoseconds since Unix epoch
pub timestamp_nanos: i64,
/// Optional banner (max 128 bytes)
pub banner: Option<String>,
/// Optional service name (max 32 bytes)
pub service: Option<String>,
/// Optional service version (max 64 bytes)
pub version: Option<String>,
/// Optional raw response (limited to 256 bytes to fit in entry)
pub raw_response: Option<Vec<u8>>,
}

impl From<&ScanResult> for ScanResultRkyv {
fn from(result: &ScanResult) -> Self {

Check warning on line 632 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L632

Added line #L632 was not covered by tests
// Convert IpAddr to bytes
let (target_ip_bytes, is_ipv4) = match result.target_ip {
IpAddr::V4(ipv4) => {
let mut bytes = [0u8; 16];
bytes[..4].copy_from_slice(&ipv4.octets());
(bytes, true)

Check warning on line 638 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L634-L638

Added lines #L634 - L638 were not covered by tests
}
IpAddr::V6(ipv6) => (ipv6.octets(), false),

Check warning on line 640 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L640

Added line #L640 was not covered by tests
};

// Convert PortState to u8
let state = match result.state {
PortState::Open => 0,
PortState::Closed => 1,
PortState::Filtered => 2,
PortState::Unknown => 3,

Check warning on line 648 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L644-L648

Added lines #L644 - L648 were not covered by tests
};

// Convert response time to u64 nanoseconds (avoid truncation issues)
// Note: u64 can represent up to ~584 years, which is more than sufficient
// for network response times. We clamp to u64::MAX to avoid overflow.
let response_time_nanos = result.response_time.as_nanos().min(u64::MAX as u128) as u64;

Check warning on line 654 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L654

Added line #L654 was not covered by tests

// Convert timestamp with proper error handling
let timestamp_nanos = result
.timestamp

Check warning on line 658 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L657-L658

Added lines #L657 - L658 were not covered by tests
.timestamp_nanos_opt()
.expect("timestamp out of range for nanosecond representation");

Self {
target_ip_bytes,
is_ipv4,
port: result.port,

Check warning on line 665 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L665

Added line #L665 was not covered by tests
state,
response_time_nanos,
timestamp_nanos,
banner: result.banner.clone(),
service: result.service.clone(),
version: result.version.clone(),
raw_response: result.raw_response.clone(),

Check warning on line 672 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L669-L672

Added lines #L669 - L672 were not covered by tests
}
}
}

impl From<ScanResultRkyv> for ScanResult {
fn from(rkyv: ScanResultRkyv) -> Self {

Check warning on line 678 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L678

Added line #L678 was not covered by tests
// Convert bytes back to IpAddr
let target_ip = if rkyv.is_ipv4 {
let mut octets = [0u8; 4];
octets.copy_from_slice(&rkyv.target_ip_bytes[..4]);
IpAddr::V4(std::net::Ipv4Addr::from(octets))

Check warning on line 683 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L680-L683

Added lines #L680 - L683 were not covered by tests
} else {
IpAddr::V6(std::net::Ipv6Addr::from(rkyv.target_ip_bytes))

Check warning on line 685 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L685

Added line #L685 was not covered by tests
};

// Convert u8 back to PortState
let state = match rkyv.state {
0 => PortState::Open,
1 => PortState::Closed,
2 => PortState::Filtered,
_ => PortState::Unknown,

Check warning on line 693 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L689-L693

Added lines #L689 - L693 were not covered by tests
};

// Convert u64 nanoseconds back to Duration
// Safe: u64::MAX nanoseconds fits within Duration's range
let response_time = Duration::from_nanos(rkyv.response_time_nanos);

Check warning on line 698 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L698

Added line #L698 was not covered by tests

// Convert i64 nanoseconds back to DateTime
let timestamp = DateTime::from_timestamp_nanos(rkyv.timestamp_nanos);

Check warning on line 701 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L701

Added line #L701 was not covered by tests

Self {
target_ip,
port: rkyv.port,

Check warning on line 705 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L705

Added line #L705 was not covered by tests
state,
response_time,
timestamp,
banner: rkyv.banner,
service: rkyv.service,
version: rkyv.version,
raw_response: rkyv.raw_response,

Check warning on line 712 in crates/prtip-core/src/types.rs

View check run for this annotation

Codecov / codecov/patch

crates/prtip-core/src/types.rs#L709-L712

Added lines #L709 - L712 were not covered by tests
}
}
}

/// Port filtering for exclusion/inclusion lists
///
/// Provides efficient port filtering using hash sets for O(1) lookups.
Expand Down
2 changes: 1 addition & 1 deletion crates/prtip-scanner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pcap-file = "2.0"

# Memory-mapped I/O
memmap2 = { workspace = true }
bincode = { workspace = true }
rkyv = { workspace = true }

[dev-dependencies]
tokio = { workspace = true }
Expand Down
Loading
Loading