use std::io::{BufRead, BufReader, Write};
use std::net::{Shutdown, TcpStream};
use std::ops::Drop;
use whoami;
#[derive(PartialEq, Debug)]
pub enum Error {
ConnectionFailed,
Unimplemented,
AllocationFailed,
ConnectionLost,
RequestDenied,
UnanticipatedReply,
}
impl Error {
pub fn to_string(&self) -> String {
match self {
Error::ConnectionFailed => String::from("Connection to port manager failed"),
Error::Unimplemented => String::from("This operation is not yet implemented"),
Error::AllocationFailed => String::from("Failed to allocate a port from the manager"),
Error::ConnectionLost => String::from("connection with server lost"),
Error::RequestDenied => String::from("Server returned a failure on the request."),
Error::UnanticipatedReply => {
String::from("The server reply was not an anticipated string")
}
}
}
}
#[derive(Debug, Clone)]
pub struct Allocation {
pub port: u16,
pub service_name: String,
pub user_name: String,
}
pub struct Client {
port: u16,
connection: Option<TcpStream>,
reader: Option<BufReader<TcpStream>>,
}
impl Client {
fn make_connection(&mut self) -> Result<TcpStream, Error> {
if self.connection.as_ref().is_some() {
Ok(self
.connection
.as_ref()
.expect("should be some")
.try_clone()
.unwrap())
} else {
let address = format!("127.0.0.1:{}", self.port);
match TcpStream::connect(&address) {
Ok(socket) => {
self.connection = Some(socket);
self.reader = Some(BufReader::new(
self.connection.as_ref().expect("OK").try_clone().unwrap(),
));
Ok(self
.connection
.as_ref()
.expect("should be some")
.try_clone()
.unwrap())
}
Err(_) => Err(Error::ConnectionFailed),
}
}
}
fn get_reply(&mut self) -> Result<Vec<String>, Error> {
let mut reply = String::new();
if self
.reader
.as_mut()
.expect("BUG")
.read_line(&mut reply)
.unwrap()
> 0
{
let words: Vec<&str> = reply.trim().split(" ").collect();
match words[0] {
"OK" => {
let mut result = Vec::<String>::new();
if words.len() > 1 {
for w in &words[1..] {
result.push(String::from(*w));
}
}
Ok(result)
}
"FAIL" => Err(Error::RequestDenied),
_ => Err(Error::UnanticipatedReply),
}
} else {
Err(Error::ConnectionLost)
}
}
fn get_allocations(&mut self, n: usize) -> Result<Vec<Allocation>, Error> {
let mut result: Vec<Allocation> = Vec::new();
for _i in 0..n {
let mut allocation_string = String::new();
if let Ok(size) = self
.reader
.as_mut()
.expect("BUG")
.read_line(&mut allocation_string)
{
if size > 0 {
let words: Vec<&str> = allocation_string.trim().split(" ").collect();
if words.len() == 3 {
let service = String::from(words[1]);
let user = String::from(words[2]);
if let Ok(port) = String::from(words[0]).parse::<u16>() {
result.push(Allocation {
port: port,
service_name: service,
user_name: user,
});
} else {
return Err(Error::UnanticipatedReply);
}
} else {
return Err(Error::UnanticipatedReply);
}
} else {
return Err(Error::ConnectionLost);
}
} else {
return Err(Error::ConnectionLost);
}
}
return Ok(result);
}
pub fn new(port: u16) -> Client {
Client {
port: port,
connection: None,
reader: None,
}
}
pub fn get(&mut self, service_name: &str) -> Result<u16, Error> {
match self.make_connection() {
Err(e) => Err(e),
Ok(mut socket) => {
let me = whoami::username();
let request = format!("GIMME {} {}\n", service_name, me);
if let Err(_e) = socket.write_all(request.as_bytes()) {
return Err(Error::ConnectionLost);
}
if let Err(_e) = socket.flush() {
return Err(Error::ConnectionLost);
}
match self.get_reply() {
Ok(port) => {
if port.len() == 1 {
let parsed_port = port[0].parse::<u16>();
match parsed_port {
Ok(num) => Ok(num),
Err(_) => Err(Error::UnanticipatedReply),
}
} else {
Err(Error::UnanticipatedReply)
}
}
Err(reason) => Err(reason),
}
}
}
}
pub fn list(&mut self) -> Result<Vec<Allocation>, Error> {
match self.make_connection() {
Err(e) => Err(e),
Ok(mut socket) => {
if let Err(_e) = socket.write_all(b"LIST\n") {
return Err(Error::ConnectionLost);
}
if let Err(_e) = socket.flush() {
return Err(Error::ConnectionLost);
}
match self.get_reply() {
Ok(tail) => {
if tail.len() == 1 {
let num_lines = tail[0].parse::<usize>();
match num_lines {
Ok(n) => self.get_allocations(n),
Err(_) => Err(Error::UnanticipatedReply),
}
} else {
Err(Error::UnanticipatedReply)
}
}
Err(reason) => Err(reason),
}
}
}
}
pub fn find_by_service(&mut self, service_name: &str) -> Result<Vec<Allocation>, Error> {
match self.list() {
Ok(all_services) => {
let result: Vec<Allocation> = all_services
.into_iter()
.filter(|item| item.service_name == service_name)
.collect::<Vec<Allocation>>();
Ok(result)
}
Err(e) => Err(e),
}
}
pub fn find_by_user(&mut self, user_name: &str) -> Result<Vec<Allocation>, Error> {
match self.list() {
Ok(all_services) => {
let result = all_services
.into_iter()
.filter(|item| item.user_name == user_name)
.collect::<Vec<Allocation>>();
Ok(result)
}
Err(e) => Err(e),
}
}
pub fn find_exact(
&mut self,
service_name: &str,
user_name: &str,
) -> Result<Vec<Allocation>, Error> {
match self.find_by_user(user_name) {
Ok(user_services) => {
let result = user_services
.into_iter()
.filter(|item| item.service_name == service_name)
.collect::<Vec<Allocation>>();
Ok(result)
}
Err(e) => Err(e),
}
}
pub fn find_my_service(&mut self, service_name: &str) -> Result<Vec<Allocation>, Error> {
let me = whoami::username();
self.find_exact(service_name, &me)
}
}
impl Drop for Client {
fn drop(&mut self) {
if let Some(s) = &mut self.connection {
let _ = s.shutdown(Shutdown::Both);
}
}
}
#[cfg(feature = "test_with_portman")]
mod portman_ctests {
use super::*;
use whoami;
#[test]
fn new_1() {
let portman = Client::new(30000);
assert_eq!(30000, portman.port);
assert!(portman.connection.is_none());
}
#[test]
fn connect_1() {
let mut portman = Client::new(30000);
match portman.make_connection() {
Ok(_) => assert!(true),
Err(_reason) => assert!(false, "Should have connected"),
}
}
#[test]
fn connect_2() {
let mut portman = Client::new(30001); match portman.make_connection() {
Ok(_) => assert!(false, "Connection should have failed"),
Err(reason) => assert_eq!(Error::ConnectionFailed, reason),
}
}
#[test]
fn get_1() {
let mut portman = Client::new(30000);
match portman.get("testing") {
Ok(_port) => assert!(true),
Err(e) => assert!(false, "{}", e.to_string()),
}
}
#[test]
fn get_2() {
let mut portman = Client::new(30000);
portman.get("testing").unwrap();
match portman.get("testing") {
Ok(_) => assert!(false, "Double allocation should fail"),
Err(e) => assert_eq!(Error::RequestDenied, e),
}
}
#[test]
fn list_1() {
let mut portman = Client::new(30000);
match portman.list() {
Ok(allocs) => assert_eq!(0, allocs.len()),
Err(_) => assert!(false, "List failed"),
}
}
#[test]
fn list_2() {
let mut portman = Client::new(30000);
portman.get("Testing").unwrap();
let me = whoami::username();
let result = portman.list().unwrap();
assert_eq!(1, result.len());
assert_eq!("Testing", result[0].service_name);
assert_eq!(me, result[0].user_name);
}
#[test]
fn list_3() {
let mut portman = Client::new(30000);
portman.get("service1").unwrap();
portman.get("service2").unwrap();
portman.get("service3").unwrap();
portman.get("service4").unwrap();
let mut allocs = portman.list().unwrap();
assert_eq!(4, allocs.len());
allocs.sort_by_key(|item| String::from(item.service_name.as_str()));
assert_eq!("service1", allocs[0].service_name);
assert_eq!("service2", allocs[1].service_name);
assert_eq!("service3", allocs[2].service_name);
assert_eq!("service4", allocs[3].service_name);
}
#[test]
fn find_service_1() {
let mut portman = Client::new(30000);
portman.get("service1").unwrap();
portman.get("service2").unwrap();
portman.get("service3").unwrap();
portman.get("service4").unwrap();
let matches = portman.find_by_service("service2").unwrap();
assert_eq!(1, matches.len());
assert_eq!("service2", matches[0].service_name);
assert_eq!(whoami::username(), matches[0].user_name);
}
#[test]
fn find_service_2() {
let mut portman = Client::new(30000);
portman.get("service1").unwrap();
portman.get("service2").unwrap();
portman.get("service3").unwrap();
portman.get("service4").unwrap();
let matches = portman.find_by_service("service0").unwrap();
assert_eq!(0, matches.len());
}
#[test]
fn find_by_user_1() {
let mut portman = Client::new(30000);
portman.get("service1").unwrap();
portman.get("service2").unwrap();
portman.get("service3").unwrap();
portman.get("service4").unwrap();
let mut matches = portman.find_by_user(&whoami::username()).unwrap();
assert_eq!(4, matches.len());
matches.sort_by_key(|item| String::from(item.service_name.as_str()));
assert_eq!("service1", matches[0].service_name);
assert_eq!("service2", matches[1].service_name);
assert_eq!("service3", matches[2].service_name);
assert_eq!("service4", matches[3].service_name);
}
#[test]
fn find_by_user_2() {
let mut portman = Client::new(30000);
portman.get("service1").unwrap();
portman.get("service2").unwrap();
portman.get("service3").unwrap();
portman.get("service4").unwrap();
let matches = portman.find_by_user("no-such-user").unwrap();
assert_eq!(0, matches.len());
}
#[test]
fn find_exact_1() {
let mut portman = Client::new(30000);
portman.get("service1").unwrap();
portman.get("service2").unwrap();
portman.get("service3").unwrap();
portman.get("service4").unwrap();
let matches = portman.find_exact("service3", &whoami::username()).unwrap();
assert_eq!(1, matches.len());
assert_eq!("service3", matches[0].service_name);
assert_eq!(whoami::username(), matches[0].user_name);
}
#[test]
fn find_exact_2() {
let mut portman = Client::new(30000);
portman.get("service1").unwrap();
portman.get("service2").unwrap();
portman.get("service3").unwrap();
portman.get("service4").unwrap();
let matches = portman.find_exact("soivice", &whoami::username()).unwrap();
assert_eq!(0, matches.len());
}
}