use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::net::{SocketAddrV4, SocketAddr};
use std::sync::Arc;
use std::io;
use crypto_common::KeyInit;
use blowfish::Blowfish;
use rand::RngCore;
use rsa::RsaPrivateKey;
use rand::rngs::OsRng;
use crate::net::bundle::BundleElement;
use crate::net::element::login::{
id as login_id,
Ping,
LoginRequest, LoginRequestEncryption,
LoginResponse, LoginResponseEncryption,
ChallengeResponse, CuckooCycleResponse,
LoginChallenge, LoginSuccess, LoginError,
};
use super::{Interface, Shared, Peer};
pub struct LoginInterface<S: LoginShared> {
inner: Interface<LoginApp<S>>,
}
impl<S: LoginShared> LoginInterface<S> {
pub fn new(addr: SocketAddrV4, shared: S) -> io::Result<Self> {
let mut inner = Interface::new(addr, LoginApp {
shared,
priv_key: None,
clients: HashMap::new(),
})?;
inner.register(login_id::LOGIN_REQUEST, LoginApp::on_login_request, LoginApp::login_request_config);
inner.register_simple(login_id::PING, LoginApp::on_ping);
inner.register_simple(login_id::CHALLENGE_RESPONSE, LoginApp::on_challenge_response);
Ok(Self { inner })
}
#[inline]
pub fn shared(&self) -> &S {
&self.inner.shared().shared
}
#[inline]
pub fn shared_mut(&mut self) -> &mut S {
&mut self.inner.shared_mut().shared
}
}
pub struct LoginApp<S: LoginShared> {
#[allow(unused)] shared: S,
priv_key: Option<Arc<RsaPrivateKey>>,
clients: HashMap<SocketAddr, Client>,
}
impl<S: LoginShared> LoginApp<S> {
fn on_ping(&mut self, element: BundleElement<Ping>, mut peer: Peer<Self>) {
peer.element_writer().write_simple_reply(element.element, element.request_id.unwrap());
}
fn ensure_client(&mut self, addr: SocketAddr) -> &mut Client {
match self.clients.entry(addr) {
Entry::Occupied(o) => o.into_mut(),
Entry::Vacant(v) => v.insert(Client::new()),
}
}
fn login_request_config(&mut self, _addr: SocketAddr) -> LoginRequestEncryption {
if let Some(priv_key) = &self.priv_key {
LoginRequestEncryption::Server(priv_key.clone())
} else {
LoginRequestEncryption::Clear
}
}
fn on_login_request(&mut self, element: BundleElement<LoginRequest>, mut peer: Peer<Self>) {
let request_id = element.request_id.expect("login request must have a request id");
let client = self.ensure_client(peer.addr());
let bf = Arc::new(Blowfish::new_from_slice(&element.element.blowfish_key).unwrap());
client.blowfish = Some(bf.clone());
let encryption = LoginResponseEncryption::Encrypted(bf);
if !client.challenge_complete {
let cuckoo_prefix_value = OsRng.next_u64();
let cuckoo_prefix = format!("{cuckoo_prefix_value:>02X}");
let cuckoo_easiness = 0.9;
let challenge = LoginChallenge::CuckooCycle {
prefix: cuckoo_prefix,
max_nonce: ((1 << 20) as f32 * cuckoo_easiness) as _,
};
peer.element_writer().write_reply(LoginResponse::Challenge(challenge), &encryption, request_id);
} else {
let res;
match self.shared.try_login(&element.element) {
Ok((addr, login_key)) => {
res = LoginResponse::Success(LoginSuccess {
addr,
login_key,
server_message: String::new(),
});
}
Err(()) => {
res = LoginResponse::Error(LoginError::InvalidPassword, String::new());
}
}
peer.element_writer().write_reply(res, &encryption, request_id);
}
}
fn on_challenge_response(&mut self, _element: BundleElement<ChallengeResponse<CuckooCycleResponse>>, peer: Peer<Self>) {
self.ensure_client(peer.addr()).challenge_complete = true;
}
}
impl<S: LoginShared> Shared for LoginApp<S> { }
pub trait LoginShared: Shared {
fn try_login(&mut self, request: &LoginRequest) -> Result<(SocketAddrV4, u32), ()>;
}
#[derive(Debug)]
struct Client {
blowfish: Option<Arc<Blowfish>>,
challenge_complete: bool,
}
impl Client {
#[inline]
pub fn new() -> Self {
Self {
blowfish: None,
challenge_complete: false,
}
}
}