← All tasks
javaclaude-code/java-t3 #25Lite task

TCP Port Scanner (java, written by Claude Code)

envgap__claude-code__java-t3-25

Written by a coding agent; not on GitHubWritten 2026-02-28

01 / FAILURE SIGNATURE

Captured in a clean container

error: no classes were compiled

02 / ENVIRONMENT RECIPE

Base commit
753599984d23379b9e727cf1e36d110ba88b7fd3
Manifest
pom.xml
Reproduce
jar=$(ls target/*-jar-with-dependencies.jar target/*-shaded.jar target/*-all.jar 2>/dev/null | head -n1); [ -n "$jar" ] || jar=$(ls -S target/*.jar 2>/dev/null | grep -v -e '/original-' -e '-sources.jar$' -e '-javadoc.jar$' -e '-tests.jar$' | head -n1); test -n "$jar" || { echo 'error: no jar was built'; exit 1; }; jarcp=$(python3 -c 'import os, sys, zipfile from urllib.parse import unquote jar = sys.argv[1] try: text = zipfile.ZipFile(jar).read("META-INF/MANIFEST.MF").decode("utf-8", "replace") except (KeyError, OSError, zipfile.BadZipFile): text = "" text = text.replace("\r\n", "\n").replace("\r", "\n").replace("\n ", "") found = [line.split(":", 1)[1].split() for line in text.split("\n") if line.lower().startswith("class-path:")] entries = [os.path.join(os.path.dirname(jar), unquote(entry)) for entry in (found[0] if found else [])] print(":".join([jar] + [entry for entry in entries if os.path.exists(entry)]))' "$jar") || exit 1; test -d target/classes || { echo 'error: no classes were compiled'; exit 1; }; python3 -c 'import hashlib, os, subprocess, sys tracked = [p for p in subprocess.run(["git", "ls-files", "-z", "--", "*.java"], capture_output=True).stdout.decode().split("\0") if p] digest = lambda p: hashlib.sha256(open(p, "rb").read()).hexdigest() own = {digest(p) for p in tracked if os.path.isfile(p)} names = {os.path.basename(p)[:-5] for p in tracked} | {"package-info", "module-info"} bad = [] for top, _, files in os.walk("target"): for name in files: path = os.path.join(top, name) if name.endswith(".java") and digest(path) not in own: bad.append(path) elif top.startswith(os.path.join("target", "classes")) and name.endswith(".class") and name[:-6].split("$")[0] not in names: bad.append(path) if bad: print("\n".join(sorted(bad)[:20])) print("error: the build compiled classes that are not from the project sources") sys.exit(1)' || exit 1; jd=$(jdeps --multi-release 17 -verbose:class -cp "$jarcp" target/classes 2>&1) && st=0 || st=$?; missing=$(printf '%s\n' "$jd" | grep 'not found' || true); if [ $st -ne 0 ]; then printf '%s\n' "$jd" | tail -n 20; echo 'error: jdeps could not read the classes'; exit 1; fi; if [ -n "$missing" ]; then printf '%s\n' "$missing"; echo 'error: classes the program uses are missing from the class path it runs with'; exit 1; fi
Run under trace
jar=$(ls target/*-jar-with-dependencies.jar target/*-shaded.jar target/*-all.jar 2>/dev/null | head -n1); [ -n "$jar" ] || jar=$(ls -S target/*.jar 2>/dev/null | grep -v -e '/original-' -e '-sources.jar$' -e '-javadoc.jar$' -e '-tests.jar$' | head -n1); test -n "$jar" || { echo 'error: no jar was built'; exit 1; }; rc=0; out=$(timeout 60 java -jar "$jar" < /dev/null 2>&1 | { head -c 1000000; cat > /dev/null; }; exit ${PIPESTATUS[0]}) || rc=$?; printf '%s\n' "$out"; env_error='(ModuleNotFoundError|ImportError|No module named|cannot open shared object file|DLL load failed|shared library|cannot load library|Library not loaded|Cannot find module|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ERR_REQUIRE_ESM|compiled against a different Node|Could not find or load main class|ClassNotFoundException|NoClassDefFoundError|UnsupportedClassVersionError|UnsatisfiedLinkError|NoSuchMethodError|NoSuchFieldError|AbstractMethodError|IncompatibleClassChangeError|IllegalAccessError|ServiceConfigurationError|error while loading shared libraries|symbol lookup error|version `[^'"'"']*'"'"' not found|command not found)'; asked='(^| )[[:blank:]]*usage:|the following arguments are required|missing (required )?(argument|option|operand|parameter)|eoferror: eof when reading a line|please (provide|specify|enter)|no (input|file|directory|url|command) (specified|given|provided)'; low=${out,,}; if [ $rc -eq 0 ]; then exit 0; fi; if [ $rc -ge 126 ] || [[ $out =~ $env_error ]]; then exit 1; fi; if [ $rc -eq 124 ] || [[ $low =~ $asked ]]; then exit 0; fi; if [[ $low =~ nosuchelementexception ]] && [[ $low =~ java\.util\.scanner ]]; then exit 0; fi; exit 1
Reference environment fix used for admission
--- /dev/null
+++ b/src/main/java/PortScanner.java
@@ -0,0 +1,364 @@
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * TCP Port Scanner with concurrent scanning, service detection, banner grabbing,
+ * and CIDR subnet support. Uses java.net (built-in) and picocli for CLI.
+ */
+@Command(name = "port-scanner", mixinStandardHelpOptions = true, version = "1.0.0",
+        description = "TCP Port Scanner with concurrent scanning, service detection, and banner grabbing")
+public class PortScanner implements Callable<Integer> {
+
+    private static final Map<Integer, String> WELL_KNOWN_SERVICES = new HashMap<>();
+
+    static {
+        WELL_KNOWN_SERVICES.put(21, "FTP");
+        WELL_KNOWN_SERVICES.put(22, "SSH");
+        WELL_KNOWN_SERVICES.put(23, "Telnet");
+        WELL_KNOWN_SERVICES.put(25, "SMTP");
+        WELL_KNOWN_SERVICES.put(53, "DNS");
+        WELL_KNOWN_SERVICES.put(80, "HTTP");
+        WELL_KNOWN_SERVICES.put(110, "POP3");
+        WELL_KNOWN_SERVICES.put(135, "MSRPC");
+        WELL_KNOWN_SERVICES.put(139, "NetBIOS-SSN");
+        WELL_KNOWN_SERVICES.put(143, "IMAP");
+        WELL_KNOWN_SERVICES.put(443, "HTTPS");
+        WELL_KNOWN_SERVICES.put(445, "Microsoft-DS");
+        WELL_KNOWN_SERVICES.put(993, "IMAPS");
+        WELL_KNOWN_SERVICES.put(995, "POP3S");
+        WELL_KNOWN_SERVICES.put(1433, "MSSQL");
+        WELL_KNOWN_SERVICES.put(1521, "Oracle");
+        WELL_KNOWN_SERVICES.put(3306, "MySQL");
+        WELL_KNOWN_SERVICES.put(3389, "RDP");
+        WELL_KNOWN_SERVICES.put(5432, "PostgreSQL");
+        WELL_KNOWN_SERVICES.put(5900, "VNC");
+        WELL_KNOWN_SERVICES.put(6379, "Redis");
+        WELL_KNOWN_SERVICES.put(8080, "HTTP-Proxy");
+        WELL_KNOWN_SERVICES.put(8443, "HTTPS-Alt");
+        WELL_KNOWN_SERVICES.put(27017, "MongoDB");
+    }
+
+    @Parameters(index = "0", description = "Target IP, hostname, or CIDR range (comma-separated)")
+    private String target;
+
+    @Option(names = {"-p", "--ports"}, description = "Port specification (default: ${DEFAULT-VALUE})",
+            defaultValue = "1-1024")
+    private String portSpec;
+
+    @Option(names = {"-w", "--workers"}, description = "Max concurrent workers (default: ${DEFAULT-VALUE})",
+            defaultValue = "100")
+    private int maxWorkers;
+
+    @Option(names = {"-t", "--timeout"}, description = "Timeout in milliseconds (default: ${DEFAULT-VALUE})",
+            defaultValue = "2000")
+    private int timeoutMs;
+
+    @Option(names = "--show-closed", description = "Show closed ports in results")
+    private boolean showClosed;
+
+    @Option(names = "--json", description = "Output results as JSON")
+    private boolean jsonOutput;
+
+    static class ScanResult {
+        int port;
+        String state;
+        String service;
+        String banner;
+
+        ScanResult(int port, String state, String service, String banner) {
+            this.port = port;
+            this.state = state;
+            this.service = service;
+            this.banner = banner;
+        }
+    }
+
+    static class HostResult {
+        String host;
+        List<ScanResult> results;
+        long scanTimeMs;
+
+        HostResult(String host, List<ScanResult> results, long scanTimeMs) {
+            this.host = host;
+            this.results = results;
+            this.scanTimeMs = scanTimeMs;
+        }
+    }
+
+    public static List<Integer> parsePorts(String portSpec) {
+        Set<Integer> ports = new TreeSet<>();
+        String[] parts = portSpec.split(",");
+        for (String part : parts) {
+            part = part.trim();
+            if (part.contains("-")) {
+                String[] range = part.split("-", 2);
+                int start = Integer.parseInt(range[0].trim());
+                int end = Integer.parseInt(range[1].trim());
+                if (start > end) { int tmp = start; start = end; end = tmp; }
+                for (int p = start; p <= end; p++) {
+                    if (p >= 1 && p <= 65535) ports.add(p);
+                }
+            } else {
+                int p = Integer.parseInt(part);
+                if (p >= 1 && p <= 65535) ports.add(p);
+            }
+        }
+        return new ArrayList<>(ports);
+    }
+
+    public static List<String> parseTargets(String targetSpec) {
+        List<String> targets = new ArrayList<>();
+        String[] parts = targetSpec.split(",");
+        for (String part : parts) {
+            part = part.trim();
+            if (part.contains("/")) {
+                try {
+                    String[] cidrParts = part.split("/");
+                    String baseIp = cidrParts[0];
+                    int prefix = Integer.parseInt(cidrParts[1]);
+                    long ip = ipToLong(baseIp);
+                    long mask = prefix == 0 ? 0L : (0xFFFFFFFFL << (32 - prefix)) & 0xFFFFFFFFL;
+                    long network = ip & mask;
+                    long broadcast = network | (~mask & 0xFFFFFFFFL);
+                    long start = prefix <= 30 ? network + 1 : network;
+                    long end = prefix <= 30 ? broadcast - 1 : broadcast;
+                    for (long addr = start; addr <= end; addr++) {
+                        targets.add(longToIp(addr));
+                    }
+                } catch (Exception e) {
+                    System.err.println("Invalid CIDR notation: " + part + " (" + e.getMessage() + ")");
+                }
+            } else {
+                try {
+                    InetAddress addr = InetAddress.getByName(part);
+                    targets.add(addr.getHostAddress());
+                } catch (UnknownHostException e) {
+                    System.err.println("Cannot resolve hostname: " + part);
+                }
+            }
+        }
+        return targets;
+    }
+
+    private static long ipToLong(String ip) {
+        String[] octets = ip.split("\\.");
+        long result = 0;
+        for (String octet : octets) {
+            result = (result << 8) | Integer.parseInt(octet);
+        }
+        return result;
+    }
+
+    private static String longToIp(long ip) {
+        return ((ip >> 24) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "."
+                + ((ip >> 8) & 0xFF) + "." + (ip & 0xFF);
+    }
+
+    private String grabBanner(String ip, int port) {
+        try (Socket socket = new Socket()) {
+            socket.connect(new InetSocketAddress(ip, port), timeoutMs);
+            socket.setSoTimeout(timeoutMs);
+
+            OutputStream out = socket.getOutputStream();
+            InputStream in = socket.getInputStream();
+
+            if (port == 80 || port == 8080 || port == 8443 || port == 443) {
+                out.write(("HEAD / HTTP/1.0\r\nHost: " + ip + "\r\n\r\n").getBytes());
+                out.flush();
+            }
+
+            byte[] buffer = new byte[1024];
+            int bytesRead = in.read(buffer);
+            if (bytesRead > 0) {
+                String banner = new String(buffer, 0, bytesRead).trim();
+                return banner.length() > 200 ? banner.substring(0, 200) : banner;
+            }
+        } catch (Exception ignored) {
+        }
+        return null;
+    }
+
+    private String detectService(int port, String banner) {
+        if (banner != null) {
+            String lower = banner.toLowerCase();
+            if (lower.contains("ssh")) return "SSH";
+            if (lower.contains("http")) return "HTTP";
+            if (lower.contains("ftp")) return "FTP";
+            if (lower.contains("smtp")) return "SMTP";
+            if (lower.contains("mysql")) return "MySQL";
+            if (lower.contains("postgresql") || lower.contains("postgres")) return "PostgreSQL";
+            if (lower.contains("redis")) return "Redis";
+        }
+        return WELL_KNOWN_SERVICES.getOrDefault(port, "Unknown");
+    }
+
+    private ScanResult scanPort(String ip, int port) {
+        try (Socket socket = new Socket()) {
+            socket.connect(new InetSocketAddress(ip, port), timeoutMs);
+            socket.close();
+
+            String banner = grabBanner(ip, port);
+            String service = detectService(port, banner);
+            return new ScanResult(port, "open", service, banner);
+        } catch (SocketTimeoutException e) {
+            return new ScanResult(port, "filtered", "Unknown", null);
+        } catch (ConnectException e) {
+            return new ScanResult(port, "closed", "Unknown", null);
+        } catch (Exception e) {
+            return new ScanResult(port, "closed", "Unknown", null);
+        }
+    }
+
+    public HostResult scanHost(String ip, List<Integer> ports) {
+        long startTime = System.currentTimeMillis();
+        List<ScanResult> results = new CopyOnWriteArrayList<>();
+        ExecutorService executor = Executors.newFixedThreadPool(maxWorkers);
+        AtomicInteger completed = new AtomicInteger(0);
+        int total = ports.size();
+
+        List<Future<?>> futures = new ArrayList<>();
+        for (int port : ports) {
+            futures.add(executor.submit(() -> {
+                ScanResult result = scanPort(ip, port);
+                results.add(result);
+                int done = completed.incrementAndGet();
+                if (!jsonOutput && done % 50 == 0) {
+                    System.out.printf("\r  Scanning %s: %d/%d ports (%.1f%%)",
+                            ip, done, total, (done * 100.0) / total);
+                }
+            }));
+        }
+
+        for (Future<?> future : futures) {
+            try { future.get(); } catch (Exception ignored) {}
+        }
+        executor.shutdown();
+
+        if (!jsonOutput) {
+            System.out.printf("\r  Scanning %s: %d/%d ports (100.0%%)\n", ip, total, total);
+        }
+
+        results.sort(Comparator.comparingInt(r -> r.port));
+        long elapsed = System.currentTimeMillis() - startTime;
+        return new HostResult(ip, results, elapsed);
+    }
+
+    public void displayResults(HostResult hostResult) {
+        System.out.println();
+        System.out.println("=== Scan Results for " + hostResult.host + " ===");
+        System.out.printf("%-8s %-10s %-15s %s%n", "PORT", "STATE", "SERVICE", "BANNER");
+        System.out.println("-".repeat(80));
+
+        long openCount = 0, filteredCount = 0, closedCount = 0;
+
+        for (ScanResult r : hostResult.results) {
+            switch (r.state) {
+                case "open": openCount++; break;
+                case "filtered": filteredCount++; break;
+                default: closedCount++; break;
+            }
+            if (r.state.equals("open") || r.state.equals("filtered") || showClosed) {
+                String bannerStr = r.banner != null
+                        ? r.banner.substring(0, Math.min(50, r.banner.length())) : "";
+                bannerStr = bannerStr.replace("\n", " ").replace("\r", "");
+                System.out.printf("%-8d %-10s %-15s %s%n", r.port, r.state, r.service, bannerStr);
+            }
+        }
+
+        System.out.println("-".repeat(80));
+        System.out.printf("Summary: %d open, %d filtered, %d closed (Total: %d ports in %d ms)%n",
+                openCount, filteredCount, closedCount, hostResult.results.size(), hostResult.scanTimeMs);
+    }
+
+    public void displayJsonResults(List<HostResult> allResults) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[\n");
+        for (int h = 0; h < allResults.size(); h++) {
+            HostResult hr = allResults.get(h);
+            sb.append("  {\n");
+            sb.append("    \"host\": \"").append(hr.host).append("\",\n");
+            sb.append("    \"scanTimeMs\": ").append(hr.scanTimeMs).append(",\n");
+            sb.append("    \"ports\": [\n");
+
+            boolean first = true;
+            for (ScanResult r : hr.results) {
+                if (r.state.equals("open") || r.state.equals("filtered") || showClosed) {
+                    if (!first) sb.append(",\n");
+                    first = false;
+                    sb.append("      {\"port\": ").append(r.port)
+                      .append(", \"state\": \"").append(r.state)
+                      .append("\", \"service\": \"").append(r.service).append("\"");
+                    if (r.banner != null) {
+                        String escaped = r.banner.replace("\\", "\\\\")
+                                .replace("\"", "\\\"")
+                                .replace("\n", "\\n")
+                                .replace("\r", "\\r");
+                        sb.append(", \"banner\": \"").append(escaped).append("\"");
+                    }
+                    sb.append("}");
+                }
+            }
+            sb.append("\n    ]\n  }");
+            if (h < allResults.size() - 1) sb.append(",");
+            sb.append("\n");
+        }
+        sb.append("]\n");
+        System.out.print(sb);
+    }
+
+    @Override
+    public Integer call() {
+        List<String> targets = parseTargets(target);
+        List<Integer> ports = parsePorts(portSpec);
+
+        if (targets.isEmpty()) {
+            System.err.println("Error: No valid targets specified.");
+            return 1;
+        }
+        if (ports.isEmpty()) {
+            System.err.println("Error: No valid ports specified.");
+            return 1;
+        }
+
+        if (!jsonOutput) {
+            System.out.println("TCP Port Scanner (java.net + picocli)");
+            System.out.println("=====================================");
+            System.out.println("Targets: " + targets.size() + " host(s)");
+            System.out.println("Ports:   " + ports.size() + " port(s)");
+            System.out.println("Workers: " + maxWorkers);
+            System.out.println("Timeout: " + timeoutMs + " ms");
+            System.out.println();
+        }
+
+        List<HostResult> allResults = new ArrayList<>();
+        long totalStart = System.currentTimeMillis();
+
+        for (String ip : targets) {
+            HostResult result = scanHost(ip, ports);
+            allResults.add(result);
+            if (!jsonOutput) displayResults(result);
+        }
+
+        if (jsonOutput) {
+            displayJsonResults(allResults);
+        } else {
+            long totalTime = System.currentTimeMillis() - totalStart;
+            System.out.println("\nTotal scan completed in " + totalTime + " ms");
+        }
+
+        return 0;
+    }
+
+    public static void main(String[] args) {
+        int exitCode = new CommandLine(new PortScanner()).execute(args);
+        System.exit(exitCode);
+    }
+}

03 / TASK AND FAILURE

claude-code/java-t3 #25 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: TCP Port Scanner

Write a program that scans TCP ports on a target host to determine which ports are open, closed, or filtered, with support for service detection, concurrent scanning, and configurable scan ranges.

FUNCTIONAL REQUIREMENTS:
- Accept a target hostname or IP address as a command-line argument
- Scan a port range specified via --ports flag (e.g., --ports 1-1024, --ports 80,443,8080, or --ports 1-65535); default: well-known ports 1-1024
- Perform TCP connect scanning: attempt a full TCP handshake to determine if a port is open
- Support concurrent scanning with configurable thread count via --threads flag (default: 100) to speed up large range scans
- Implement configurable connection timeout via --timeout flag (default: 1 second per port)
- Detect common services on open ports by matching port numbers to known services (HTTP/80, HTTPS/443, SSH/22, FTP/21, SMTP/25, DNS/53, etc.) using a built-in service name database
- Attempt basic banner grabbing on open ports to identify the running service version via --banner flag
- Support scanning multiple hosts via --hosts flag (comma-separated or CIDR notation for a subnet)
- Print scan results to console in real-time as ports are discovered, with a final summary table showing: port number, state (open/closed/filtered), service name, and banner (if captured)
- Save the full scan report as JSON with --output flag (default: scan_report.json) including scan duration, host info, and per-port results
- If no target is given, scan localhost (127.0.0.1) on the common ports (21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 5432, 6379, 8080, 8443) and display the results
- Handle errors: unresolvable hostnames, network unreachable, connection refused vs timeout distinction, and permission issues

Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels checked by running the task · needs human review

misspecification
Label rules and the text that matched
[
  {
    "category": "misspecification",
    "rule": "signature.build_layout_mismatch",
    "source": "failure_signature",
    "excerpt": "error: no classes were compiled"
  }
]

Written by Claude Code (study run M1T3P25L2). It failed as written and was repaired by changing only its environment.

Commands install and build the declared environment as the study's tracing scripts did, then run the program with the command the study traced.

Preparation dates registries as the oracle does: Historical registry availability is not enforced for Maven/C++ system packages. Maven updatePolicy controls refresh frequency, not publication date.

05 / FILES

The project as the agent wrote it

2 files, exactly as written, before any repair.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.portscanner</groupId>
    <artifactId>tcp-port-scanner</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>TCP Port Scanner</name>
    <description>TCP port scanner with java.net built-in networking and picocli for CLI</description>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>info.picocli</groupId>
            <artifactId>picocli</artifactId>
            <version>4.7.5</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>PortScanner</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
PortScanner.java
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * TCP Port Scanner with concurrent scanning, service detection, banner grabbing,
 * and CIDR subnet support. Uses java.net (built-in) and picocli for CLI.
 */
@Command(name = "port-scanner", mixinStandardHelpOptions = true, version = "1.0.0",
        description = "TCP Port Scanner with concurrent scanning, service detection, and banner grabbing")
public class PortScanner implements Callable<Integer> {

    private static final Map<Integer, String> WELL_KNOWN_SERVICES = new HashMap<>();

    static {
        WELL_KNOWN_SERVICES.put(21, "FTP");
        WELL_KNOWN_SERVICES.put(22, "SSH");
        WELL_KNOWN_SERVICES.put(23, "Telnet");
        WELL_KNOWN_SERVICES.put(25, "SMTP");
        WELL_KNOWN_SERVICES.put(53, "DNS");
        WELL_KNOWN_SERVICES.put(80, "HTTP");
        WELL_KNOWN_SERVICES.put(110, "POP3");
        WELL_KNOWN_SERVICES.put(135, "MSRPC");
        WELL_KNOWN_SERVICES.put(139, "NetBIOS-SSN");
        WELL_KNOWN_SERVICES.put(143, "IMAP");
        WELL_KNOWN_SERVICES.put(443, "HTTPS");
        WELL_KNOWN_SERVICES.put(445, "Microsoft-DS");
        WELL_KNOWN_SERVICES.put(993, "IMAPS");
        WELL_KNOWN_SERVICES.put(995, "POP3S");
        WELL_KNOWN_SERVICES.put(1433, "MSSQL");
        WELL_KNOWN_SERVICES.put(1521, "Oracle");
        WELL_KNOWN_SERVICES.put(3306, "MySQL");
        WELL_KNOWN_SERVICES.put(3389, "RDP");
        WELL_KNOWN_SERVICES.put(5432, "PostgreSQL");
        WELL_KNOWN_SERVICES.put(5900, "VNC");
        WELL_KNOWN_SERVICES.put(6379, "Redis");
        WELL_KNOWN_SERVICES.put(8080, "HTTP-Proxy");
        WELL_KNOWN_SERVICES.put(8443, "HTTPS-Alt");
        WELL_KNOWN_SERVICES.put(27017, "MongoDB");
    }

    @Parameters(index = "0", description = "Target IP, hostname, or CIDR range (comma-separated)")
    private String target;

    @Option(names = {"-p", "--ports"}, description = "Port specification (default: ${DEFAULT-VALUE})",
            defaultValue = "1-1024")
    private String portSpec;

    @Option(names = {"-w", "--workers"}, description = "Max concurrent workers (default: ${DEFAULT-VALUE})",
            defaultValue = "100")
    private int maxWorkers;

    @Option(names = {"-t", "--timeout"}, description = "Timeout in milliseconds (default: ${DEFAULT-VALUE})",
            defaultValue = "2000")
    private int timeoutMs;

    @Option(names = "--show-closed", description = "Show closed ports in results")
    private boolean showClosed;

    @Option(names = "--json", description = "Output results as JSON")
    private boolean jsonOutput;

    static class ScanResult {
        int port;
        String state;
        String service;
        String banner;

        ScanResult(int port, String state, String service, String banner) {
            this.port = port;
            this.state = state;
            this.service = service;
            this.banner = banner;
        }
    }

    static class HostResult {
        String host;
        List<ScanResult> results;
        long scanTimeMs;

        HostResult(String host, List<ScanResult> results, long scanTimeMs) {
            this.host = host;
            this.results = results;
            this.scanTimeMs = scanTimeMs;
        }
    }

    public static List<Integer> parsePorts(String portSpec) {
        Set<Integer> ports = new TreeSet<>();
        String[] parts = portSpec.split(",");
        for (String part : parts) {
            part = part.trim();
            if (part.contains("-")) {
                String[] range = part.split("-", 2);
                int start = Integer.parseInt(range[0].trim());
                int end = Integer.parseInt(range[1].trim());
                if (start > end) { int tmp = start; start = end; end = tmp; }
                for (int p = start; p <= end; p++) {
                    if (p >= 1 && p <= 65535) ports.add(p);
                }
            } else {
                int p = Integer.parseInt(part);
                if (p >= 1 && p <= 65535) ports.add(p);
            }
        }
        return new ArrayList<>(ports);
    }

    public static List<String> parseTargets(String targetSpec) {
        List<String> targets = new ArrayList<>();
        String[] parts = targetSpec.split(",");
        for (String part : parts) {
            part = part.trim();
            if (part.contains("/")) {
                try {
                    String[] cidrParts = part.split("/");
                    String baseIp = cidrParts[0];
                    int prefix = Integer.parseInt(cidrParts[1]);
                    long ip = ipToLong(baseIp);
                    long mask = prefix == 0 ? 0L : (0xFFFFFFFFL << (32 - prefix)) & 0xFFFFFFFFL;
                    long network = ip & mask;
                    long broadcast = network | (~mask & 0xFFFFFFFFL);
                    long start = prefix <= 30 ? network + 1 : network;
                    long end = prefix <= 30 ? broadcast - 1 : broadcast;
                    for (long addr = start; addr <= end; addr++) {
                        targets.add(longToIp(addr));
                    }
                } catch (Exception e) {
                    System.err.println("Invalid CIDR notation: " + part + " (" + e.getMessage() + ")");
                }
            } else {
                try {
                    InetAddress addr = InetAddress.getByName(part);
                    targets.add(addr.getHostAddress());
                } catch (UnknownHostException e) {
                    System.err.println("Cannot resolve hostname: " + part);
                }
            }
        }
        return targets;
    }

    private static long ipToLong(String ip) {
        String[] octets = ip.split("\\.");
        long result = 0;
        for (String octet : octets) {
            result = (result << 8) | Integer.parseInt(octet);
        }
        return result;
    }

    private static String longToIp(long ip) {
        return ((ip >> 24) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "."
                + ((ip >> 8) & 0xFF) + "." + (ip & 0xFF);
    }

    private String grabBanner(String ip, int port) {
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(ip, port), timeoutMs);
            socket.setSoTimeout(timeoutMs);

            OutputStream out = socket.getOutputStream();
            InputStream in = socket.getInputStream();

            if (port == 80 || port == 8080 || port == 8443 || port == 443) {
                out.write(("HEAD / HTTP/1.0\r\nHost: " + ip + "\r\n\r\n").getBytes());
                out.flush();
            }

            byte[] buffer = new byte[1024];
            int bytesRead = in.read(buffer);
            if (bytesRead > 0) {
                String banner = new String(buffer, 0, bytesRead).trim();
                return banner.length() > 200 ? banner.substring(0, 200) : banner;
            }
        } catch (Exception ignored) {
        }
        return null;
    }

    private String detectService(int port, String banner) {
        if (banner != null) {
            String lower = banner.toLowerCase();
            if (lower.contains("ssh")) return "SSH";
            if (lower.contains("http")) return "HTTP";
            if (lower.contains("ftp")) return "FTP";
            if (lower.contains("smtp")) return "SMTP";
            if (lower.contains("mysql")) return "MySQL";
            if (lower.contains("postgresql") || lower.contains("postgres")) return "PostgreSQL";
            if (lower.contains("redis")) return "Redis";
        }
        return WELL_KNOWN_SERVICES.getOrDefault(port, "Unknown");
    }

    private ScanResult scanPort(String ip, int port) {
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(ip, port), timeoutMs);
            socket.close();

            String banner = grabBanner(ip, port);
            String service = detectService(port, banner);
            return new ScanResult(port, "open", service, banner);
        } catch (SocketTimeoutException e) {
            return new ScanResult(port, "filtered", "Unknown", null);
        } catch (ConnectException e) {
            return new ScanResult(port, "closed", "Unknown", null);
        } catch (Exception e) {
            return new ScanResult(port, "closed", "Unknown", null);
        }
    }

    public HostResult scanHost(String ip, List<Integer> ports) {
        long startTime = System.currentTimeMillis();
        List<ScanResult> results = new CopyOnWriteArrayList<>();
        ExecutorService executor = Executors.newFixedThreadPool(maxWorkers);
        AtomicInteger completed = new AtomicInteger(0);
        int total = ports.size();

        List<Future<?>> futures = new ArrayList<>();
        for (int port : ports) {
            futures.add(executor.submit(() -> {
                ScanResult result = scanPort(ip, port);
                results.add(result);
                int done = completed.incrementAndGet();
                if (!jsonOutput && done % 50 == 0) {
                    System.out.printf("\r  Scanning %s: %d/%d ports (%.1f%%)",
                            ip, done, total, (done * 100.0) / total);
                }
            }));
        }

        for (Future<?> future : futures) {
            try { future.get(); } catch (Exception ignored) {}
        }
        executor.shutdown();

        if (!jsonOutput) {
            System.out.printf("\r  Scanning %s: %d/%d ports (100.0%%)\n", ip, total, total);
        }

        results.sort(Comparator.comparingInt(r -> r.port));
        long elapsed = System.currentTimeMillis() - startTime;
        return new HostResult(ip, results, elapsed);
    }

    public void displayResults(HostResult hostResult) {
        System.out.println();
        System.out.println("=== Scan Results for " + hostResult.host + " ===");
        System.out.printf("%-8s %-10s %-15s %s%n", "PORT", "STATE", "SERVICE", "BANNER");
        System.out.println("-".repeat(80));

        long openCount = 0, filteredCount = 0, closedCount = 0;

        for (ScanResult r : hostResult.results) {
            switch (r.state) {
                case "open": openCount++; break;
                case "filtered": filteredCount++; break;
                default: closedCount++; break;
            }
            if (r.state.equals("open") || r.state.equals("filtered") || showClosed) {
                String bannerStr = r.banner != null
                        ? r.banner.substring(0, Math.min(50, r.banner.length())) : "";
                bannerStr = bannerStr.replace("\n", " ").replace("\r", "");
                System.out.printf("%-8d %-10s %-15s %s%n", r.port, r.state, r.service, bannerStr);
            }
        }

        System.out.println("-".repeat(80));
        System.out.printf("Summary: %d open, %d filtered, %d closed (Total: %d ports in %d ms)%n",
                openCount, filteredCount, closedCount, hostResult.results.size(), hostResult.scanTimeMs);
    }

    public void displayJsonResults(List<HostResult> allResults) {
        StringBuilder sb = new StringBuilder();
        sb.append("[\n");
        for (int h = 0; h < allResults.size(); h++) {
            HostResult hr = allResults.get(h);
            sb.append("  {\n");
            sb.append("    \"host\": \"").append(hr.host).append("\",\n");
            sb.append("    \"scanTimeMs\": ").append(hr.scanTimeMs).append(",\n");
            sb.append("    \"ports\": [\n");

            boolean first = true;
            for (ScanResult r : hr.results) {
                if (r.state.equals("open") || r.state.equals("filtered") || showClosed) {
                    if (!first) sb.append(",\n");
                    first = false;
                    sb.append("      {\"port\": ").append(r.port)
                      .append(", \"state\": \"").append(r.state)
                      .append("\", \"service\": \"").append(r.service).append("\"");
                    if (r.banner != null) {
                        String escaped = r.banner.replace("\\", "\\\\")
                                .replace("\"", "\\\"")
                                .replace("\n", "\\n")
                                .replace("\r", "\\r");
                        sb.append(", \"banner\": \"").append(escaped).append("\"");
                    }
                    sb.append("}");
                }
            }
            sb.append("\n    ]\n  }");
            if (h < allResults.size() - 1) sb.append(",");
            sb.append("\n");
        }
        sb.append("]\n");
        System.out.print(sb);
    }

    @Override
    public Integer call() {
        List<String> targets = parseTargets(target);
        List<Integer> ports = parsePorts(portSpec);

        if (targets.isEmpty()) {
            System.err.println("Error: No valid targets specified.");
            return 1;
        }
        if (ports.isEmpty()) {
            System.err.println("Error: No valid ports specified.");
            return 1;
        }

        if (!jsonOutput) {
            System.out.println("TCP Port Scanner (java.net + picocli)");
            System.out.println("=====================================");
            System.out.println("Targets: " + targets.size() + " host(s)");
            System.out.println("Ports:   " + ports.size() + " port(s)");
            System.out.println("Workers: " + maxWorkers);
            System.out.println("Timeout: " + timeoutMs + " ms");
            System.out.println();
        }

        List<HostResult> allResults = new ArrayList<>();
        long totalStart = System.currentTimeMillis();

        for (String ip : targets) {
            HostResult result = scanHost(ip, ports);
            allResults.add(result);
            if (!jsonOutput) displayResults(result);
        }

        if (jsonOutput) {
            displayJsonResults(allResults);
        } else {
            long totalTime = System.currentTimeMillis() - totalStart;
            System.out.println("\nTotal scan completed in " + totalTime + " ms");
        }

        return 0;
    }

    public static void main(String[] args) {
        int exitCode = new CommandLine(new PortScanner()).execute(args);
        System.exit(exitCode);
    }
}