hdc_rs/
forward.rs

1//! Port forwarding functionality
2
3/// Forward node type
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum ForwardNode {
6    /// TCP port: tcp:port
7    Tcp(u16),
8    /// Local filesystem Unix domain socket: localfilesystem:name
9    LocalFilesystem(String),
10    /// Local reserved Unix domain socket: localreserved:name
11    LocalReserved(String),
12    /// Local abstract Unix domain socket: localabstract:name
13    LocalAbstract(String),
14    /// Device: dev:name
15    Dev(String),
16    /// JDWP process (remote only): jdwp:pid
17    Jdwp(u32),
18    /// Ark debugger (remote only): ark:pid@tid@Debugger
19    Ark {
20        pid: u32,
21        tid: u32,
22        debugger: String,
23    },
24}
25
26impl ForwardNode {
27    /// Parse a forward node from string format
28    ///
29    /// Format examples:
30    /// - `tcp:8080`
31    /// - `localfilesystem:/tmp/socket`
32    /// - `localreserved:name`
33    /// - `localabstract:name`
34    /// - `dev:device_name`
35    /// - `jdwp:1234`
36    /// - `ark:1234@5678@Debugger`
37    pub fn parse(s: &str) -> crate::error::Result<Self> {
38        if let Some(port_str) = s.strip_prefix("tcp:") {
39            let port = port_str.parse::<u16>().map_err(|_| {
40                crate::error::HdcError::Protocol(format!("Invalid TCP port: {}", port_str))
41            })?;
42            Ok(Self::Tcp(port))
43        } else if let Some(name) = s.strip_prefix("localfilesystem:") {
44            Ok(Self::LocalFilesystem(name.to_string()))
45        } else if let Some(name) = s.strip_prefix("localreserved:") {
46            Ok(Self::LocalReserved(name.to_string()))
47        } else if let Some(name) = s.strip_prefix("localabstract:") {
48            Ok(Self::LocalAbstract(name.to_string()))
49        } else if let Some(name) = s.strip_prefix("dev:") {
50            Ok(Self::Dev(name.to_string()))
51        } else if let Some(pid_str) = s.strip_prefix("jdwp:") {
52            let pid = pid_str.parse::<u32>().map_err(|_| {
53                crate::error::HdcError::Protocol(format!("Invalid JDWP pid: {}", pid_str))
54            })?;
55            Ok(Self::Jdwp(pid))
56        } else if let Some(ark_str) = s.strip_prefix("ark:") {
57            let parts: Vec<&str> = ark_str.split('@').collect();
58            if parts.len() != 3 {
59                return Err(crate::error::HdcError::Protocol(format!(
60                    "Invalid ark format: expected pid@tid@debugger, got {}",
61                    ark_str
62                )));
63            }
64            let pid = parts[0].parse::<u32>().map_err(|_| {
65                crate::error::HdcError::Protocol(format!("Invalid pid in ark: {}", parts[0]))
66            })?;
67            let tid = parts[1].parse::<u32>().map_err(|_| {
68                crate::error::HdcError::Protocol(format!("Invalid tid in ark: {}", parts[1]))
69            })?;
70            Ok(Self::Ark {
71                pid,
72                tid,
73                debugger: parts[2].to_string(),
74            })
75        } else {
76            Err(crate::error::HdcError::Protocol(format!(
77                "Invalid forward node format: {}",
78                s
79            )))
80        }
81    }
82
83    /// Convert to protocol string representation
84    pub fn as_protocol_string(&self) -> String {
85        match self {
86            Self::Tcp(port) => format!("tcp:{}", port),
87            Self::LocalFilesystem(name) => format!("localfilesystem:{}", name),
88            Self::LocalReserved(name) => format!("localreserved:{}", name),
89            Self::LocalAbstract(name) => format!("localabstract:{}", name),
90            Self::Dev(name) => format!("dev:{}", name),
91            Self::Jdwp(pid) => format!("jdwp:{}", pid),
92            Self::Ark { pid, tid, debugger } => format!("ark:{}@{}@{}", pid, tid, debugger),
93        }
94    }
95}
96
97/// Forward task information
98#[derive(Debug, Clone)]
99pub struct ForwardTask {
100    pub local_node: ForwardNode,
101    pub remote_node: ForwardNode,
102    pub is_forward: bool, // true for fport, false for rport
103}
104
105impl ForwardTask {
106    /// Create a forward (fport) task
107    pub fn forward(local: ForwardNode, remote: ForwardNode) -> Self {
108        Self {
109            local_node: local,
110            remote_node: remote,
111            is_forward: true,
112        }
113    }
114
115    /// Create a reverse (rport) task
116    pub fn reverse(remote: ForwardNode, local: ForwardNode) -> Self {
117        Self {
118            local_node: local,
119            remote_node: remote,
120            is_forward: false,
121        }
122    }
123
124    /// Convert to command string format
125    pub fn to_command_string(&self) -> String {
126        if self.is_forward {
127            format!(
128                "fport {} {}",
129                self.local_node.as_protocol_string(),
130                self.remote_node.as_protocol_string()
131            )
132        } else {
133            format!(
134                "rport {} {}",
135                self.remote_node.as_protocol_string(),
136                self.local_node.as_protocol_string()
137            )
138        }
139    }
140
141    /// Get task string (for removal)
142    pub fn task_string(&self) -> String {
143        format!(
144            "{} {}",
145            self.local_node.as_protocol_string(),
146            self.remote_node.as_protocol_string()
147        )
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_parse_tcp() {
157        let node = ForwardNode::parse("tcp:8080").unwrap();
158        assert_eq!(node, ForwardNode::Tcp(8080));
159        assert_eq!(node.as_protocol_string(), "tcp:8080");
160    }
161
162    #[test]
163    fn test_parse_jdwp() {
164        let node = ForwardNode::parse("jdwp:1234").unwrap();
165        assert_eq!(node, ForwardNode::Jdwp(1234));
166        assert_eq!(node.as_protocol_string(), "jdwp:1234");
167    }
168
169    #[test]
170    fn test_parse_ark() {
171        let node = ForwardNode::parse("ark:100@200@Debugger").unwrap();
172        let expected = ForwardNode::Ark {
173            pid: 100,
174            tid: 200,
175            debugger: "Debugger".to_string(),
176        };
177        assert_eq!(node, expected);
178        assert_eq!(node.as_protocol_string(), "ark:100@200@Debugger");
179    }
180
181    #[test]
182    fn test_forward_task() {
183        let task = ForwardTask::forward(ForwardNode::Tcp(8080), ForwardNode::Tcp(8081));
184        assert_eq!(task.to_command_string(), "fport tcp:8080 tcp:8081");
185        assert_eq!(task.task_string(), "tcp:8080 tcp:8081");
186    }
187}