# AGENTS.md — jns ## What this project is **jns** is a high-performance network port scanner (Java 25, virtual threads), scanning IPv4/IPv6 ranges for open TCP (+ optional UDP) ports with service-type detection from banners. It is a CLI library + app: `App`/`Main` parse args, `Scan` holds config, `Scanner` orchestrates, `CliPrinter` renders results, `ScannerLibrary` exposes a programmatic API for other apps. ## Build / run / test - Build: `mvn -q package -DskipTests` → `target/jns-1.0-SNAPSHOT.jar` (no unit tests exist; `mvn test` is a no-op). - Run: `java -jar target/jns-1.0-SNAPSHOT.jar -p 1-1024 ...` (see `--help`; `-i -` reads hosts from stdin, `-u`/`-uu` for UDP, `-q` quiet, `-sf` show filtered ports, `-f` rate limit, `-b` banner recognition). - Sanity checks used during development: - open port: start `python3 -m http.server 8765`, scan `127.0.0.1 -p 22,80,8765` → expect exactly `127.0.0.1` + `\t (1) open:\t8765`; - offline host: 1024 ports → empty output, port count 1 (online check only); - `scanTests/` holds recorded expected outputs (`scan-result-*`, `scan-error-*`); compare new output against the matching file before declaring a change done. ## Architecture notes (read before touching) - Concurrency: one virtual thread per port probe today (`ProducerThread` → `executor.submit(new ScanPortTask(...))`), throttled by semaphores: `socketLimit` (default 2048), `maxHostsLimit`, `maxWorkersPerHost`. `CompletionConsumer` drains `PortScanResultAccumulator`; Ctrl-C cancels but partial results are still printed — preserve that contract. - Shared per-scan state flows through the record `ScanExecutionContext` (semaphores, rate limiter, shared `PortRange`, ...). `ScanHostContext` is the host-level record. **Multi-scan constraint: the scanner must support several concurrent scans with different configs in one JVM — never store per-scan state in statics; only true compile-time constants may be static.** - Carrier-thread pinning is forbidden: never sleep/lock around blocking calls inside virtual threads (see `PortScanRateLimiter` sleep-outside-synchronized pattern and `Scan*PortTask` non-blocking wait loops). - `LinuxUdpSocketAPI` (Foreign Memory API, per-probe `Arena.ofConfined`): do NOT share its errno call-state segment across threads (data race). - `CliPrinter.getColumnWidth()`: the `stty size` process spawn per call is intentional (terminal resize) — do not cache the width. ## Optimization state - Phase 2 (allocation/CPU hot-path cleanups) is **done and measured**: see `TODO.md` (commits, benchmark evidence: −31 % CPU, −4 % wall, −8 % RSS on a 20 k hosts × 65535-port run). The scan is network-latency-bound; remaining headroom is in `TODO.md` Phase 2b / 3 / 4 (deferred, measure-first). - Benchmark method that was used: `/usr/bin/time -v` around `java -jar ... -i - -p all` (host list on stdin), keep the `before-optimization.txt` / `after-optimization-N.txt` artifacts. ## Working rules for this repo - Never change user-visible output format or scan behavior "while at it" — only when it is the explicit purpose of the change. - Small, logical steps per file edit; verify with `mvn -q compile` before showing the diff. - `/tmp` is not persistent between tool calls; re-read files before editing to confirm their current state.