From 5764aad91c9b3e5bcc48326282d9d5ede131ec7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:22:01 +0000 Subject: [PATCH 01/11] chore(deps): bump the cargo group across 2 directories with 1 update Bumps the cargo group with 1 update in the / directory: [rsa](https://github.com/RustCrypto/RSA). Bumps the cargo group with 1 update in the /fuzz directory: [rsa](https://github.com/RustCrypto/RSA). Updates `rsa` from 0.9.9 to 0.9.10 - [Changelog](https://github.com/RustCrypto/RSA/blob/v0.9.10/CHANGELOG.md) - [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.9...v0.9.10) Updates `rsa` from 0.9.8 to 0.9.10 - [Changelog](https://github.com/RustCrypto/RSA/blob/v0.9.10/CHANGELOG.md) - [Commits](https://github.com/RustCrypto/RSA/compare/v0.9.9...v0.9.10) --- updated-dependencies: - dependency-name: rsa dependency-version: 0.9.10 dependency-type: indirect dependency-group: cargo - dependency-name: rsa dependency-version: 0.9.10 dependency-type: indirect dependency-group: cargo ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 +- fuzz/Cargo.lock | 220 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 215 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eacc487..f2feb6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2587,9 +2587,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 2cd6569..299962f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -152,6 +158,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.10.0" @@ -170,6 +185,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -297,6 +322,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -478,6 +512,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -510,6 +565,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "equivalent" version = "1.0.2" @@ -569,6 +630,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.1" @@ -1110,6 +1181,25 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "lua-src" +version = "548.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdc4e1aff422ad5f08cffb4719603dcdbc2be2307f4c1510d7aab74b7fa88ca8" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.6.5+7152e15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e64ac463f01a02ee793423f9b351369cf244c5ee8bb9e2729a75b2eb404181" +dependencies = [ + "cc", + "which", +] + [[package]] name = "md-5" version = "0.10.6" @@ -1126,12 +1216,31 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.0" @@ -1143,6 +1252,35 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mlua" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "935ac67539907efcd7198137eb7358e052555f77fe1b2916600a2249351f2b33" +dependencies = [ + "bstr", + "either", + "libc", + "mlua-sys", + "num-traits", + "parking_lot", + "rustc-hash", + "rustversion", +] + +[[package]] +name = "mlua-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c968af21bf6b19fc9ca8e7b85ee16f86e4c9e3d0591de101a5608086bda0ad8" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -1220,9 +1358,9 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ "lazy_static", "libm", @@ -1344,6 +1482,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "parking" version = "2.2.1" @@ -1570,12 +1714,15 @@ dependencies = [ [[package]] name = "prtip-core" -version = "0.4.6" +version = "0.5.9" dependencies = [ "anyhow", "chrono", + "dirs", + "flate2", "indicatif", "ipnetwork", + "parking_lot", "rand", "regex", "rlimit", @@ -1586,6 +1733,7 @@ dependencies = [ "tokio", "toml", "tracing", + "uuid", ] [[package]] @@ -1603,9 +1751,10 @@ dependencies = [ [[package]] name = "prtip-network" -version = "0.4.6" +version = "0.5.9" dependencies = [ "anyhow", + "bytes", "etherparse", "libc", "nix", @@ -1624,9 +1773,10 @@ dependencies = [ [[package]] name = "prtip-scanner" -version = "0.4.6" +version = "0.5.9" dependencies = [ "anyhow", + "bincode", "chrono", "crossbeam", "dashmap 6.1.0", @@ -1635,6 +1785,8 @@ dependencies = [ "governor", "indicatif", "ipnetwork", + "memmap2", + "mlua", "native-tls", "nonzero_ext", "parking_lot", @@ -1654,7 +1806,9 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", + "toml", "tracing", + "uuid", "webpki-roots", "x509-parser 0.15.1", ] @@ -1757,6 +1911,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "regex" version = "1.12.2" @@ -1811,9 +1976,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -1829,6 +1994,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -2044,6 +2215,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.11" @@ -2692,6 +2869,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2796,6 +2985,17 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix", + "winsafe", +] + [[package]] name = "whoami" version = "1.6.1" @@ -3146,6 +3346,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen" version = "0.46.0" From 05430f9398d5d7c22d1a5641e373bdec0af48242 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:10:35 +0000 Subject: [PATCH 02/11] Initial plan From 9ee6330a42e92329c3e38f6d13c7afb6074ca884 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:14:30 +0000 Subject: [PATCH 03/11] feat: Add Copilot custom instructions and CI analysis report - Created .github/copilot-instructions.md with comprehensive project guidance - Created .github/CI-FIX-REPORT.md documenting CI status and fixes - Analyzed failing Gemini Dispatch workflow: root cause is missing API credentials - All critical CI checks (build, test, security) are passing Co-authored-by: doublegate <6858123+doublegate@users.noreply.github.com> --- .github/CI-FIX-REPORT.md | 201 ++++++++++++++++++ .github/copilot-instructions.md | 365 ++++++++++++++++++++++++++++++++ 2 files changed, 566 insertions(+) create mode 100644 .github/CI-FIX-REPORT.md create mode 100644 .github/copilot-instructions.md diff --git a/.github/CI-FIX-REPORT.md b/.github/CI-FIX-REPORT.md new file mode 100644 index 0000000..a8ca0f8 --- /dev/null +++ b/.github/CI-FIX-REPORT.md @@ -0,0 +1,201 @@ +# CI Fix Report for Dependabot PR #18 + +## Analysis Date +2026-01-24 + +## Executive Summary + +Analyzed all failing CI checks for the Dependabot PR that bumps `rsa` from 0.9.9 to 0.9.10. + +**Status**: Only 1 workflow is failing, and it's due to missing API credentials (expected/configuration issue, not a code problem). + +## Workflow Status Summary + +### ✅ Passing Workflows (3/4) + +1. **CI Workflow** - ✅ SUCCESS + - Run ID: 20757846798 + - All build, test, and lint jobs passed + - Platform matrix: Linux, macOS, Windows + - Duration: ~20 minutes + +2. **CodeQL** - ✅ SUCCESS + - Run ID: 20757846807 + - Security scanning completed successfully + - No vulnerabilities detected + +3. **Dependency Review** - ✅ SUCCESS + - Run ID: 20757846869 + - Dependency changes validated + - No security issues with rsa 0.9.10 update + +### ❌ Failing Workflow (1/4) + +4. **Gemini Dispatch (🔀)** - ❌ FAILURE + - Run ID: 20757846808 + - **Root Cause**: Missing API credentials + - **Error**: "Please set an Auth method in your /home/runner/.gemini/settings.json or specify one of the following environment variables before running: GEMINI_API_KEY, GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_GENAI_USE_GCA" + +## Detailed Analysis: Gemini Dispatch Failure + +### What is Gemini Dispatch? + +The Gemini Dispatch workflow is an AI-powered code review system that: +- Automatically reviews pull requests when opened +- Responds to `@gemini-cli` mentions in comments +- Provides intelligent code review feedback +- Triages issues automatically + +### Why is it Failing? + +The workflow requires authentication to Google's Gemini AI service. It needs **ONE** of the following configured: + +#### Option 1: Direct API Key (Simplest) +- **Required Secret**: `GEMINI_API_KEY` +- **How to get it**: + 1. Visit https://aistudio.google.com/app/apikey + 2. Create a new API key + 3. Add it to GitHub Secrets as `GEMINI_API_KEY` + +#### Option 2: Google Cloud Vertex AI (Enterprise) +- **Required Variables**: + - `GOOGLE_GENAI_USE_VERTEXAI=true` + - `GOOGLE_CLOUD_PROJECT` (your GCP project ID) + - `GOOGLE_CLOUD_LOCATION` (e.g., "us-central1") + - `GCP_WIF_PROVIDER` (Workload Identity Federation provider) + - `SERVICE_ACCOUNT_EMAIL` (GCP service account) +- **Use Case**: Enterprise deployments with existing GCP infrastructure + +#### Option 3: Google Code Assist (Enterprise) +- **Required Variables**: + - `GOOGLE_GENAI_USE_GCA=true` + - Additional GCP configuration +- **Use Case**: Organizations using Google Cloud Code Assist + +### Current Configuration Status + +```yaml +# From .github/workflows/gemini-review.yml +gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' # ❌ NOT SET +use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' # ❌ NOT SET +use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' # ❌ NOT SET +``` + +**Result**: No authentication method is configured, causing the workflow to fail. + +## Is This a Problem? + +**No, this is expected behavior for this type of workflow.** + +### Why This Failure is Acceptable + +1. **Not a Code Issue**: The rsa dependency update itself is fine - all actual CI tests pass +2. **Optional Feature**: Gemini code review is a nice-to-have, not a requirement for merging +3. **Configuration Required**: This requires repository admin access to configure secrets +4. **Security Best Practice**: It's better to fail safely than to expose API keys or skip authentication + +### Impact Assessment + +- **Code Quality**: ✅ No impact - manual reviews still work +- **Build Success**: ✅ No impact - all actual builds pass +- **Tests**: ✅ No impact - all 2,557 tests pass +- **Security**: ✅ No impact - CodeQL and dependency review pass +- **Merge Safety**: ✅ Safe to merge - this is just a missing optional feature + +## Recommendations + +### Immediate Action (Optional) + +If you want to enable the Gemini code review feature: + +1. **Get a Gemini API Key** (free tier available): + ```bash + # Visit: https://aistudio.google.com/app/apikey + # Create API key + ``` + +2. **Add to GitHub Secrets**: + - Go to: Repository Settings → Secrets and variables → Actions + - Click "New repository secret" + - Name: `GEMINI_API_KEY` + - Value: Your API key from step 1 + +3. **Verify**: Re-run the failed Gemini Dispatch workflow + +### Alternative Action + +Simply ignore this failure and merge the PR - all critical checks are passing. + +## Fix Actions Taken in This PR + +### 1. Created Copilot Custom Instructions ✅ + +**File**: `.github/copilot-instructions.md` + +This comprehensive guide helps GitHub Copilot provide better code suggestions by understanding: +- Project architecture and design decisions +- Coding standards and best practices +- Development workflow and commands +- Security requirements +- Testing guidelines +- Common patterns and idioms + +The file is based on the existing `CLAUDE.md` but formatted specifically for GitHub Copilot's consumption. + +### 2. Documented CI Status ✅ + +**File**: `.github/CI-FIX-REPORT.md` (this document) + +Comprehensive analysis of: +- All workflow statuses +- Root cause of Gemini Dispatch failure +- Why it's not a blocking issue +- How to fix it (if desired) +- Recommendations + +## Conclusion + +### Summary + +- **3 of 4 workflows passing** ✅ +- **Only failure is Gemini Dispatch** (missing API credentials) +- **This is a configuration issue**, not a code problem +- **Safe to merge** the rsa dependency update +- **Copilot custom instructions created** to improve future development + +### What Cannot Be Fixed via Code + +The Gemini Dispatch workflow failure **cannot** be resolved through code changes. It requires repository administrator action to: +1. Obtain API credentials from Google +2. Add them to GitHub repository secrets/variables +3. Re-run the workflow + +### Recommended Next Steps + +For repository administrators: + +1. **Short-term**: Merge PR #18 - the dependency update is safe +2. **Medium-term**: Decide if Gemini code review is desired +3. **If yes**: Follow "Immediate Action" steps above to configure +4. **If no**: Consider disabling or removing the Gemini workflows + +--- + +## Technical References + +### Files Modified in This PR +- `.github/copilot-instructions.md` (created) - 12KB comprehensive guide +- `.github/CI-FIX-REPORT.md` (created) - This analysis document + +### Workflow File Locations +- `.github/workflows/ci.yml` - Main CI (passing) +- `.github/workflows/codeql.yml` - Security scanning (passing) +- `.github/workflows/dependency-review.yml` - Dependency validation (passing) +- `.github/workflows/gemini-dispatch.yml` - AI review dispatcher (failing - config issue) +- `.github/workflows/gemini-review.yml` - AI review implementation (never runs due to dispatch failure) + +### Related Documentation +- `CLAUDE.md` - Primary AI assistant guidance (source of truth) +- `CLAUDE.local.md` - Session-by-session development log +- `CONTRIBUTING.md` - Contribution guidelines +- `docs/08-SECURITY.md` - Security audit checklist diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..7445599 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,365 @@ +# GitHub Copilot Custom Instructions for ProRT-IP + +## Project Overview + +**ProRT-IP WarScan** is a high-performance network scanner implemented in Rust that combines the speed of Masscan/ZMap with the detection depth of Nmap. This is a penetration testing and red team tool designed for security professionals. + +- **Current Version**: v0.5.9 (Phase 6 Complete) +- **Test Coverage**: 2,557 tests passing, 51.40% coverage +- **License**: GPL-3.0 +- **Repository**: https://github.com/doublegate/ProRT-IP + +## Architecture & Key Design Decisions + +### Hybrid Scanning Approach +- **Fast Stateless Discovery**: Masscan-style speed (10M+ packets per second) +- **Deep Stateful Enumeration**: Nmap-style detection and fingerprinting +- **Stream to Disk**: Handles internet-scale scanning without memory exhaustion + +### Technology Stack +```toml +tokio = "1.35" # Async runtime (multi-threaded) +pnet = "0.34" # Cross-platform packet manipulation +clap = "4.4" # CLI argument parsing +sqlx = "0.7" # Async SQL operations +ratatui = "0.29" # Terminal UI framework +``` + +### Core Architectural Decisions +1. **Async Runtime**: Tokio multi-threaded runtime with CPU-core workers +2. **Lock-Free Communication**: Crossbeam channels for inter-thread communication +3. **Raw Sockets**: Cross-platform support (AF_PACKET/Npcap/BPF via pnet) +4. **Privilege Management**: Create elevated → drop immediately → run unprivileged +5. **Zero-Copy**: For packets >10KB to minimize memory pressure + +## Project Structure + +This is a Rust workspace with multiple crates: +- `crates/prtip-core`: Core data structures and utilities +- `crates/prtip-network`: Packet manipulation and network I/O +- `crates/prtip-scanner`: Scanning orchestration and algorithms +- `crates/prtip-cli`: Command-line interface +- `tests/`: Integration tests +- `fuzz/`: Fuzz testing targets +- `docs/`: Technical documentation +- `ref-docs/`: Reference specifications + +## Coding Standards + +### Rust Code Style +- **Edition**: Rust 2021 +- **Formatting**: Use `cargo fmt` (rustfmt defaults) +- **Indentation**: 4 spaces +- **Naming Conventions**: + - `snake_case` for functions and variables + - `SCREAMING_SNAKE_CASE` for constants + - `CamelCase` for types and traits +- **Documentation**: All public items must have `///` doc comments with examples where practical +- **Error Handling**: Use `Result` or `anyhow::Result`; avoid `unwrap()` outside tests +- **Comments**: Add brief comments explaining protocol assumptions for networking code + +### Code Quality Requirements +- **No Warnings**: `cargo clippy --workspace --all-targets -- -D warnings` must pass +- **Format Check**: `cargo fmt --check` must pass before commits +- **Test Coverage**: Maintain >60% overall coverage, >90% for core modules +- **Security**: Follow `docs/08-SECURITY.md` requirements + +## Development Workflow + +### Building +```bash +cargo build # Debug build +cargo build --release # Optimized release build +``` + +### Testing +```bash +cargo test --workspace --all-targets # Run all tests +cargo test --doc # Run doc tests +cargo test -p prtip-core # Test specific crate +``` + +### Linting +```bash +cargo fmt # Format code +cargo fmt --check # Check formatting +cargo clippy --workspace --all-targets -- -D warnings # Lint with zero warnings +``` + +### Running Locally +```bash +cargo run -p prtip-cli -- --help # Show CLI help +cargo run -p prtip-cli -- -sS -p 1-1000 10.0.0.0/24 # Example SYN scan +``` + +## Security Requirements + +This project handles sensitive network operations and requires strict security practices: + +### Input Validation +- Use `IpAddr::parse()` for IP addresses +- Use `ipnetwork` crate for CIDR parsing +- Implement allowlist validation at all trust boundaries +- Never trust user input for packet construction + +### Privilege Handling +- Create sockets with elevated privileges +- Drop privileges immediately after socket creation +- Run main application logic unprivileged + +### Packet Parsing +- Use pnet/etherparse with bounds checking +- Return `Option`/`Result` for malformed packets +- Never panic on malformed network data + +### DoS Prevention +- Use `tokio::sync::Semaphore` to bound concurrent operations +- Stream results to disk for large scans +- Implement adaptive rate limiting +- Respect system resource limits + +## Testing Guidelines + +### Test Organization +- **Unit Tests**: `#[cfg(test)]` modules alongside code +- **Integration Tests**: `tests/` directory for cross-crate scenarios +- **Doc Tests**: Runnable examples in `///` doc comments +- **Fuzz Tests**: `fuzz/` directory for property-based testing + +### Test Naming +- Name test functions with `test_*` prefix +- Use descriptive names: `test_syn_scan_detects_open_port` + +### Async Tests +```rust +#[tokio::test] +async fn test_async_operation() { + // Async test code +} +``` + +### CI Expectations +Before opening a PR, ensure: +1. `cargo fmt --check` passes +2. `cargo clippy --workspace --all-targets -- -D warnings` passes +3. `cargo test --workspace --all-targets` passes +4. New features have test coverage for both success and failure paths + +## CLI Design Principles + +### Nmap Compatibility +ProRT-IP maintains compatibility with 50+ Nmap flags: +- Scan types: `-sS`, `-sT`, `-sU`, `-sF`, `-sN`, `-sX`, `-sA`, `-sI` +- Port specs: `-p`, `-F`, `--top-ports` +- Output: `-oN`, `-oX`, `-oG`, `-oJ` +- Timing: `-T0` through `-T5` +- Evasion: `-f`, `--mtu`, `--ttl`, `-D`, `--badsum`, `-g` + +### Binary Name +The CLI binary is named `prtip` (not `prtip-cli`) + +### Example Usage +```bash +prtip -sS -p 1-1000 10.0.0.0/24 # SYN scan +prtip -F 192.168.1.1 # Fast scan (top 100 ports) +prtip -A -p 80,443 target.com # Aggressive scan with detection +``` + +## Documentation + +### Key Documentation Files +- **Root Level**: + - `README.md`: Project overview and quick start + - `ROADMAP.md`: Development phases and milestones + - `CONTRIBUTING.md`: Contribution guidelines + - `SECURITY.md`: Security policy and reporting + - `CHANGELOG.md`: Version history + - `CLAUDE.md`: AI assistant guidance (this is the source of truth) + - `CLAUDE.local.md`: Session-by-session development log + +- **Technical Docs** (`docs/`): + - `00-ARCHITECTURE.md`: System design + - `01-ROADMAP.md`: Detailed sprint planning + - `03-DEV-SETUP.md`: Development environment setup + - `04-IMPLEMENTATION-GUIDE.md`: Code structure guide + - `06-TESTING.md`: Testing strategy + - `07-PERFORMANCE.md`: Performance benchmarks + - `08-SECURITY.md`: Security audit checklist + - `10-PROJECT-STATUS.md`: Current task tracking + - `TUI-ARCHITECTURE.md`: Terminal UI design + +- **Reference** (`ref-docs/`): Technical specifications (36KB-190KB guides) + +### Documentation Updates +When making changes: +- Update `CHANGELOG.md` for user-visible changes +- Update relevant docs in `docs/` for architectural changes +- Keep `CLAUDE.local.md` updated with session notes +- Maintain accurate module-level docs in code + +## Capabilities & Features + +### Scan Types (8 Total) +- **TCP**: SYN (default), Connect, FIN/NULL/Xmas (stealth), ACK (firewall mapping), Idle (anonymity) +- **UDP**: Protocol-specific payloads (DNS, SNMP, NetBIOS) with ICMP interpretation + +### Detection Capabilities +- **Service Detection**: 187 probes, 85-90% accuracy +- **OS Fingerprinting**: 16-probe system, 2,600+ signature database +- **TLS Detection**: Certificate chain analysis + +### Performance Features +- **Packet Rate**: 10M+ packets per second (stateless mode) +- **Scale**: Internet-scale IPv4/IPv6 scanning +- **Timing Profiles**: T0 (paranoid) through T5 (insane) +- **TUI**: 60 FPS, 4-tab dashboard, handles 10K+ events/sec + +### Output Formats +- Text (colorized terminal output) +- JSON (structured data) +- XML (Nmap-compatible) +- Greppable (line-based parsing) +- PCAPNG (packet capture) +- SQL (database export) + +## Common Patterns + +### Error Handling +```rust +use anyhow::{Context, Result}; + +fn parse_target(input: &str) -> Result { + input.parse() + .context("Failed to parse IP address") +} +``` + +### Async Network Operations +```rust +use tokio::net::TcpStream; + +async fn connect_scan(addr: SocketAddr) -> Result { + match tokio::time::timeout( + Duration::from_secs(3), + TcpStream::connect(addr) + ).await { + Ok(Ok(_)) => Ok(true), + _ => Ok(false), + } +} +``` + +### Privilege Dropping +```rust +// Create socket with elevated privileges +let socket = create_raw_socket()?; + +// Drop privileges immediately +drop_privileges()?; + +// Continue with unprivileged operations +``` + +## Important Notes + +### Platform-Specific Considerations +- **Linux**: Requires CAP_NET_RAW capability or root for raw sockets +- **Windows**: Requires Npcap; older versions have 90s initialization delay +- **macOS**: Requires ChmodBPF or root; FIN/NULL/Xmas scans don't work on some networks + +### Limitations +- **FIN/NULL/Xmas scans**: Don't work reliably on Windows or through Cisco equipment +- **UDP scanning**: ~10-100x slower than TCP due to protocol characteristics +- **Rate limiting**: Required to prevent DoS; default uses adaptive rate limiting + +### Release Standards +- **Tag Messages**: 100-150 lines with executive summary, features, metrics, technical details +- **GitHub Releases**: 150-200 lines with all tag content plus links, installation, platform matrix +- **Examples**: See v0.3.7-v0.3.9, v0.4.0-v0.4.5 for reference + +## Historical Context + +### Development Phases +| Phase | Status | Tests | Key Features | +|-------|--------|-------|--------------| +| 1-3 | ✅ Complete | 391 | Core scanning, protocols, detection | +| 4 | ✅ Complete | 1,166 | Zero-copy, NUMA, PCAPNG, evasion, IPv6 foundation | +| 5 | ✅ Complete | 868 | IPv6 100%, Idle scan, Service detection, Rate limiting, TLS | +| 6 | ✅ Complete | 2,557 | TUI (60 FPS), Dashboard, CDN filtering, Network optimizations, Buffer Pool | + +### Current Status (as of latest update) +- **Phase 6**: Production-ready TUI with real-time visualization +- **Tests**: 2,557 passing (73 ignored for platform-specific reasons) +- **Coverage**: 51.40% overall (>90% in core modules) +- **CI**: GitHub Actions with Linux, macOS, Windows matrix +- **Fuzz Testing**: 230M+ executions, 0 crashes + +## Quick Reference + +### Pre-Commit Checklist +1. `cargo fmt` - Format code +2. `cargo clippy --workspace --all-targets -- -D warnings` - Lint +3. `cargo test --workspace --all-targets` - Run tests +4. `cargo build --release` - Ensure release builds +5. Update documentation if behavior changed +6. Update CHANGELOG.md for user-visible changes + +### Common Commands +```bash +# Development +cargo check # Fast compile check +cargo build # Debug build +cargo test # Run tests +cargo doc --open # Generate and open docs + +# Quality +cargo clippy --workspace --all-targets -- -D warnings +cargo fmt --check +cargo audit # Security audit + +# Performance +cargo bench # Run benchmarks +cargo build --release --features performance # Optimized build +``` + +### Getting Help +- Read `CLAUDE.md` for detailed AI assistant guidance +- Check `docs/` directory for technical documentation +- Review `CONTRIBUTING.md` for contribution process +- See `SECURITY.md` for security concerns +- Check GitHub Issues for known problems + +## Maintenance + +### Weekly Tasks +- Run `cargo audit` to check for security vulnerabilities +- Update CLAUDE.local.md with session notes +- Sync PROJECT-STATUS.md with current work + +### Before Releases +- Review `08-SECURITY.md` checklist +- Update CHANGELOG.md with all changes +- Run full test suite across platforms +- Update version numbers in Cargo.toml files +- Tag release with comprehensive notes (see Release Standards) + +## Additional Context + +This is a professional-grade security tool used for: +- Network reconnaissance +- Penetration testing +- Security auditing +- Red team operations + +Users must have authorization to scan target networks. The tool includes: +- User confirmation for internet-scale scans +- Audit logging capabilities +- Rate limiting to prevent accidental DoS +- Clear warnings about legal and ethical usage + +When contributing code: +- Prioritize security and correctness over convenience +- Consider performance implications of changes +- Maintain cross-platform compatibility +- Write comprehensive tests +- Document security-relevant behavior From f222fd2736e043c8144169acac561c3c6613a4b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:32:46 +0000 Subject: [PATCH 04/11] fix: Update copilot-instructions.md with accurate crate versions and test counts - Update pnet version from 0.34 to 0.35 - Update clap version from 4.4 to 4.5 - Update sqlx version from 0.7 to 0.8 - Add missing prtip-tui crate to project structure - Update ignored test count from 73 to 96 Addresses review comments from PR #19 Co-authored-by: doublegate <6858123+doublegate@users.noreply.github.com> --- .github/copilot-instructions.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7445599..c1ac907 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -19,9 +19,9 @@ ### Technology Stack ```toml tokio = "1.35" # Async runtime (multi-threaded) -pnet = "0.34" # Cross-platform packet manipulation -clap = "4.4" # CLI argument parsing -sqlx = "0.7" # Async SQL operations +pnet = "0.35" # Cross-platform packet manipulation +clap = "4.5" # CLI argument parsing +sqlx = "0.8" # Async SQL operations ratatui = "0.29" # Terminal UI framework ``` @@ -39,6 +39,7 @@ This is a Rust workspace with multiple crates: - `crates/prtip-network`: Packet manipulation and network I/O - `crates/prtip-scanner`: Scanning orchestration and algorithms - `crates/prtip-cli`: Command-line interface +- `crates/prtip-tui`: Terminal user interface (TUI) - `tests/`: Integration tests - `fuzz/`: Fuzz testing targets - `docs/`: Technical documentation @@ -289,7 +290,7 @@ drop_privileges()?; ### Current Status (as of latest update) - **Phase 6**: Production-ready TUI with real-time visualization -- **Tests**: 2,557 passing (73 ignored for platform-specific reasons) +- **Tests**: 2,557 passing (96 ignored for platform-specific reasons) - **Coverage**: 51.40% overall (>90% in core modules) - **CI**: GitHub Actions with Linux, macOS, Windows matrix - **Fuzz Testing**: 230M+ executions, 0 crashes From d65da0c409fc201b3f1dc136d1ff767656ecb9aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:49:09 +0000 Subject: [PATCH 05/11] Initial plan From 6b422415d9cc2dd89ee8e8e99629b4a241ff1dd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:02:41 +0000 Subject: [PATCH 06/11] refactor: migrate from bincode to rkyv v0.8.14 for serialization Co-authored-by: doublegate <6858123+doublegate@users.noreply.github.com> --- Cargo.lock | 129 ++++++++++++++++-- Cargo.toml | 2 +- crates/prtip-core/Cargo.toml | 1 + crates/prtip-core/src/lib.rs | 2 +- crates/prtip-core/src/types.rs | 51 ++++++- crates/prtip-scanner/Cargo.toml | 2 +- .../prtip-scanner/src/output/mmap_reader.rs | 21 ++- .../prtip-scanner/src/output/mmap_writer.rs | 9 +- 8 files changed, 197 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2feb6d..9253325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,15 +181,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "2.10.0" @@ -225,6 +216,29 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytecheck" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -1689,6 +1703,26 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -2272,6 +2306,7 @@ dependencies = [ "parking_lot", "rand", "regex", + "rkyv", "rlimit", "serde", "serde_json", @@ -2313,7 +2348,6 @@ name = "prtip-scanner" version = "0.5.9" dependencies = [ "anyhow", - "bincode", "chrono", "criterion", "crossbeam", @@ -2335,6 +2369,7 @@ dependencies = [ "prtip-network", "rand", "regex", + "rkyv", "rustls", "serde", "serde_json", @@ -2372,6 +2407,26 @@ dependencies = [ "uuid", ] +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "quanta" version = "0.12.6" @@ -2412,6 +2467,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rancor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee" +dependencies = [ + "ptr_meta", +] + [[package]] name = "rand" version = "0.8.5" @@ -2562,6 +2626,15 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rend" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" +dependencies = [ + "bytecheck", +] + [[package]] name = "ring" version = "0.17.14" @@ -2576,6 +2649,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360b333c61ae24e5af3ae7c8660bd6b21ccd8200dbbc5d33c2454421e85b9c69" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.16.1", + "indexmap", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02f8cdd12b307ab69fe0acf4cd2249c7460eb89dce64a0febadf934ebb6a9e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "rlimit" version = "0.10.2" @@ -2884,6 +2987,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.11" diff --git a/Cargo.toml b/Cargo.toml index ce9fa9a..00d0bc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ rusqlite = { version = "0.31", features = ["bundled"] } # Memory-mapped I/O (Sprint 6.6) memmap2 = "0.9" -bincode = "1.3" +rkyv = "0.8.14" # CSV export (promote to workspace) csv = "1.3" diff --git a/crates/prtip-core/Cargo.toml b/crates/prtip-core/Cargo.toml index 60486ee..56f3ef4 100644 --- a/crates/prtip-core/Cargo.toml +++ b/crates/prtip-core/Cargo.toml @@ -30,6 +30,7 @@ rand = { workspace = true } sysinfo = { workspace = true } flate2 = "1.0" dirs = "5.0" +rkyv = { workspace = true } [dev-dependencies] tokio = { workspace = true } diff --git a/crates/prtip-core/src/lib.rs b/crates/prtip-core/src/lib.rs index d702ea6..ecef24c 100644 --- a/crates/prtip-core/src/lib.rs +++ b/crates/prtip-core/src/lib.rs @@ -136,4 +136,4 @@ 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}; diff --git a/crates/prtip-core/src/types.rs b/crates/prtip-core/src/types.rs index 7084f49..e8fd48e 100644 --- a/crates/prtip-core/src/types.rs +++ b/crates/prtip-core/src/types.rs @@ -3,6 +3,7 @@ use crate::error::{Error, Result}; use chrono::{DateTime, Utc}; use ipnetwork::IpNetwork; +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use serde::{Deserialize, Serialize}; use std::fmt; use std::net::IpAddr; @@ -315,7 +316,8 @@ impl Iterator for PortRangeIterator { } /// State of a scanned port -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)] +#[rkyv(derive(Debug))] pub enum PortState { /// Port is open and accepting connections Open, @@ -475,6 +477,53 @@ impl fmt::Display for TimingTemplate { } } +/// Serializable representation of ScanResult for rkyv +#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize)] +#[rkyv(derive(Debug))] +pub struct ScanResultRkyv { + target_ip: IpAddr, + port: u16, + state: PortState, + response_time_nanos: u128, + timestamp_nanos: i64, + banner: Option, + service: Option, + version: Option, + raw_response: Option>, +} + +impl From<&ScanResult> for ScanResultRkyv { + fn from(result: &ScanResult) -> Self { + Self { + target_ip: result.target_ip, + port: result.port, + state: result.state, + response_time_nanos: result.response_time.as_nanos(), + timestamp_nanos: result.timestamp.timestamp_nanos_opt().unwrap_or(0), + banner: result.banner.clone(), + service: result.service.clone(), + version: result.version.clone(), + raw_response: result.raw_response.clone(), + } + } +} + +impl From for ScanResult { + fn from(rkyv: ScanResultRkyv) -> Self { + Self { + target_ip: rkyv.target_ip, + port: rkyv.port, + state: rkyv.state, + response_time: Duration::from_nanos(rkyv.response_time_nanos as u64), + timestamp: DateTime::from_timestamp_nanos(rkyv.timestamp_nanos), + banner: rkyv.banner, + service: rkyv.service, + version: rkyv.version, + raw_response: rkyv.raw_response, + } + } +} + /// Result of scanning a single port #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScanResult { diff --git a/crates/prtip-scanner/Cargo.toml b/crates/prtip-scanner/Cargo.toml index c42f5e5..6d99d00 100644 --- a/crates/prtip-scanner/Cargo.toml +++ b/crates/prtip-scanner/Cargo.toml @@ -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 } diff --git a/crates/prtip-scanner/src/output/mmap_reader.rs b/crates/prtip-scanner/src/output/mmap_reader.rs index 84003e6..c45c540 100644 --- a/crates/prtip-scanner/src/output/mmap_reader.rs +++ b/crates/prtip-scanner/src/output/mmap_reader.rs @@ -1,7 +1,7 @@ //! Memory-mapped result reader for zero-copy access to scan results use memmap2::Mmap; -use prtip_core::ScanResult; +use prtip_core::{ScanResult, ScanResultRkyv}; use std::fs::File; use std::io; use std::path::Path; @@ -77,8 +77,23 @@ impl MmapResultReader { let offset = HEADER_SIZE + (index * self.entry_size); let entry_bytes = &self.mmap[offset..offset + self.entry_size]; - // Deserialize the entry (bincode handles trailing zeros) - bincode::deserialize(entry_bytes).ok() + // Find the end of actual data (before zero-padding) + let data_end = entry_bytes.iter() + .rposition(|&b| b != 0) + .map(|pos| pos + 1) + .unwrap_or(0); + + if data_end == 0 { + return None; + } + + let actual_bytes = &entry_bytes[..data_end]; + + // Deserialize using rkyv + match rkyv::from_bytes::(actual_bytes) { + Ok(rkyv_result) => Some(rkyv_result.into()), + Err(_) => None, + } } /// Create an iterator over all entries diff --git a/crates/prtip-scanner/src/output/mmap_writer.rs b/crates/prtip-scanner/src/output/mmap_writer.rs index 7b9ee77..4451cc2 100644 --- a/crates/prtip-scanner/src/output/mmap_writer.rs +++ b/crates/prtip-scanner/src/output/mmap_writer.rs @@ -5,7 +5,7 @@ //! fixed-size entries for zero-copy random access. use memmap2::{MmapMut, MmapOptions}; -use prtip_core::ScanResult; +use prtip_core::{ScanResult, ScanResultRkyv}; use std::fs::{File, OpenOptions}; use std::io; use std::path::Path; @@ -56,8 +56,11 @@ impl MmapResultWriter { } let offset = HEADER_SIZE + (self.entry_count * ENTRY_SIZE); - let entry_bytes = bincode::serialize(result) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + // Convert to rkyv-compatible type and serialize + let rkyv_result: ScanResultRkyv = result.into(); + let entry_bytes = rkyv::to_bytes::(&rkyv_result) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; if entry_bytes.len() > ENTRY_SIZE { return Err(io::Error::new( From 79ebaa0eaa59ae0e9f88ce087a61ae41170a0581 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:03:29 +0000 Subject: [PATCH 07/11] docs: fix dead link in Phase 4 enhancements documentation Co-authored-by: doublegate <6858123+doublegate@users.noreply.github.com> --- docs/archive/19-PHASE4-ENHANCEMENTS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/archive/19-PHASE4-ENHANCEMENTS.md b/docs/archive/19-PHASE4-ENHANCEMENTS.md index 039f073..2f617f4 100644 --- a/docs/archive/19-PHASE4-ENHANCEMENTS.md +++ b/docs/archive/19-PHASE4-ENHANCEMENTS.md @@ -83,7 +83,7 @@ Following the successful completion of Phase 4 (Performance Optimization) with 7 **Primary Research:** - **Local Code Analysis**: Analyzed code_ref/ directory containing RustScan (Rust), Nmap (C++), Masscan fragments - **GitHub Repository Review**: Deep dive into RustScan/RustScan (18.2K stars), robertdavidgraham/masscan (24.9K stars) -- **Online Research**: 15+ articles including Medium analyses, GeeksforGeeks tutorials, findsec.org comparisons +- **Online Research**: 15+ articles including Medium analyses, GeeksforGeeks tutorials, network scanner comparisons - **Community Discussions**: Reddit (r/netsec, r/rust), Stack Overflow, GitHub issues across all projects **Analysis Period:** October 2025 @@ -1583,8 +1583,8 @@ Create comprehensive usage examples library, common scenarios guide, update all - URL: https://medium.com/@lukwagoasuman236/pros-dont-use-nmap-or-rustscan-they-use-this-2026-d9e0964ece1b - Insights: Performance comparison, use case recommendations -2. **"Top Network Scanners Compared: Nmap, Masscan, ZMap, and More"** (findsec.org) - - URL: https://findsec.org/index.php/blog/493-nmap-vs-masscan-zmap-rustscan-comparison +2. ~~**"Top Network Scanners Compared: Nmap, Masscan, ZMap, and More"** (findsec.org)~~ + - ~~URL: https://findsec.org/index.php/blog/493-nmap-vs-masscan-zmap-rustscan-comparison~~ (Link no longer accessible) - Insights: Feature matrix, speed benchmarks, tool selection guide 3. **"01/31/2025 – masscan vs nmap Scan"** (victsao.wordpress.com) @@ -1621,7 +1621,7 @@ Create comprehensive usage examples library, common scenarios guide, update all **Total Sources:** - **Code Repositories**: 3 (RustScan, Masscan, Nmap) -- **Online Articles**: 15+ (Medium, GeeksforGeeks, findsec.org, etc.) +- **Online Articles**: 15+ (Medium, GeeksforGeeks, network scanner comparisons, etc.) - **Technical Documentation**: Nmap book, RFC specifications - **Community Discussions**: Reddit, Stack Overflow, GitHub issues From f16651799be8da8f108e3d1b1c48722850debca1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:07:18 +0000 Subject: [PATCH 08/11] fix: resolve rkyv alignment issues with aligned buffer copy Co-authored-by: doublegate <6858123+doublegate@users.noreply.github.com> --- crates/prtip-scanner/Cargo.toml | 2 +- .../prtip-scanner/src/output/mmap_reader.rs | 20 +++++++++---------- .../prtip-scanner/src/output/mmap_writer.rs | 17 +++++++++++----- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/prtip-scanner/Cargo.toml b/crates/prtip-scanner/Cargo.toml index 6d99d00..b073d45 100644 --- a/crates/prtip-scanner/Cargo.toml +++ b/crates/prtip-scanner/Cargo.toml @@ -87,7 +87,7 @@ pcap-file = "2.0" # Memory-mapped I/O memmap2 = { workspace = true } -rkyv = { workspace = true } +rkyv = { workspace = true, features = ["alloc"] } [dev-dependencies] tokio = { workspace = true } diff --git a/crates/prtip-scanner/src/output/mmap_reader.rs b/crates/prtip-scanner/src/output/mmap_reader.rs index c45c540..0fb92b4 100644 --- a/crates/prtip-scanner/src/output/mmap_reader.rs +++ b/crates/prtip-scanner/src/output/mmap_reader.rs @@ -8,6 +8,7 @@ use std::path::Path; const HEADER_SIZE: usize = 64; const ENTRY_SIZE: usize = 512; +const LENGTH_PREFIX_SIZE: usize = 8; // 8 bytes for length to maintain alignment /// Memory-mapped result reader pub struct MmapResultReader { @@ -75,22 +76,21 @@ impl MmapResultReader { } let offset = HEADER_SIZE + (index * self.entry_size); - let entry_bytes = &self.mmap[offset..offset + self.entry_size]; - - // Find the end of actual data (before zero-padding) - let data_end = entry_bytes.iter() - .rposition(|&b| b != 0) - .map(|pos| pos + 1) - .unwrap_or(0); - if data_end == 0 { + // Read length prefix (8 bytes) + let len_bytes: [u8; 8] = self.mmap[offset..offset + LENGTH_PREFIX_SIZE].try_into().ok()?; + let len = u64::from_le_bytes(len_bytes) as usize; + + if len == 0 || len + LENGTH_PREFIX_SIZE > self.entry_size { return None; } - let actual_bytes = &entry_bytes[..data_end]; + // Copy data to an aligned buffer (rkyv requires alignment) + let entry_bytes = &self.mmap[offset + LENGTH_PREFIX_SIZE..offset + LENGTH_PREFIX_SIZE + len]; + let aligned_data: Vec = entry_bytes.to_vec(); // Deserialize using rkyv - match rkyv::from_bytes::(actual_bytes) { + match rkyv::from_bytes::(&aligned_data) { Ok(rkyv_result) => Some(rkyv_result.into()), Err(_) => None, } diff --git a/crates/prtip-scanner/src/output/mmap_writer.rs b/crates/prtip-scanner/src/output/mmap_writer.rs index 4451cc2..2643550 100644 --- a/crates/prtip-scanner/src/output/mmap_writer.rs +++ b/crates/prtip-scanner/src/output/mmap_writer.rs @@ -12,6 +12,7 @@ use std::path::Path; const HEADER_SIZE: usize = 64; // Version, entry_count, entry_size, checksum const ENTRY_SIZE: usize = 512; // Fixed-size entries (padded if needed) +const LENGTH_PREFIX_SIZE: usize = 8; // 8 bytes for length to maintain alignment /// Memory-mapped result writer pub struct MmapResultWriter { @@ -62,22 +63,28 @@ impl MmapResultWriter { let entry_bytes = rkyv::to_bytes::(&rkyv_result) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; - if entry_bytes.len() > ENTRY_SIZE { + // Check if data + length prefix fits + if entry_bytes.len() + LENGTH_PREFIX_SIZE > ENTRY_SIZE { return Err(io::Error::new( io::ErrorKind::InvalidData, format!( - "Entry size {} exceeds maximum {}", + "Entry size {} (+{} length) exceeds maximum {}", entry_bytes.len(), + LENGTH_PREFIX_SIZE, ENTRY_SIZE ), )); } - // Write serialized data - self.mmap[offset..offset + entry_bytes.len()].copy_from_slice(&entry_bytes); + // Write length prefix (8 bytes for alignment) + let len = entry_bytes.len() as u64; + self.mmap[offset..offset + LENGTH_PREFIX_SIZE].copy_from_slice(&len.to_le_bytes()); + + // Write serialized data after length prefix + self.mmap[offset + LENGTH_PREFIX_SIZE..offset + LENGTH_PREFIX_SIZE + entry_bytes.len()].copy_from_slice(&entry_bytes); // Zero-fill remaining space - for i in entry_bytes.len()..ENTRY_SIZE { + for i in (LENGTH_PREFIX_SIZE + entry_bytes.len())..ENTRY_SIZE { self.mmap[offset + i] = 0; } From bc8e3e2bad05b56fdc7edbb693676eaf5cae8f31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:16:55 +0000 Subject: [PATCH 09/11] Initial plan From 1fa1c194dd39013d8d699055858be233128e987a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:25:32 +0000 Subject: [PATCH 10/11] fix: remove duplicate ScanResultRkyv definition and fix dead link Co-authored-by: doublegate <6858123+doublegate@users.noreply.github.com> --- crates/prtip-core/src/types.rs | 137 ++---------------- .../prtip-scanner/src/output/mmap_reader.rs | 24 +-- docs/archive/19-PHASE4-ENHANCEMENTS.md | 2 +- 3 files changed, 20 insertions(+), 143 deletions(-) diff --git a/crates/prtip-core/src/types.rs b/crates/prtip-core/src/types.rs index 3271e60..a7b99e1 100644 --- a/crates/prtip-core/src/types.rs +++ b/crates/prtip-core/src/types.rs @@ -316,7 +316,20 @@ impl Iterator for PortRangeIterator { } /// State of a scanned port -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Archive, + RkyvSerialize, + RkyvDeserialize, +)] #[rkyv(derive(Debug))] pub enum PortState { /// Port is open and accepting connections @@ -641,128 +654,6 @@ impl fmt::Display for ScanResult { } } -/// 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, - /// Optional service name (max 32 bytes) - pub service: Option, - /// Optional service version (max 64 bytes) - pub version: Option, - /// Optional raw response (limited to 256 bytes to fit in entry) - pub raw_response: Option>, -} - -impl From<&ScanResult> for ScanResultRkyv { - fn from(result: &ScanResult) -> Self { - // 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) - } - IpAddr::V6(ipv6) => (ipv6.octets(), false), - }; - - // Convert PortState to u8 - let state = match result.state { - PortState::Open => 0, - PortState::Closed => 1, - PortState::Filtered => 2, - PortState::Unknown => 3, - }; - - // 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; - - // Convert timestamp with proper error handling - let timestamp_nanos = result - .timestamp - .timestamp_nanos_opt() - .expect("timestamp out of range for nanosecond representation"); - - Self { - target_ip_bytes, - is_ipv4, - port: result.port, - state, - response_time_nanos, - timestamp_nanos, - banner: result.banner.clone(), - service: result.service.clone(), - version: result.version.clone(), - raw_response: result.raw_response.clone(), - } - } -} - -impl From for ScanResult { - fn from(rkyv: ScanResultRkyv) -> Self { - // 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)) - } else { - IpAddr::V6(std::net::Ipv6Addr::from(rkyv.target_ip_bytes)) - }; - - // Convert u8 back to PortState - let state = match rkyv.state { - 0 => PortState::Open, - 1 => PortState::Closed, - 2 => PortState::Filtered, - _ => PortState::Unknown, - }; - - // 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); - - // Convert i64 nanoseconds back to DateTime - let timestamp = DateTime::from_timestamp_nanos(rkyv.timestamp_nanos); - - Self { - target_ip, - port: rkyv.port, - state, - response_time, - timestamp, - banner: rkyv.banner, - service: rkyv.service, - version: rkyv.version, - raw_response: rkyv.raw_response, - } - } -} - /// Port filtering for exclusion/inclusion lists /// /// Provides efficient port filtering using hash sets for O(1) lookups. diff --git a/crates/prtip-scanner/src/output/mmap_reader.rs b/crates/prtip-scanner/src/output/mmap_reader.rs index 8b7e894..0e6713f 100644 --- a/crates/prtip-scanner/src/output/mmap_reader.rs +++ b/crates/prtip-scanner/src/output/mmap_reader.rs @@ -87,33 +87,19 @@ impl MmapResultReader { } let offset = HEADER_SIZE + (index * self.entry_size); - + // Read length prefix (8 bytes) - let len_bytes: [u8; 8] = self.mmap[offset..offset + LENGTH_PREFIX_SIZE].try_into().ok()?; + let len_bytes: [u8; 8] = self.mmap[offset..offset + LENGTH_PREFIX_SIZE] + .try_into() + .ok()?; let len = u64::from_le_bytes(len_bytes) as usize; - - if len == 0 || len + LENGTH_PREFIX_SIZE > self.entry_size { - return None; - } - - // Read length prefix (u64 in little-endian) - let len = u64::from_le_bytes( - entry_bytes[..LENGTH_PREFIX_SIZE] - .try_into() - .expect("LENGTH_PREFIX_SIZE is 8 bytes"), - ) as usize; - // Validate length if len == 0 || len + LENGTH_PREFIX_SIZE > self.entry_size { - eprintln!( - "MmapResultReader: invalid entry length {} at index {}", - len, index - ); return None; } // Use zero-copy deserialization without unnecessary allocation - let data_bytes = &entry_bytes[LENGTH_PREFIX_SIZE..LENGTH_PREFIX_SIZE + len]; + let data_bytes = &self.mmap[offset + LENGTH_PREFIX_SIZE..offset + LENGTH_PREFIX_SIZE + len]; match rkyv::from_bytes::(data_bytes) { Ok(rkyv_result) => Some(ScanResult::from(rkyv_result)), Err(e) => { diff --git a/docs/archive/19-PHASE4-ENHANCEMENTS.md b/docs/archive/19-PHASE4-ENHANCEMENTS.md index 2f617f4..0014007 100644 --- a/docs/archive/19-PHASE4-ENHANCEMENTS.md +++ b/docs/archive/19-PHASE4-ENHANCEMENTS.md @@ -1584,7 +1584,7 @@ Create comprehensive usage examples library, common scenarios guide, update all - Insights: Performance comparison, use case recommendations 2. ~~**"Top Network Scanners Compared: Nmap, Masscan, ZMap, and More"** (findsec.org)~~ - - ~~URL: https://findsec.org/index.php/blog/493-nmap-vs-masscan-zmap-rustscan-comparison~~ (Link no longer accessible) + - URL: (Link no longer accessible - findsec.org/index.php/blog/493-nmap-vs-masscan-zmap-rustscan-comparison) - Insights: Feature matrix, speed benchmarks, tool selection guide 3. **"01/31/2025 – masscan vs nmap Scan"** (victsao.wordpress.com) From d2f4d6ef9efef9bf45a407fa7dbcb797c0c2ba89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 05:00:45 +0000 Subject: [PATCH 11/11] fix: restore correct ScanResultRkyv with manual IpAddr serialization Co-authored-by: doublegate <6858123+doublegate@users.noreply.github.com> --- crates/prtip-core/src/types.rs | 118 +++++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 19 deletions(-) diff --git a/crates/prtip-core/src/types.rs b/crates/prtip-core/src/types.rs index a7b99e1..af26cb5 100644 --- a/crates/prtip-core/src/types.rs +++ b/crates/prtip-core/src/types.rs @@ -490,29 +490,85 @@ impl fmt::Display for TimingTemplate { } } -/// Serializable representation of ScanResult for rkyv -#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize)] +/// 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. +/// +/// # Manual Serialization for IpAddr +/// +/// std::net::IpAddr does not implement rkyv traits, so we manually serialize +/// to bytes and convert between IpAddr and byte arrays in the From implementations. +/// +/// # 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: IpAddr, - port: u16, - state: PortState, - response_time_nanos: u128, - timestamp_nanos: i64, - banner: Option, - service: Option, - version: Option, - raw_response: Option>, + /// 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, + /// Optional service name (max 32 bytes) + pub service: Option, + /// Optional service version (max 64 bytes) + pub version: Option, + /// Optional raw response (limited to 256 bytes to fit in entry) + pub raw_response: Option>, } impl From<&ScanResult> for ScanResultRkyv { fn from(result: &ScanResult) -> Self { + // 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) + } + IpAddr::V6(ipv6) => (ipv6.octets(), false), + }; + + // Convert PortState to u8 + let state = match result.state { + PortState::Open => 0, + PortState::Closed => 1, + PortState::Filtered => 2, + PortState::Unknown => 3, + }; + + // 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; + + // Convert timestamp with proper error handling + let timestamp_nanos = result + .timestamp + .timestamp_nanos_opt() + .expect("timestamp out of range for nanosecond representation"); + Self { - target_ip: result.target_ip, + target_ip_bytes, + is_ipv4, port: result.port, - state: result.state, - response_time_nanos: result.response_time.as_nanos(), - timestamp_nanos: result.timestamp.timestamp_nanos_opt().unwrap_or(0), + state, + response_time_nanos, + timestamp_nanos, banner: result.banner.clone(), service: result.service.clone(), version: result.version.clone(), @@ -523,12 +579,36 @@ impl From<&ScanResult> for ScanResultRkyv { impl From for ScanResult { fn from(rkyv: ScanResultRkyv) -> Self { + // 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)) + } else { + IpAddr::V6(std::net::Ipv6Addr::from(rkyv.target_ip_bytes)) + }; + + // Convert u8 back to PortState + let state = match rkyv.state { + 0 => PortState::Open, + 1 => PortState::Closed, + 2 => PortState::Filtered, + _ => PortState::Unknown, + }; + + // 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); + + // Convert i64 nanoseconds back to DateTime + let timestamp = DateTime::from_timestamp_nanos(rkyv.timestamp_nanos); + Self { - target_ip: rkyv.target_ip, + target_ip, port: rkyv.port, - state: rkyv.state, - response_time: Duration::from_nanos(rkyv.response_time_nanos as u64), - timestamp: DateTime::from_timestamp_nanos(rkyv.timestamp_nanos), + state, + response_time, + timestamp, banner: rkyv.banner, service: rkyv.service, version: rkyv.version,