1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
use anyhow::{Context, Result};
use clap::{Args, Subcommand};
use futures::StreamExt;
use mullvad_management_interface::{MullvadProxyClient, client::DaemonEvent};
use mullvad_types::{device::DeviceState, states::TunnelState};
use serde::Serialize;
use std::fmt::Debug;
use crate::format;
#[derive(Subcommand, Debug, PartialEq)]
pub enum Status {
/// Listen for tunnel state changes
Listen,
}
#[derive(Args, Debug)]
pub struct StatusArgs {
/// Enable verbose output
#[arg(long, short = 'v')]
verbose: bool,
/// Enable debug output
#[arg(long, short = 'd', conflicts_with_all = ["verbose", "json"])]
debug: bool,
/// Format output as JSON
#[arg(long, short = 'j', conflicts_with_all = ["verbose", "debug"])]
json: bool,
}
impl Status {
pub async fn listen(
mut rpc: MullvadProxyClient,
args: StatusArgs,
mut previous_tunnel_state: TunnelState,
) -> Result<()> {
let mut event_stream = rpc.events_listen().await?;
while let Some(event) = event_stream.next().await {
match event? {
DaemonEvent::TunnelState(new_state) => {
if args.debug {
println!("New tunnel state: {new_state:#?}");
} else if args.json {
let json = serde_json::to_string(&new_state)
.context("Failed to format output as JSON")?;
println!("{json}");
} else {
format::print_state(&new_state, Some(&previous_tunnel_state), args.verbose);
previous_tunnel_state = new_state;
}
}
DaemonEvent::Settings(settings) => {
print_debug_or_json(&args, "New settings", &settings)?;
}
DaemonEvent::RelayList(relay_list) => {
print_debug_or_json(&args, "New relay list", &relay_list)?;
}
DaemonEvent::AppVersionInfo(app_version_info) => {
print_debug_or_json(&args, "New app version info", &app_version_info)?;
}
DaemonEvent::Device(device) => {
print_debug_or_json(&args, "Device event", &device)?;
}
DaemonEvent::RemoveDevice(device) => {
print_debug_or_json(&args, "Remove device event", &device)?;
}
DaemonEvent::NewAccessMethod(access_method) => {
print_debug_or_json(&args, "New access method", &access_method)?;
}
}
}
Ok(())
}
}
pub async fn handle(cmd: Option<Status>, args: StatusArgs) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let state = rpc.get_tunnel_state().await?;
let device = rpc.get_device().await?;
print_account_logged_out(&state, &device);
if args.debug {
println!("Tunnel state: {state:#?}");
} else if args.json {
let json = serde_json::to_string(&state).context("Failed to format output as JSON")?;
println!("{json}");
} else {
format::print_state(&state, None, args.verbose);
}
if cmd == Some(Status::Listen) {
Status::listen(rpc, args, state).await?;
}
Ok(())
}
fn print_account_logged_out(state: &TunnelState, device: &DeviceState) {
match state {
TunnelState::Connecting { .. } | TunnelState::Connected { .. } | TunnelState::Error(_) => {
match device {
DeviceState::LoggedOut => {
println!("Warning: You are not logged in to an account.")
}
DeviceState::Revoked => println!("Warning: This device has been revoked."),
DeviceState::LoggedIn(_) => (),
}
}
TunnelState::Disconnected { .. } | TunnelState::Disconnecting(_) => (),
}
}
fn print_debug_or_json<T: Debug + Serialize>(
args: &StatusArgs,
debug_message: &str,
t: &T,
) -> Result<()> {
if args.debug {
println!("{debug_message}: {t:#?}");
} else if args.json {
let json = serde_json::to_string(&t).context("Failed to format output as JSON")?;
println!("{json}");
}
Ok(())
}
|