use argparse::ArgumentParser;
use argparse::Store;
use argparse::StoreTrue;
use bytes::Bytes;
use futures::channel::oneshot;
use futures::join;
use futures::StreamExt;
use futures::SinkExt;
use mumble_protocol::control::msgs;
use mumble_protocol::control::ClientControlCodec;
use mumble_protocol::control::ControlPacket;
use mumble_protocol::crypt::ClientCryptState;
use mumble_protocol::voice::VoicePacket;
use mumble_protocol::voice::VoicePacketPayload;
use std::convert::Into;
use std::convert::TryInto;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
use tokio::net::TcpStream;
use tokio::net::UdpSocket;
use tokio_native_tls::TlsConnector;
use tokio_util::codec::Decoder;
use tokio_util::udp::UdpFramed;
async fn connect(
server_addr: SocketAddr,
server_host: String,
user_name: String,
accept_invalid_cert: bool,
crypt_state_sender: oneshot::Sender<ClientCryptState>,
) {
let mut crypt_state_sender = Some(crypt_state_sender);
let stream = TcpStream::connect(&server_addr).await.expect("Failed to connect to server:");
println!("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")
.into();
let tls_stream = connector
.connect(&server_host, stream)
.await
.expect("Failed to connect TLS: {}");
println!("TLS connected..");
let (mut sink, mut stream) = ClientControlCodec::new().framed(tls_stream).split();
let mut msg = msgs::Authenticate::new();
msg.set_username(user_name);
msg.set_opus(true);
sink.send(msg.into()).await.unwrap();
println!("Logging in..");
let mut crypt_state = None;
while let Some(packet) = stream.next().await {
match packet.unwrap() {
ControlPacket::TextMessage(mut msg) => {
println!(
"Got message from user with session ID {}: {}",
msg.get_actor(),
msg.get_message()
);
let mut response = msgs::TextMessage::new();
response.mut_session().push(msg.get_actor());
response.set_message(msg.take_message());
sink.send(response.into()).await.unwrap();
}
ControlPacket::CryptSetup(msg) => {
crypt_state = Some(ClientCryptState::new_from(
msg.get_key()
.try_into()
.expect("Server sent private key with incorrect size"),
msg.get_client_nonce()
.try_into()
.expect("Server sent client_nonce with incorrect size"),
msg.get_server_nonce()
.try_into()
.expect("Server sent server_nonce with incorrect size"),
));
}
ControlPacket::ServerSync(_) => {
println!("Logged in!");
if let Some(sender) = crypt_state_sender.take() {
let _ = sender.send(
crypt_state
.take()
.expect("Server didn't send us any CryptSetup packet!"),
);
}
}
ControlPacket::Reject(msg) => {
println!("Login rejected: {:?}", msg);
}
_ => {},
}
}
}
async fn handle_udp(
server_addr: SocketAddr,
crypt_state: oneshot::Receiver<ClientCryptState>,
) {
let udp_socket = UdpSocket::bind((Ipv6Addr::from(0u128), 0u16))
.await
.expect("Failed to bind UDP socket");
let crypt_state = match crypt_state.await {
Ok(crypt_state) => crypt_state,
Err(_) => return,
};
println!("UDP ready!");
let (mut sink, mut source) = UdpFramed::new(udp_socket, crypt_state).split();
sink.send((
VoicePacket::Audio {
_dst: std::marker::PhantomData,
target: 0,
session_id: (),
seq_num: 0,
payload: VoicePacketPayload::Opus(Bytes::from([0u8; 128].as_ref()), true),
position_info: None,
},
server_addr,
)).await.unwrap();
while let Some(packet) = source.next().await {
let (packet, src_addr) = match packet {
Ok(packet) => packet,
Err(err) => {
eprintln!("Got an invalid UDP packet: {}", err);
continue
}
};
match packet {
VoicePacket::Ping { .. } => {
continue
}
VoicePacket::Audio {
seq_num,
payload,
position_info,
..
} => {
let reply = VoicePacket::Audio {
_dst: std::marker::PhantomData,
target: 0, session_id: (), seq_num,
payload,
position_info,
};
sink.send((reply, src_addr)).await.unwrap();
}
}
}
}
#[tokio::main]
async fn main() {
let mut server_host = "".to_string();
let mut server_port = 64738u16;
let mut user_name = "EchoBot".to_string();
let mut accept_invalid_cert = false;
{
let mut ap = ArgumentParser::new();
ap.set_description("Run the echo client example");
ap.refer(&mut server_host)
.add_option(&["--host"], Store, "Hostname of mumble server")
.required();
ap.refer(&mut server_port)
.add_option(&["--port"], Store, "Port of mumble server");
ap.refer(&mut user_name)
.add_option(&["--username"], Store, "User name used to connect");
ap.refer(&mut accept_invalid_cert).add_option(
&["--accept-invalid-cert"],
StoreTrue,
"Accept invalid TLS certificates",
);
ap.parse_args_or_exit();
}
let server_addr = (server_host.as_ref(), server_port)
.to_socket_addrs()
.expect("Failed to parse server address")
.next()
.expect("Failed to resolve server address");
let (crypt_state_sender, crypt_state_receiver) = oneshot::channel::<ClientCryptState>();
join!(
connect(
server_addr,
server_host,
user_name,
accept_invalid_cert,
crypt_state_sender,
),
handle_udp(server_addr, crypt_state_receiver)
);
}