#[macro_use]
mod errors;
use std::net::{SocketAddr, ToSocketAddrs};
use errors::AnidbError;
use std::str;
use std::thread;
use std::time::Duration;
use std::net::UdpSocket;
pub type Result<T> = std::result::Result<T, AnidbError>;
pub struct Anidb {
pub socket: UdpSocket,
pub address: SocketAddr,
pub session: String,
}
pub struct ServerReply<'a> {
pub code: usize,
pub data: &'a str,
}
impl Anidb {
pub fn new<A: ToSocketAddrs>(addr: A) -> Result<Anidb> {
let socket = try!(UdpSocket::bind(("0.0.0.0", 9000)));
try!(socket.connect(&addr));
Ok(Anidb {
socket: socket,
address: addr.to_socket_addrs().unwrap().next().unwrap(),
session: "".to_owned(),
})
}
pub fn login(&mut self, username: &str, password: &str) -> Result<()> {
let mut result = [0; 2048];
let login_str = Self::format_login_string(username, password);
try!(self.socket.send(login_str.as_bytes()));
let len = try!(self.socket.recv(&mut result));
let reply = try!(Self::parse_reply(&result, len));
println!("Reply from server {}", reply.data);
self.session = try!(Self::validate_auth_command(&reply));
Ok(())
}
pub fn logout(&mut self) -> Result<()> {
let mut result = [0; 2048];
if self.session == "" {
return Err(AnidbError::StaticError("Not logged in"));
}
let login_str = Self::format_logout_string(&self.session);
try!(self.socket.send(login_str.as_bytes()));
let len = try!(self.socket.recv(&mut result));
let reply = try!(Self::parse_reply(&result, len));
println!("Reply from server {}", reply.data);
Ok(())
}
fn validate_auth_command(reply: &ServerReply) -> Result<String> {
if reply.code != 200 {
return Err(AnidbError::ErrorCode(reply.code, reply.data.to_owned()));
}
let v: Vec<&str> = reply.data.split(' ').collect();
if v.len() != 3 {
return Err(AnidbError::Error(format!("Invalid AUTH reply: {} expceted 3 args", reply.data)));
}
if v[1] != "LOGIN" || v[2] != "ACCEPTED\n" {
return Err(AnidbError::Error(format!("Invalid AUTH reply: {} LOGIN ACCEPTED\\n expected", reply.data)));
}
Ok(v[0].to_owned())
}
pub fn wait_exec_command(&self, time: u64) {
thread::sleep(Duration::from_millis(time))
}
fn parse_reply(reply: &[u8], len: usize) -> Result<ServerReply> {
if len < 5 {
return Err(AnidbError::StaticError("Reply less than 5 chars"));
}
let code_str = try!(str::from_utf8(&reply[0..3]));
let code = try!(code_str.parse::<usize>());
Ok(ServerReply {
code: code,
data: try!(str::from_utf8(&reply[4..len])),
})
}
fn format_logout_string(session_id: &str) -> String {
format!("LOGOUT s={}", session_id)
}
fn format_login_string(username: &str, password: &str) -> String {
format!("AUTH user={}&pass={}&protover=3&client=anidbrs&clientver=1", username, password)
}
}
#[cfg(test)]
mod test_parse {
use super::*;
#[test]
fn test_parse_reply_ok() {
let reply = b"500 LOGIN FAILED";
let ret = Anidb::parse_reply(reply, reply.len()).unwrap();
assert_eq!(ret.code, 500);
assert_eq!(ret.data, "LOGIN FAILED");
}
#[test]
fn test_parse_reply_fail_1() {
let reply = b"a3i5LOGIN FAILED";
assert_eq!(true, Anidb::parse_reply(reply, reply.len()).is_err());
}
#[test]
fn test_parse_reply_fail_2() {
let reply = b"34i5LOGIN FAILED";
assert_eq!(true, Anidb::parse_reply(reply, reply.len()).is_err());
}
#[test]
fn test_parse_reply_too_short() {
let reply = b"3D";
assert_eq!(true, Anidb::parse_reply(reply, reply.len()).is_err());
}
#[test]
fn test_parse_reply_exact_length() {
let reply = b"777 O";
let ret = Anidb::parse_reply(reply, reply.len()).unwrap();
assert_eq!(ret.code, 777);
assert_eq!(ret.data, "O");
}
}
#[cfg(test)]
mod test_format {
use super::*;
#[test]
fn test_format_login_string() {
let login_string = Anidb::format_login_string("leeloo_dallas", "multipass");
assert_eq!(login_string, "AUTH user=leeloo_dallas&pass=multipass&protover=3&client=anidbrs&clientver=1");
}
#[test]
fn test_format_logout_string() {
let logout_str = Anidb::format_logout_string("abcd1234");
assert_eq!(logout_str, "LOGOUT s=abcd1234");
}
}