Skip to main content

mousehop_cli/
lib.rs

1use clap::{Args, Parser, Subcommand};
2use futures::StreamExt;
3
4use std::{net::IpAddr, time::Duration};
5use thiserror::Error;
6
7use mousehop_ipc::{
8    ClientHandle, ConnectionError, FrontendEvent, FrontendRequest, IpcError, Position,
9    connect_async,
10};
11
12#[derive(Debug, Error)]
13pub enum CliError {
14    /// is the service running?
15    #[error("could not connect: `{0}` - is the service running?")]
16    ServiceNotRunning(#[from] ConnectionError),
17    #[error("error communicating with service: {0}")]
18    Ipc(#[from] IpcError),
19}
20
21#[derive(Parser, Clone, Debug, PartialEq, Eq)]
22#[command(name = "mousehop-cli", about = "Mousehop CLI interface")]
23pub struct CliArgs {
24    #[command(subcommand)]
25    command: CliSubcommand,
26}
27
28#[derive(Args, Clone, Debug, PartialEq, Eq)]
29struct Client {
30    #[arg(long)]
31    hostname: Option<String>,
32    #[arg(long)]
33    port: Option<u16>,
34    #[arg(long)]
35    ips: Option<Vec<IpAddr>>,
36    #[arg(long)]
37    enter_hook: Option<String>,
38}
39
40#[derive(Clone, Subcommand, Debug, PartialEq, Eq)]
41enum CliSubcommand {
42    /// add a new client
43    AddClient(Client),
44    /// remove an existing client
45    RemoveClient { id: ClientHandle },
46    /// activate a client
47    Activate { id: ClientHandle },
48    /// deactivate a client
49    Deactivate { id: ClientHandle },
50    /// list configured clients
51    List,
52    /// change hostname
53    SetHost {
54        id: ClientHandle,
55        host: Option<String>,
56    },
57    /// change port
58    SetPort { id: ClientHandle, port: u16 },
59    /// set position
60    SetPosition { id: ClientHandle, pos: Position },
61    /// set ips
62    SetIps { id: ClientHandle, ips: Vec<IpAddr> },
63    /// re-enable capture
64    EnableCapture,
65    /// re-enable emulation
66    EnableEmulation,
67    /// authorize a public key
68    AuthorizeKey {
69        description: String,
70        sha256_fingerprint: String,
71    },
72    /// deauthorize a public key
73    RemoveAuthorizedKey { sha256_fingerprint: String },
74    /// save configuration to file
75    SaveConfig,
76}
77
78pub async fn run(args: CliArgs) -> Result<(), CliError> {
79    execute(args.command).await?;
80    Ok(())
81}
82
83async fn execute(cmd: CliSubcommand) -> Result<(), CliError> {
84    let (mut rx, mut tx) = connect_async(Some(Duration::from_millis(500))).await?;
85    match cmd {
86        CliSubcommand::AddClient(Client {
87            hostname,
88            port,
89            ips,
90            enter_hook,
91        }) => {
92            tx.request(FrontendRequest::Create).await?;
93            while let Some(e) = rx.next().await {
94                if let FrontendEvent::Created(handle, _, _) = e? {
95                    if let Some(hostname) = hostname {
96                        tx.request(FrontendRequest::UpdateHostname(handle, Some(hostname)))
97                            .await?;
98                    }
99                    if let Some(port) = port {
100                        tx.request(FrontendRequest::UpdatePort(handle, port))
101                            .await?;
102                    }
103                    if let Some(ips) = ips {
104                        tx.request(FrontendRequest::UpdateFixIps(handle, ips))
105                            .await?;
106                    }
107                    if let Some(enter_hook) = enter_hook {
108                        tx.request(FrontendRequest::UpdateEnterHook(handle, Some(enter_hook)))
109                            .await?;
110                    }
111                    break;
112                }
113            }
114        }
115        CliSubcommand::RemoveClient { id } => tx.request(FrontendRequest::Delete(id)).await?,
116        CliSubcommand::Activate { id } => tx.request(FrontendRequest::Activate(id, true)).await?,
117        CliSubcommand::Deactivate { id } => {
118            tx.request(FrontendRequest::Activate(id, false)).await?
119        }
120        CliSubcommand::List => {
121            tx.request(FrontendRequest::Enumerate()).await?;
122            while let Some(e) = rx.next().await {
123                if let FrontendEvent::Enumerate(clients) = e? {
124                    for (handle, config, state) in clients {
125                        let host = config.hostname.unwrap_or("unknown".to_owned());
126                        let port = config.port;
127                        let pos = config.pos;
128                        let active = state.active;
129                        let ips = state.ips;
130                        println!(
131                            "id {handle}: {host}:{port} ({pos}) active: {active}, ips: {ips:?}"
132                        );
133                    }
134                    break;
135                }
136            }
137        }
138        CliSubcommand::SetHost { id, host } => {
139            tx.request(FrontendRequest::UpdateHostname(id, host))
140                .await?
141        }
142        CliSubcommand::SetPort { id, port } => {
143            tx.request(FrontendRequest::UpdatePort(id, port)).await?
144        }
145        CliSubcommand::SetPosition { id, pos } => {
146            tx.request(FrontendRequest::UpdatePosition(id, pos)).await?
147        }
148        CliSubcommand::SetIps { id, ips } => {
149            tx.request(FrontendRequest::UpdateFixIps(id, ips)).await?
150        }
151        CliSubcommand::EnableCapture => tx.request(FrontendRequest::EnableCapture).await?,
152        CliSubcommand::EnableEmulation => tx.request(FrontendRequest::EnableEmulation).await?,
153        CliSubcommand::AuthorizeKey {
154            description,
155            sha256_fingerprint,
156        } => {
157            tx.request(FrontendRequest::AuthorizeKey(
158                description,
159                sha256_fingerprint,
160            ))
161            .await?
162        }
163        CliSubcommand::RemoveAuthorizedKey { sha256_fingerprint } => {
164            tx.request(FrontendRequest::RemoveAuthorizedKey(sha256_fingerprint))
165                .await?
166        }
167        CliSubcommand::SaveConfig => tx.request(FrontendRequest::SaveConfiguration).await?,
168    }
169    Ok(())
170}