app_instance_detector/
lib.rs

1// ==== ./src/lib.rs ====
2/*!
3 * This file is part of App Instance Detector, an Autonomo AI, FZCO, Project.
4 * autonomo-file-copy is a component of Autonomo AI Programming Agent.
5 *
6 * Copyright © 2025 Autonomo AI, FZCO
7 * Author: Theodore R. Smith <theodore.smith@autonomo.codes>
8 *   GPG Fingerprint: 6CAC F838 454C 8912 8AA2  26DB 89DC D8F1 3BB9 33B3
9 *   https://www.phpexperts.pro/
10 *   https://www.autonomo.codes/
11 *   https://github.com/AutonomoDev/autonomo
12 *
13 * This file is proprietary.
14 * All rights are reserved.
15 */
16
17use std::{
18    io::{Read, Write},
19    net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream},
20    thread,
21    time::Duration,
22};
23
24/// Finds all running instances by checking ports sequentially and sending a customizable handshake request.
25/// Returns a vector of tuples, where each tuple contains the port and the
26/// string response received from the running instance.
27///
28/// The `request_message` is the byte sequence sent to each potential instance.
29/// The `timeout` applies to both connecting and reading the response.
30pub fn find_all_instances(
31    base_port: u16,
32    max_search: u16,
33    timeout: Duration,
34    request_message: &[u8],
35) -> Vec<(u16, String)> {
36    let mut instances = Vec::new();
37
38    for port in base_port..(base_port + max_search) {
39        // Attempt a handshake. If it succeeds, add it to our list.
40        // This is more direct than checking is_port_in_use first.
41        if let Ok(response) = send_handshake_request(port, timeout, request_message) {
42            instances.push((port, response));
43        }
44    }
45
46    instances
47}
48
49/// Finds the next available port starting from `base_port` by checking sequentially.
50///
51/// It iterates through ports from `base_port` up to `base_port + max_search - 1`.
52/// Returns the first port in the search range that is not in use.
53/// If no port is found within the specified range, it returns `base_port` as a fallback.
54pub fn find_next_available_port(base_port: u16, max_search: u16) -> u16 {
55    for port in base_port..(base_port + max_search) {
56        if !is_port_in_use(port) {
57            return port;
58        }
59    }
60    // Fallback if no port is found
61    base_port
62}
63
64/// Starts a background handshake server on the given port.
65///
66/// The server listens for any incoming TCP connection. For each connection, it attempts
67/// to read some data (the "handshake request"). It then responds with a string
68/// generated by the `response_generator` closure.
69///
70/// This function spawns a new thread and returns immediately.
71pub fn start_handshake_server<F>(port: u16, response_generator: F)
72where
73    F: Fn() -> String + Send + Sync + 'static + Clone,
74{
75    thread::spawn(move || {
76        let listener = match TcpListener::bind((Ipv4Addr::LOCALHOST, port)) {
77            Ok(listener) => listener,
78            Err(e) => {
79                eprintln!("[!] Failed to bind handshake server on port {}: {}", port, e);
80                return;
81            }
82        };
83        eprintln!("[+] Handshake server listening on port {}", port);
84
85        for stream in listener.incoming() {
86            match stream {
87                Ok(mut stream) => {
88                    let local_generator = response_generator.clone();
89                    thread::spawn(move || {
90                        let mut buffer = [0u8; 1024];
91                        let peer_addr = stream.peer_addr()
92                            .map(|a| a.to_string())
93                            .unwrap_or_else(|_| "unknown".to_string());
94                        
95                        // Handle the handshake.
96                        match stream.read(&mut buffer) {
97                            Ok(_) => {
98                                // We don't care how many bytes were read, just that a request was made.
99                                let response_str = local_generator();
100                                if let Err(e) = stream.write_all(response_str.as_bytes()) {
101                                    eprintln!("[!] Error sending handshake response to {}: {}", peer_addr, e);
102                                }
103                            }
104                            Err(e) => {
105                                eprintln!("[!] Error reading handshake request from {}: {}", peer_addr, e);
106                            }
107                        }
108                    });
109                }
110                Err(e) => {
111                    eprintln!("[!] Handshake server connection error on port {}: {}", port, e);
112                }
113            }
114        }
115        eprintln!("[-] Handshake server on port {} stopped.", port);
116    });
117}
118
119/// Checks if a port is in use by attempting to connect to it.
120/// This function uses a short, fixed timeout.
121pub fn is_port_in_use(port: u16) -> bool {
122    let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port);
123    TcpStream::connect_timeout(&addr, Duration::from_millis(100)).is_ok()
124}
125
126/// Sends a customizable handshake request to a given port and returns the response.
127pub fn send_handshake_request(
128    port: u16,
129    timeout: Duration,
130    request_message: &[u8],
131) -> anyhow::Result<String> {
132    let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port);
133    let mut stream = TcpStream::connect_timeout(&addr, timeout)?;
134
135    stream.set_read_timeout(Some(timeout))?;
136    stream.write_all(request_message)?;
137
138    let mut buffer = [0u8; 1024];
139    let n = stream.read(&mut buffer)?;
140    let response = String::from_utf8_lossy(&buffer[..n]).to_string();
141
142    Ok(response)
143}