edb_engine/rpc/
utils.rs

1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Utility functions for RPC server operations.
18//!
19//! This module provides common utilities needed by the RPC server including:
20//! - Port discovery and availability checking
21//! - Socket address parsing with sensible defaults
22//! - Error handling helpers for JSON-RPC responses
23//! - Convenience functions for common error types
24//!
25//! # Port Management
26//!
27//! The module includes functions to find available ports automatically,
28//! starting from a preferred port (3000) and searching upward if needed.
29//!
30//! # Error Handling
31//!
32//! Provides helper functions to create properly formatted JSON-RPC error
33//! responses with standard error codes and descriptive messages.
34
35use eyre::{eyre, Result};
36use std::net::{SocketAddr, TcpListener};
37use tracing::{debug, info};
38
39/// Find an available port starting from a base port
40pub fn find_available_port(start_port: u16) -> Result<u16> {
41    for port in start_port..65535 {
42        if is_port_available(port) {
43            info!("Found available port: {}", port);
44            return Ok(port);
45        }
46    }
47    Err(eyre!("No available port found in range {}-65534", start_port))
48}
49
50/// Check if a port is available on localhost
51pub fn is_port_available(port: u16) -> bool {
52    match TcpListener::bind(("127.0.0.1", port)) {
53        Ok(_) => {
54            debug!("Port {} is available", port);
55            true
56        }
57        Err(_) => {
58            debug!("Port {} is not available", port);
59            false
60        }
61    }
62}
63
64/// Get default RPC server port (tries 3000 first, then searches)
65pub fn get_default_rpc_port() -> Result<u16> {
66    if is_port_available(3000) {
67        Ok(3000)
68    } else {
69        find_available_port(3001)
70    }
71}
72
73/// Parse a socket address, with sensible defaults
74pub fn parse_socket_addr(addr_str: Option<&str>, default_port: u16) -> Result<SocketAddr> {
75    match addr_str {
76        Some(addr) => addr.parse().map_err(|e| eyre!("Invalid socket address '{}': {}", addr, e)),
77        None => Ok(SocketAddr::from(([127, 0, 0, 1], default_port))),
78    }
79}
80
81/// Convert error to RPC error format
82pub fn to_rpc_error(
83    code: i32,
84    message: &str,
85    data: Option<serde_json::Value>,
86) -> crate::rpc::types::RpcError {
87    crate::rpc::types::RpcError { code, message: message.to_string(), data }
88}
89
90/// Helper to create internal error responses
91pub fn internal_error(message: &str) -> crate::rpc::types::RpcError {
92    to_rpc_error(crate::rpc::types::error_codes::INTERNAL_ERROR, message, None)
93}
94
95/// Helper to create method not found error
96pub fn method_not_found(method: &str) -> crate::rpc::types::RpcError {
97    to_rpc_error(
98        crate::rpc::types::error_codes::METHOD_NOT_FOUND,
99        &format!("Method '{method}' not found"),
100        None,
101    )
102}
103
104/// Helper to create invalid params error
105pub fn invalid_params(message: &str) -> crate::rpc::types::RpcError {
106    to_rpc_error(crate::rpc::types::error_codes::INVALID_PARAMS, message, None)
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_port_availability() {
115        // Test with a port that should be available
116        let port = find_available_port(50000).expect("Should find an available port");
117        assert!(port >= 50000);
118        assert!(is_port_available(port));
119    }
120
121    #[test]
122    fn test_socket_addr_parsing() {
123        let addr = parse_socket_addr(Some("127.0.0.1:8080"), 3000).unwrap();
124        assert_eq!(addr.port(), 8080);
125
126        let addr = parse_socket_addr(None, 3000).unwrap();
127        assert_eq!(addr.port(), 3000);
128        assert_eq!(addr.ip().to_string(), "127.0.0.1");
129    }
130
131    #[test]
132    fn test_error_helpers() {
133        let err = internal_error("test message");
134        assert_eq!(err.code, -32603);
135        assert_eq!(err.message, "test message");
136
137        let err = method_not_found("test_method");
138        assert_eq!(err.code, -32601);
139        assert!(err.message.contains("test_method"));
140    }
141}