aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGustav Sörnäs <gustav@sornas.net>2021-03-31 21:51:47 +0200
committerGitHub <noreply@github.com>2021-03-31 21:51:47 +0200
commit3f6281020b72ba949147a282c18c60a2842ad3dc (patch)
tree0ba20ba532d325bf072969013fe8cf5bde84f6ba
parent795e46c98616801c678bd0a403b08cb0fcd5ee43 (diff)
parent46a3938b6d9d81649e38e6e793599a52991d803d (diff)
downloadmum-3f6281020b72ba949147a282c18c60a2842ad3dc.tar.gz
Merge pull request #42 from mum-rs/handle-panics
-rw-r--r--CHANGELOG1
-rw-r--r--Cargo.lock121
-rw-r--r--mumctl/src/main.rs26
-rw-r--r--mumd/src/audio.rs26
-rw-r--r--mumd/src/client.rs27
-rw-r--r--mumd/src/error.rs127
-rw-r--r--mumd/src/main.rs35
-rw-r--r--mumd/src/network.rs19
-rw-r--r--mumd/src/network/tcp.rs87
-rw-r--r--mumd/src/network/udp.rs16
-rw-r--r--mumd/src/notifications.rs (renamed from mumd/src/notify.rs)10
-rw-r--r--mumd/src/state.rs26
-rw-r--r--mumlib/Cargo.toml1
-rw-r--r--mumlib/src/config.rs87
-rw-r--r--mumlib/src/error.rs39
15 files changed, 479 insertions, 169 deletions
diff --git a/CHANGELOG b/CHANGELOG
index b13aca1..b567521 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -36,6 +36,7 @@ Fixed
* Client no longer sends empty audio packets.
* Informative error message instead of panic when a running mumd-process can't
be found.
+ * Lots of other minor informative error messages instead of panics.
Other
~~~~~
diff --git a/Cargo.lock b/Cargo.lock
index 3bb9e3e..93f7118 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -32,6 +32,18 @@ dependencies = [
]
[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -49,6 +61,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
name = "bincode"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -84,6 +102,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
+name = "blake2b_simd"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "constant_time_eq",
+]
+
+[[package]]
name = "bumpalo"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -180,6 +209,12 @@ dependencies = [
]
[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -246,6 +281,17 @@ dependencies = [
]
[[package]]
+name = "crossbeam-utils"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "lazy_static",
+]
+
+[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -383,6 +429,26 @@ dependencies = [
]
[[package]]
+name = "dirs"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
name = "fern"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -497,13 +563,24 @@ dependencies = [
[[package]]
name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
@@ -811,6 +888,7 @@ name = "mumlib"
version = "0.3.0"
dependencies = [
"colored",
+ "dirs",
"fern",
"log",
"serde",
@@ -1065,7 +1143,7 @@ dependencies = [
"cfg-if",
"instant",
"libc",
- "redox_syscall",
+ "redox_syscall 0.2.5",
"smallvec",
"winapi",
]
@@ -1192,7 +1270,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
- "getrandom",
+ "getrandom 0.2.2",
]
[[package]]
@@ -1206,6 +1284,12 @@ dependencies = [
[[package]]
name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
@@ -1214,6 +1298,17 @@ dependencies = [
]
[[package]]
+name = "redox_users"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
+dependencies = [
+ "getrandom 0.1.16",
+ "redox_syscall 0.1.57",
+ "rust-argon2",
+]
+
+[[package]]
name = "regex"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1238,6 +1333,18 @@ dependencies = [
]
[[package]]
+name = "rust-argon2"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
+dependencies = [
+ "base64",
+ "blake2b_simd",
+ "constant_time_eq",
+ "crossbeam-utils",
+]
+
+[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1396,7 +1503,7 @@ dependencies = [
"cfg-if",
"libc",
"rand",
- "redox_syscall",
+ "redox_syscall 0.2.5",
"remove_dir_all",
"winapi",
]
@@ -1548,6 +1655,12 @@ dependencies = [
[[package]]
name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
diff --git a/mumctl/src/main.rs b/mumctl/src/main.rs
index 298c04f..a187a3a 100644
--- a/mumctl/src/main.rs
+++ b/mumctl/src/main.rs
@@ -44,7 +44,13 @@ static LOGGER: SimpleLogger = SimpleLogger;
fn main() {
log::set_logger(&LOGGER)
.map(|()| log::set_max_level(LevelFilter::Info)).unwrap();
- let mut config = config::read_default_cfg();
+ let mut config = match config::read_default_cfg() {
+ Ok(c) => c,
+ Err(e) => {
+ error!("Couldn't read config: {}", e);
+ return;
+ }
+ };
let mut app = App::new("mumctl")
.setting(AppSettings::ArgRequiredElseHelp)
@@ -186,15 +192,15 @@ fn main() {
if !config::cfg_exists() {
println!(
"Config file not found. Create one in {}? [Y/n]",
- config::get_creatable_cfg_path()
+ config::default_cfg_path().display(),
);
let stdin = std::io::stdin();
let response = stdin.lock().lines().next();
if let Some(Ok(true)) = response.map(|e| e.map(|e| &e == "Y")) {
- config.write_default_cfg(true).unwrap();
+ error_if_err!(config.write_default_cfg(true));
}
} else {
- config.write_default_cfg(false).unwrap();
+ error_if_err!(config.write_default_cfg(false));
}
}
@@ -202,11 +208,7 @@ fn process_matches(matches: ArgMatches, config: &mut Config, app: &mut App) -> R
if let Some(matches) = matches.subcommand_matches("connect") {
match_server_connect(matches, &config)?;
} else if let Some(_) = matches.subcommand_matches("disconnect") {
- match send_command(Command::ServerDisconnect) {
- Ok(v) => error_if_err!(v),
- Err(e) => error!("{}", e),
- }
- // error_if_err!(send_command(Command::ServerDisconnect));
+ error_if_err!(send_command(Command::ServerDisconnect)?);
} else if let Some(matches) = matches.subcommand_matches("server") {
if let Some(matches) = matches.subcommand_matches("config") {
match_server_config(matches, config);
@@ -276,13 +278,13 @@ fn process_matches(matches: ArgMatches, config: &mut Config, app: &mut App) -> R
match name {
"audio.input_volume" => {
if let Ok(volume) = value.parse() {
- send_command(Command::InputVolumeSet(volume))?.unwrap();
+ error_if_err!(send_command(Command::InputVolumeSet(volume))?);
config.audio.input_volume = Some(volume);
}
}
"audio.output_volume" => {
if let Ok(volume) = value.parse() {
- send_command(Command::OutputVolumeSet(volume))?.unwrap();
+ error_if_err!(send_command(Command::OutputVolumeSet(volume))?);
config.audio.output_volume = Some(volume);
}
}
@@ -291,7 +293,7 @@ fn process_matches(matches: ArgMatches, config: &mut Config, app: &mut App) -> R
}
}
} else if matches.subcommand_matches("config-reload").is_some() {
- send_command(Command::ConfigReload)?.unwrap();
+ error_if_err!(send_command(Command::ConfigReload)?);
} else if let Some(matches) = matches.subcommand_matches("completions") {
app.gen_completions_to(
"mumctl",
diff --git a/mumd/src/audio.rs b/mumd/src/audio.rs
index 6b46a7a..fdbeaee 100644
--- a/mumd/src/audio.rs
+++ b/mumd/src/audio.rs
@@ -4,6 +4,7 @@ mod noise_gate;
use crate::audio::output::SaturatingAdd;
use crate::audio::noise_gate::{from_interleaved_samples_stream, OpusEncoder, StreamingNoiseGate, StreamingSignalExt};
+use crate::error::{AudioError, AudioStream};
use crate::network::VoiceStreamType;
use crate::state::StatePhase;
@@ -85,16 +86,16 @@ pub struct Audio {
}
impl Audio {
- pub fn new(input_volume: f32, output_volume: f32, phase_watcher: watch::Receiver<StatePhase>) -> Self {
+ pub fn new(input_volume: f32, output_volume: f32, phase_watcher: watch::Receiver<StatePhase>) -> Result<Self, AudioError> {
let sample_rate = SampleRate(SAMPLE_RATE);
let host = cpal::default_host();
let output_device = host
.default_output_device()
- .expect("default output device not found");
+ .ok_or(AudioError::NoDevice(AudioStream::Output))?;
let output_supported_config = output_device
.supported_output_configs()
- .expect("error querying output configs")
+ .map_err(|e| AudioError::NoConfigs(AudioStream::Output, e))?
.find_map(|c| {
if c.min_sample_rate() <= sample_rate && c.max_sample_rate() >= sample_rate {
Some(c)
@@ -102,17 +103,17 @@ impl Audio {
None
}
})
- .unwrap()
+ .ok_or(AudioError::NoSupportedConfig(AudioStream::Output))?
.with_sample_rate(sample_rate);
let output_supported_sample_format = output_supported_config.sample_format();
let output_config: StreamConfig = output_supported_config.into();
let input_device = host
.default_input_device()
- .expect("default input device not found");
+ .ok_or(AudioError::NoDevice(AudioStream::Input))?;
let input_supported_config = input_device
.supported_input_configs()
- .expect("error querying output configs")
+ .map_err(|e| AudioError::NoConfigs(AudioStream::Input, e))?
.find_map(|c| {
if c.min_sample_rate() <= sample_rate && c.max_sample_rate() >= sample_rate {
Some(c)
@@ -120,7 +121,7 @@ impl Audio {
None
}
})
- .unwrap()
+ .ok_or(AudioError::NoSupportedConfig(AudioStream::Input))?
.with_sample_rate(sample_rate);
let input_supported_sample_format = input_supported_config.sample_format();
let input_config: StreamConfig = input_supported_config.into();
@@ -164,7 +165,7 @@ impl Audio {
err_fn,
),
}
- .unwrap();
+ .map_err(|e| AudioError::InvalidStream(AudioStream::Output, e))?;
let (sample_sender, sample_receiver) = futures_channel::mpsc::channel(1_000_000);
@@ -199,7 +200,7 @@ impl Audio {
err_fn,
),
}
- .unwrap();
+ .map_err(|e| AudioError::InvalidStream(AudioStream::Input, e))?;
let opus_stream = OpusEncoder::new(
4,
@@ -217,7 +218,7 @@ impl Audio {
position_info: None,
});
- output_stream.play().unwrap();
+ output_stream.play().map_err(|e| AudioError::OutputPlayError(e))?;
let mut res = Self {
output_config,
@@ -232,7 +233,7 @@ impl Audio {
play_sounds,
};
res.load_sound_effects(&[]);
- res
+ Ok(res)
}
pub fn load_sound_effects(&mut self, sound_effects: &[SoundEffect]) {
@@ -268,7 +269,7 @@ impl Audio {
let iter: Box<dyn Iterator<Item = f32>> = match spec.channels {
1 => Box::new(samples.into_iter().flat_map(|e| vec![e, e])),
2 => Box::new(samples.into_iter()),
- _ => unimplemented!() // TODO handle gracefully (this might not even happen)
+ _ => unimplemented!("Only mono and stereo sound is supported. See #80.")
};
let mut signal = signal::from_interleaved_samples_iter::<_, [f32; 2]>(iter);
let interp = Linear::new(Signal::next(&mut signal), Signal::next(&mut signal));
@@ -401,4 +402,3 @@ fn get_sfx(file: &str) -> Cow<'static, [u8]> {
fn get_default_sfx() -> Cow<'static, [u8]> {
Cow::from(include_bytes!("fallback_sfx.wav").as_ref())
}
-
diff --git a/mumd/src/client.rs b/mumd/src/client.rs
index cdae7eb..c1a0152 100644
--- a/mumd/src/client.rs
+++ b/mumd/src/client.rs
@@ -1,18 +1,21 @@
use crate::command;
+use crate::error::ClientError;
use crate::network::{tcp, udp, ConnectionInfo};
use crate::state::State;
+use futures_util::{select, FutureExt};
use mumble_protocol::{Serverbound, control::ControlPacket, crypt::ClientCryptState};
use mumlib::command::{Command, CommandResponse};
use std::sync::Arc;
-use tokio::{join, sync::{Mutex, mpsc, oneshot, watch}};
+use tokio::sync::{Mutex, mpsc, oneshot, watch};
pub async fn handle(
+ state: State,
command_receiver: mpsc::UnboundedReceiver<(
Command,
oneshot::Sender<mumlib::error::Result<Option<CommandResponse>>>,
)>,
-) {
+) -> Result<(), ClientError> {
let (connection_info_sender, connection_info_receiver) =
watch::channel::<Option<ConnectionInfo>>(None);
let (crypt_state_sender, crypt_state_receiver) =
@@ -24,30 +27,30 @@ pub async fn handle(
let (response_sender, response_receiver) =
mpsc::unbounded_channel();
- let state = State::new();
let state = Arc::new(Mutex::new(state));
- join!(
- tcp::handle(
+
+ select! {
+ r = tcp::handle(
Arc::clone(&state),
connection_info_receiver.clone(),
crypt_state_sender,
packet_sender.clone(),
packet_receiver,
response_receiver,
- ),
- udp::handle(
+ ).fuse() => r.map_err(|e| ClientError::TcpError(e)),
+ _ = udp::handle(
Arc::clone(&state),
connection_info_receiver.clone(),
crypt_state_receiver,
- ),
- command::handle(
+ ).fuse() => Ok(()),
+ _ = command::handle(
state,
command_receiver,
response_sender,
ping_request_sender,
packet_sender,
connection_info_sender,
- ),
- udp::handle_pings(ping_request_receiver),
- );
+ ).fuse() => Ok(()),
+ _ = udp::handle_pings(ping_request_receiver).fuse() => Ok(()),
+ }
}
diff --git a/mumd/src/error.rs b/mumd/src/error.rs
new file mode 100644
index 0000000..f7818a1
--- /dev/null
+++ b/mumd/src/error.rs
@@ -0,0 +1,127 @@
+use mumble_protocol::{Serverbound, control::ControlPacket};
+use mumlib::error::ConfigError;
+use std::fmt;
+use tokio::sync::mpsc;
+
+pub type ServerSendError = mpsc::error::SendError<ControlPacket<Serverbound>>;
+
+pub enum TcpError {
+ NoConnectionInfoReceived,
+ TlsConnectorBuilderError(native_tls::Error),
+ TlsConnectError(native_tls::Error),
+ SendError(ServerSendError),
+
+ IOError(std::io::Error),
+}
+
+impl fmt::Display for TcpError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ TcpError::NoConnectionInfoReceived
+ => write!(f, "No connection info received"),
+ TcpError::TlsConnectorBuilderError(e)
+ => write!(f, "Error building TLS connector: {}", e),
+ TcpError::TlsConnectError(e)
+ => write!(f, "TLS error when connecting: {}", e),
+ TcpError::SendError(e) => write!(f, "Couldn't send packet: {}", e),
+ TcpError::IOError(e) => write!(f, "IO error: {}", e),
+ }
+ }
+}
+
+impl From<std::io::Error> for TcpError {
+ fn from(e: std::io::Error) -> Self {
+ TcpError::IOError(e)
+ }
+}
+
+impl From<ServerSendError> for TcpError {
+ fn from(e: ServerSendError) -> Self {
+ TcpError::SendError(e)
+ }
+}
+
+pub enum UdpError {
+ NoConnectionInfoReceived,
+ DisconnectBeforeCryptSetup,
+
+ IOError(std::io::Error),
+}
+
+impl From<std::io::Error> for UdpError {
+ fn from(e: std::io::Error) -> Self {
+ UdpError::IOError(e)
+ }
+}
+
+pub enum ClientError {
+ TcpError(TcpError),
+}
+
+impl fmt::Display for ClientError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ ClientError::TcpError(e) => write!(f, "TCP error: {}", e),
+ }
+ }
+}
+
+pub enum AudioStream {
+ Input,
+ Output,
+}
+
+impl fmt::Display for AudioStream {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ AudioStream::Input => write!(f, "input"),
+ AudioStream::Output => write!(f, "output"),
+ }
+ }
+}
+
+pub enum AudioError {
+ NoDevice(AudioStream),
+ NoConfigs(AudioStream, cpal::SupportedStreamConfigsError),
+ NoSupportedConfig(AudioStream),
+ InvalidStream(AudioStream, cpal::BuildStreamError),
+ OutputPlayError(cpal::PlayStreamError),
+}
+
+impl fmt::Display for AudioError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ AudioError::NoDevice(s) => write!(f, "No {} device", s),
+ AudioError::NoConfigs(s, e) => write!(f, "No {} configs: {}", s, e),
+ AudioError::NoSupportedConfig(s) => write!(f, "No supported {} config found", s),
+ AudioError::InvalidStream(s, e) => write!(f, "Invalid {} stream: {}", s, e),
+ AudioError::OutputPlayError(e) => write!(f, "Playback error: {}", e),
+ }
+ }
+}
+
+pub enum StateError {
+ AudioError(AudioError),
+ ConfigError(ConfigError),
+}
+
+impl From<AudioError> for StateError {
+ fn from(e: AudioError) -> Self {
+ StateError::AudioError(e)
+ }
+}
+
+impl From<ConfigError> for StateError {
+ fn from(e: ConfigError) -> Self {
+ StateError::ConfigError(e)
+ }
+}
+
+impl fmt::Display for StateError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ StateError::AudioError(e) => write!(f, "Audio error: {}", e),
+ StateError::ConfigError(e) => write!(f, "Config error: {}", e),
+ }
+ }
+}
diff --git a/mumd/src/main.rs b/mumd/src/main.rs
index 276e2ce..d7bc2c0 100644
--- a/mumd/src/main.rs
+++ b/mumd/src/main.rs
@@ -1,15 +1,18 @@
mod audio;
mod client;
mod command;
+mod error;
mod network;
-mod notify;
+mod notifications;
mod state;
-use futures_util::{SinkExt, StreamExt};
+use crate::state::State;
+
+use futures_util::{select, FutureExt, SinkExt, StreamExt};
use log::*;
use mumlib::command::{Command, CommandResponse};
use mumlib::setup_logger;
-use tokio::{join, net::{UnixListener, UnixStream}, sync::{mpsc, oneshot}};
+use tokio::{net::{UnixListener, UnixStream}, sync::{mpsc, oneshot}};
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
use bytes::{BufMut, BytesMut};
@@ -21,7 +24,7 @@ async fn main() {
}
setup_logger(std::io::stderr(), true);
- notify::init();
+ notifications::init();
// check if another instance is live
let connection = UnixStream::connect(mumlib::SOCKET_PATH).await;
@@ -53,10 +56,26 @@ async fn main() {
let (command_sender, command_receiver) = mpsc::unbounded_channel();
- join!(
- client::handle(command_receiver),
- receive_commands(command_sender),
- );
+ let state = match State::new() {
+ Ok(s) => s,
+ Err(e) => {
+ error!("Error instantiating mumd: {}", e);
+ return;
+ }
+ };
+
+ let run = select! {
+ r = client::handle(state, command_receiver).fuse() => r,
+ _ = receive_commands(command_sender).fuse() => Ok(()),
+ };
+
+ match run {
+ Err(e) => {
+ error!("mumd: {}", e);
+ std::process::exit(1);
+ }
+ _ => {}
+ }
}
async fn receive_commands(
diff --git a/mumd/src/network.rs b/mumd/src/network.rs
index 38a97ce..4eca90d 100644
--- a/mumd/src/network.rs
+++ b/mumd/src/network.rs
@@ -4,7 +4,7 @@ pub mod udp;
use futures_util::FutureExt;
use log::*;
use std::{future::Future, net::SocketAddr};
-use tokio::{join, select, sync::{oneshot, watch}};
+use tokio::{select, sync::{oneshot, watch}};
use crate::state::StatePhase;
@@ -31,12 +31,12 @@ pub enum VoiceStreamType {
UDP,
}
-async fn run_until<F>(
+async fn run_until<F, R>(
phase_checker: impl Fn(StatePhase) -> bool,
fut: F,
mut phase_watcher: watch::Receiver<StatePhase>,
-) where
- F: Future<Output = ()>,
+) -> Option<R>
+ where F: Future<Output = R>,
{
let (tx, rx) = oneshot::channel();
let phase_transition_block = async {
@@ -55,10 +55,13 @@ async fn run_until<F>(
let rx = rx.fuse();
let fut = fut.fuse();
select! {
- _ = fut => (),
- _ = rx => (),
- };
+ r = fut => Some(r),
+ _ = rx => None,
+ }
};
- join!(main_block, phase_transition_block);
+ select! {
+ m = main_block => m,
+ _ = phase_transition_block => None,
+ }
}
diff --git a/mumd/src/network/tcp.rs b/mumd/src/network/tcp.rs
index 47b1c20..6402a89 100644
--- a/mumd/src/network/tcp.rs
+++ b/mumd/src/network/tcp.rs
@@ -1,8 +1,10 @@
+use crate::error::{ServerSendError, TcpError};
use crate::network::ConnectionInfo;
use crate::state::{State, StatePhase};
use log::*;
use futures_util::{FutureExt, SinkExt, StreamExt};
+use futures_util::select;
use futures_util::stream::{SplitSink, SplitStream, Stream};
use mumble_protocol::control::{msgs, ClientControlCodec, ControlCodec, ControlPacket};
use mumble_protocol::crypt::ClientCryptState;
@@ -19,7 +21,6 @@ use tokio_native_tls::{TlsConnector, TlsStream};
use tokio_util::codec::{Decoder, Framed};
use super::{run_until, VoiceStreamType};
-use futures_util::future::join5;
type TcpSender = SplitSink<
Framed<TlsStream<TcpStream>, ControlCodec<Serverbound, Clientbound>>,
@@ -84,7 +85,7 @@ pub async fn handle(
packet_sender: mpsc::UnboundedSender<ControlPacket<Serverbound>>,
mut packet_receiver: mpsc::UnboundedReceiver<ControlPacket<Serverbound>>,
mut tcp_event_register_receiver: mpsc::UnboundedReceiver<(TcpEvent, TcpEventCallback)>,
-) {
+) -> Result<(), TcpError> {
loop {
let connection_info = 'data: loop {
while connection_info_receiver.changed().await.is_ok() {
@@ -92,20 +93,20 @@ pub async fn handle(
break 'data data;
}
}
- return;
+ return Err(TcpError::NoConnectionInfoReceived);
};
let (mut sink, stream) = connect(
connection_info.socket_addr,
connection_info.hostname,
connection_info.accept_invalid_cert,
)
- .await;
+ .await?;
// Handshake (omitting `Version` message for brevity)
let state_lock = state.lock().await;
let username = state_lock.username().unwrap().to_string();
let password = state_lock.password().map(|x| x.to_string());
- authenticate(&mut sink, username, password).await;
+ authenticate(&mut sink, username, password).await?;
let phase_watcher = state_lock.phase_receiver();
let input_receiver = state_lock.audio().input_receiver();
drop(state_lock);
@@ -113,26 +114,30 @@ pub async fn handle(
info!("Logging in...");
+ let phase_watcher_inner = phase_watcher.clone();
+
run_until(
|phase| matches!(phase, StatePhase::Disconnected),
- join5(
- send_pings(packet_sender.clone(), 10),
- listen(
- Arc::clone(&state),
- stream,
- crypt_state_sender.clone(),
- event_queue.clone(),
- ),
- send_voice(
- packet_sender.clone(),
- Arc::clone(&input_receiver),
- phase_watcher.clone(),
- ),
- send_packets(sink, &mut packet_receiver),
- register_events(&mut tcp_event_register_receiver, event_queue.clone()),
- ).map(|_| ()),
+ async {
+ select! {
+ r = send_pings(packet_sender.clone(), 10).fuse() => r,
+ r = listen(
+ Arc::clone(&state),
+ stream,
+ crypt_state_sender.clone(),
+ event_queue.clone(),
+ ).fuse() => r,
+ r = send_voice(
+ packet_sender.clone(),
+ Arc::clone(&input_receiver),
+ phase_watcher_inner,
+ ).fuse() => r,
+ r = send_packets(sink, &mut packet_receiver).fuse() => r,
+ _ = register_events(&mut tcp_event_register_receiver, event_queue.clone()).fuse() => Ok(()),
+ }
+ },
phase_watcher,
- ).await;
+ ).await.unwrap_or(Ok(()))?;
event_queue.resolve(TcpEventData::Disconnected).await;
@@ -144,62 +149,62 @@ async fn connect(
server_addr: SocketAddr,
server_host: String,
accept_invalid_cert: bool,
-) -> (TcpSender, TcpReceiver) {
- let stream = TcpStream::connect(&server_addr)
- .await
- .expect("failed to connect to server:");
+) -> Result<(TcpSender, TcpReceiver), TcpError> {
+ let stream = TcpStream::connect(&server_addr).await?;
debug!("TCP connected");
let mut builder = native_tls::TlsConnector::builder();
builder.danger_accept_invalid_certs(accept_invalid_cert);
let connector: TlsConnector = builder
.build()
- .expect("failed to create TLS connector")
+ .map_err(|e| TcpError::TlsConnectorBuilderError(e))?
.into();
let tls_stream = connector
.connect(&server_host, stream)
.await
- .expect("failed to connect TLS: {}");
+ .map_err(|e| TcpError::TlsConnectError(e))?;
debug!("TLS connected");
// Wrap the TLS stream with Mumble's client-side control-channel codec
- ClientControlCodec::new().framed(tls_stream).split()
+ Ok(ClientControlCodec::new().framed(tls_stream).split())
}
async fn authenticate(
sink: &mut TcpSender,
username: String,
password: Option<String>
-) {
+) -> Result<(), TcpError> {
let mut msg = msgs::Authenticate::new();
msg.set_username(username);
if let Some(password) = password {
msg.set_password(password);
}
msg.set_opus(true);
- sink.send(msg.into()).await.unwrap();
+ sink.send(msg.into()).await?;
+ Ok(())
}
async fn send_pings(
packet_sender: mpsc::UnboundedSender<ControlPacket<Serverbound>>,
delay_seconds: u64,
-) {
+) -> Result<(), TcpError> {
let mut interval = time::interval(Duration::from_secs(delay_seconds));
loop {
interval.tick().await;
trace!("Sending TCP ping");
let msg = msgs::Ping::new();
- packet_sender.send(msg.into()).unwrap();
+ packet_sender.send(msg.into())?;
}
}
async fn send_packets(
mut sink: TcpSender,
packet_receiver: &mut mpsc::UnboundedReceiver<ControlPacket<Serverbound>>,
-) {
+) -> Result<(), TcpError> {
loop {
+ // Safe since we always have at least one sender alive.
let packet = packet_receiver.recv().await.unwrap();
- sink.send(packet).await.unwrap();
+ sink.send(packet).await?;
}
}
@@ -207,7 +212,7 @@ async fn send_voice(
packet_sender: mpsc::UnboundedSender<ControlPacket<Serverbound>>,
receiver: Arc<Mutex<Box<(dyn Stream<Item = VoicePacket<Serverbound>> + Unpin)>>>,
phase_watcher: watch::Receiver<StatePhase>,
-) {
+) -> Result<(), TcpError> {
loop {
let mut inner_phase_watcher = phase_watcher.clone();
loop {
@@ -226,13 +231,12 @@ async fn send_voice(
.await
.next()
.await
- .unwrap()
- .into())
- .unwrap();
+ .expect("No audio stream")
+ .into())?;
}
},
inner_phase_watcher.clone(),
- ).await;
+ ).await.unwrap_or(Ok::<(), ServerSendError>(()))?;
}
}
@@ -241,7 +245,7 @@ async fn listen(
mut stream: TcpReceiver,
crypt_state_sender: mpsc::Sender<ClientCryptState>,
event_queue: TcpEventQueue,
-) {
+) -> Result<(), TcpError> {
let mut crypt_state = None;
let mut crypt_state_sender = Some(crypt_state_sender);
@@ -367,6 +371,7 @@ async fn listen(
}
}
}
+ Ok(())
}
async fn register_events(
diff --git a/mumd/src/network/udp.rs b/mumd/src/network/udp.rs
index da92dcb..5996e43 100644
--- a/mumd/src/network/udp.rs
+++ b/mumd/src/network/udp.rs
@@ -1,3 +1,4 @@
+use crate::error::UdpError;
use crate::network::ConnectionInfo;
use crate::state::{State, StatePhase};
@@ -31,7 +32,7 @@ pub async fn handle(
state: Arc<Mutex<State>>,
mut connection_info_receiver: watch::Receiver<Option<ConnectionInfo>>,
mut crypt_state_receiver: mpsc::Receiver<ClientCryptState>,
-) {
+) -> Result<(), UdpError> {
let receiver = state.lock().await.audio().input_receiver();
loop {
@@ -41,9 +42,9 @@ pub async fn handle(
break 'data data;
}
}
- return;
+ return Err(UdpError::NoConnectionInfoReceived);
};
- let (sink, source) = connect(&mut crypt_state_receiver).await;
+ let (sink, source) = connect(&mut crypt_state_receiver).await?;
let sink = Arc::new(Mutex::new(sink));
let source = Arc::new(Mutex::new(source));
@@ -82,22 +83,21 @@ pub async fn handle(
async fn connect(
crypt_state: &mut mpsc::Receiver<ClientCryptState>,
-) -> (UdpSender, UdpReceiver) {
+) -> Result<(UdpSender, UdpReceiver), UdpError> {
// Bind UDP socket
let udp_socket = UdpSocket::bind((Ipv6Addr::from(0u128), 0u16))
- .await
- .expect("Failed to bind UDP socket");
+ .await?;
// Wait for initial CryptState
let crypt_state = match crypt_state.recv().await {
Some(crypt_state) => crypt_state,
// disconnected before we received the CryptSetup packet, oh well
- None => panic!("Disconnect before crypt packet received"), //TODO exit gracefully
+ None => return Err(UdpError::DisconnectBeforeCryptSetup),
};
debug!("UDP connected");
// Wrap the raw UDP packets in Mumble's crypto and voice codec (CryptState does both)
- UdpFramed::new(udp_socket, crypt_state).split()
+ Ok(UdpFramed::new(udp_socket, crypt_state).split())
}
async fn new_crypt_state(
diff --git a/mumd/src/notify.rs b/mumd/src/notifications.rs
index ee387cc..bccf4dd 100644
--- a/mumd/src/notify.rs
+++ b/mumd/src/notifications.rs
@@ -1,14 +1,18 @@
+use log::*;
+
pub fn init() {
#[cfg(feature = "notifications")]
- libnotify::init("mumd").unwrap();
+ if let Err(e) = libnotify::init("mumd") {
+ warn!("Unable to initialize notifications: {}", e);
+ }
}
#[cfg(feature = "notifications")]
pub fn send(msg: String) -> Option<bool> {
match libnotify::Notification::new("mumd", Some(msg.as_str()), None).show() {
Ok(_) => Some(true),
- Err(_) => {
- log::debug!("Unable to send notification");
+ Err(e) => {
+ warn!("Unable to send notification: {}", e);
Some(false)
}
}
diff --git a/mumd/src/state.rs b/mumd/src/state.rs
index 20fe660..b52b330 100644
--- a/mumd/src/state.rs
+++ b/mumd/src/state.rs
@@ -3,9 +3,10 @@ pub mod server;
pub mod user;
use crate::audio::{Audio, NotificationEvents};
+use crate::error::StateError;
use crate::network::{ConnectionInfo, VoiceStreamType};
use crate::network::tcp::{TcpEvent, TcpEventData};
-use crate::notify;
+use crate::notifications;
use crate::state::server::Server;
use log::*;
@@ -62,14 +63,14 @@ pub struct State {
}
impl State {
- pub fn new() -> Self {
- let config = mumlib::config::read_default_cfg();
+ pub fn new() -> Result<Self, StateError> {
+ let config = mumlib::config::read_default_cfg()?;
let phase_watcher = watch::channel(StatePhase::Disconnected);
let audio = Audio::new(
config.audio.input_volume.unwrap_or(1.0),
config.audio.output_volume.unwrap_or(1.0),
phase_watcher.1.clone(),
- );
+ ).map_err(|e| StateError::AudioError(e))?;
let mut state = Self {
config,
server: None,
@@ -77,7 +78,7 @@ impl State {
phase_watcher,
};
state.reload_config();
- state
+ Ok(state)
}
pub fn handle_command(
@@ -462,7 +463,7 @@ impl State {
== self.get_users_channel(self.server().unwrap().session_id().unwrap())
{
if let Some(channel) = self.server().unwrap().channels().get(&channel_id) {
- notify::send(format!(
+ notifications::send(format!(
"{} connected and joined {}",
&msg.get_name(),
channel.name()
@@ -515,7 +516,7 @@ impl State {
self.get_users_channel(self.server().unwrap().session_id().unwrap());
if from_channel == this_channel || to_channel == this_channel {
if let Some(channel) = self.server().unwrap().channels().get(&to_channel) {
- notify::send(format!(
+ notifications::send(format!(
"{} moved to channel {}",
user.name(),
channel.name()
@@ -544,7 +545,7 @@ impl State {
s += if deaf { " deafened" } else { " undeafened" };
}
s += " themselves";
- notify::send(s);
+ notifications::send(s);
}
}
}
@@ -560,7 +561,7 @@ impl State {
if this_channel == other_channel {
self.audio.play_effect(NotificationEvents::UserDisconnected);
if let Some(user) = self.server().unwrap().users().get(&msg.get_session()) {
- notify::send(format!("{} disconnected", &user.name()));
+ notifications::send(format!("{} disconnected", &user.name()));
}
}
@@ -573,7 +574,12 @@ impl State {
}
pub fn reload_config(&mut self) {
- self.config = mumlib::config::read_default_cfg();
+ match mumlib::config::read_default_cfg() {
+ Ok(config) => {
+ self.config = config;
+ }
+ Err(e) => error!("Couldn't read config: {}", e),
+ }
if let Some(input_volume) = self.config.audio.input_volume {
self.audio.set_input_volume(input_volume);
}
diff --git a/mumlib/Cargo.toml b/mumlib/Cargo.toml
index 240e017..43bd8c5 100644
--- a/mumlib/Cargo.toml
+++ b/mumlib/Cargo.toml
@@ -12,6 +12,7 @@ license = "MIT"
[dependencies]
colored = "2.0"
+dirs = "3"
fern = "0.6"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
diff --git a/mumlib/src/config.rs b/mumlib/src/config.rs
index 0a43253..9394b85 100644
--- a/mumlib/src/config.rs
+++ b/mumlib/src/config.rs
@@ -1,9 +1,12 @@
+use crate::error::ConfigError;
use crate::DEFAULT_PORT;
+
+use log::*;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fs;
use std::net::{SocketAddr, ToSocketAddrs};
-use std::path::Path;
+use std::path::{Path, PathBuf};
use toml::value::Array;
use toml::Value;
@@ -20,13 +23,9 @@ pub struct Config {
}
impl Config {
- pub fn write_default_cfg(&self, create: bool) -> Result<(), std::io::Error> {
- let path = if create {
- get_creatable_cfg_path()
- } else {
- get_cfg_path()
- };
- let path = std::path::Path::new(&path);
+ pub fn write_default_cfg(&self, create: bool) -> Result<(), ConfigError> {
+ let path = default_cfg_path();
+
// Possible race here. It's fine since it shows when:
// 1) the file doesn't exist when checked and is then created
// 2) the file exists when checked but is then removed
@@ -35,14 +34,15 @@ impl Config {
// should work. Unless the file is removed AND the permissions
// change, but then we don't have permissions so we can't
// do anything anyways.
+
if !create && !path.exists() {
- return Ok(());
+ return Err(ConfigError::WontCreateFile);
}
- fs::write(
- path,
- toml::to_string(&TOMLConfig::from(self.clone())).unwrap(),
- )
+ Ok(fs::write(
+ &path,
+ toml::to_string(&TOMLConfig::from(self.clone()))?,
+ )?)
}
}
@@ -80,36 +80,15 @@ impl ServerConfig {
}
}
-pub fn get_cfg_path() -> String {
- if let Ok(var) = std::env::var("XDG_CONFIG_HOME") {
- let path = format!("{}/mumdrc", var);
- if Path::new(&path).exists() {
- return path;
- }
- } else if let Ok(var) = std::env::var("HOME") {
- let path = format!("{}/.config/mumdrc", var);
- if Path::new(&path).exists() {
- return path;
+pub fn default_cfg_path() -> PathBuf {
+ match dirs::config_dir() {
+ Some(mut p) => {
+ p.push("mumdrc");
+ p
}
+ //TODO This isn't cross platform.
+ None => PathBuf::from("/etc/mumdrc")
}
-
- "/etc/mumdrc".to_string()
-}
-
-pub fn get_creatable_cfg_path() -> String {
- if let Ok(var) = std::env::var("XDG_CONFIG_HOME") {
- let path = format!("{}/mumdrc", var);
- if !Path::new(&path).exists() {
- return path;
- }
- } else if let Ok(var) = std::env::var("HOME") {
- let path = format!("{}/.config/mumdrc", var);
- if !Path::new(&path).exists() {
- return path;
- }
- }
-
- "/etc/mumdrc".to_string()
}
pub fn cfg_exists() -> bool {
@@ -162,6 +141,7 @@ impl From<Config> for TOMLConfig {
config
.servers
.into_iter()
+ // Safe since all ServerConfigs are valid TOML
.map(|s| Value::try_from::<ServerConfig>(s).unwrap())
.collect(),
),
@@ -169,13 +149,20 @@ impl From<Config> for TOMLConfig {
}
}
-pub fn read_default_cfg() -> Config {
- Config::try_from(
- toml::from_str::<TOMLConfig>(&match fs::read_to_string(get_cfg_path()) {
- Ok(f) => f,
- Err(_) => return Config::default(),
- })
- .expect("invalid TOML in config file"), //TODO
- )
- .expect("invalid config in TOML") //TODO
+pub fn read_default_cfg() -> Result<Config, ConfigError> {
+ let path = default_cfg_path();
+ match fs::read_to_string(&path) {
+ Ok(s) => {
+ let toml_config: TOMLConfig = toml::from_str(&s)?;
+ Ok(Config::try_from(toml_config)?)
+ },
+ Err(e) => {
+ if matches!(e.kind(), std::io::ErrorKind::NotFound) && !path.exists() {
+ warn!("Config file not found");
+ } else {
+ error!("Error reading config file: {}", e);
+ }
+ return Ok(Config::default());
+ }
+ }
}
diff --git a/mumlib/src/error.rs b/mumlib/src/error.rs
index f6a02a7..6b7dccd 100644
--- a/mumlib/src/error.rs
+++ b/mumlib/src/error.rs
@@ -42,3 +42,42 @@ impl fmt::Display for ChannelIdentifierError {
}
}
}
+
+pub enum ConfigError {
+ InvalidConfig,
+ TOMLErrorSer(toml::ser::Error),
+ TOMLErrorDe(toml::de::Error),
+
+ WontCreateFile,
+ IOError(std::io::Error),
+}
+
+impl fmt::Display for ConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ ConfigError::InvalidConfig => write!(f, "Invalid configuration"),
+ ConfigError::TOMLErrorSer(e) => write!(f, "Invalid TOML when serializing: {}", e),
+ ConfigError::TOMLErrorDe(e) => write!(f, "Invalid TOML when deserializing: {}", e),
+ ConfigError::WontCreateFile => write!(f, "File does not exist but caller didn't allow creation"),
+ ConfigError::IOError(e) => write!(f, "IO error: {}", e),
+ }
+ }
+}
+
+impl From<std::io::Error> for ConfigError {
+ fn from(e: std::io::Error) -> Self {
+ ConfigError::IOError(e)
+ }
+}
+
+impl From<toml::ser::Error> for ConfigError {
+ fn from(e: toml::ser::Error) -> Self {
+ ConfigError::TOMLErrorSer(e)
+ }
+}
+
+impl From<toml::de::Error> for ConfigError {
+ fn from(e: toml::de::Error) -> Self {
+ ConfigError::TOMLErrorDe(e)
+ }
+}