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}