xeon
4b86d62d3f
split TcpStream into read and write halves, so it's possible to recv and send concurrently implement 'player nickname' command
250 lines
7.2 KiB
Rust
250 lines
7.2 KiB
Rust
use std::sync::{atomic::AtomicU32, Arc, LazyLock};
|
|
use thiserror::Error;
|
|
|
|
use byteorder::{BE, LE};
|
|
use common::{
|
|
cryptography::{Ec2b, MhyXorpad},
|
|
util,
|
|
};
|
|
use proto::{CmdID, NapMessage, PacketHead};
|
|
use tokio::{
|
|
io::AsyncWriteExt,
|
|
net::{
|
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
|
TcpStream,
|
|
},
|
|
sync::{Mutex, OnceCell},
|
|
};
|
|
|
|
use crate::{
|
|
handlers::{self, PacketHandlingError},
|
|
ServerState,
|
|
};
|
|
|
|
use super::packet::DecodeError;
|
|
use super::NetPacket;
|
|
|
|
static SECRET_KEY: LazyLock<MhyXorpad> = LazyLock::new(|| {
|
|
let ec2b = Ec2b::read(&mut util::open_secret_key().expect("Failed to open secret key file"))
|
|
.expect("Failed to read Ec2b data");
|
|
|
|
MhyXorpad::new::<LE>(ec2b.derive_seed())
|
|
});
|
|
|
|
pub struct NetSession {
|
|
id: u64,
|
|
reader: Mutex<OwnedReadHalf>,
|
|
writer: Mutex<OwnedWriteHalf>,
|
|
session_key: OnceCell<MhyXorpad>,
|
|
packet_id_counter: AtomicU32,
|
|
state: AtomicNetSessionState,
|
|
account_uid: OnceCell<String>,
|
|
player_uid: OnceCell<u32>,
|
|
}
|
|
|
|
#[atomic_enum::atomic_enum]
|
|
#[derive(PartialEq, Eq, PartialOrd)]
|
|
pub enum NetSessionState {
|
|
StartEnterGameWorld,
|
|
PlayerGetTokenCsReq,
|
|
PlayerGetTokenScRsp,
|
|
PlayerLoginCsReq,
|
|
PlayerLoginScRsp,
|
|
StartBasicsReq,
|
|
EndBasicsReq,
|
|
EnterWorldScRsp,
|
|
}
|
|
|
|
impl NetSessionState {
|
|
pub fn is_command_allowed(&self, cmd_id: u16) -> bool {
|
|
match cmd_id {
|
|
proto::PlayerGetTokenCsReq::CMD_ID => *self == NetSessionState::StartEnterGameWorld,
|
|
proto::PlayerLoginCsReq::CMD_ID => *self == NetSessionState::PlayerGetTokenScRsp,
|
|
_ => *self >= NetSessionState::StartBasicsReq,
|
|
}
|
|
}
|
|
|
|
pub fn is_auth(&self) -> bool {
|
|
*self < NetSessionState::PlayerLoginScRsp
|
|
}
|
|
}
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum SessionError {
|
|
#[error("NetPacket decode failed: {0}")]
|
|
PacketDecode(#[from] DecodeError),
|
|
#[error("failed to handle packet: {0}")]
|
|
PacketHandling(#[from] PacketHandlingError),
|
|
}
|
|
|
|
impl NetSession {
|
|
pub fn new(id: u64, stream: TcpStream) -> Self {
|
|
let (reader, writer) = stream.into_split();
|
|
|
|
Self {
|
|
id,
|
|
reader: Mutex::new(reader),
|
|
writer: Mutex::new(writer),
|
|
session_key: OnceCell::new(),
|
|
packet_id_counter: AtomicU32::new(0),
|
|
state: AtomicNetSessionState::new(NetSessionState::StartEnterGameWorld),
|
|
account_uid: OnceCell::new(),
|
|
player_uid: OnceCell::new(),
|
|
}
|
|
}
|
|
|
|
pub async fn run(&self, state: Arc<ServerState>) -> Result<(), SessionError> {
|
|
let mut last_save_time = util::cur_timestamp();
|
|
|
|
let result = loop {
|
|
let packet = match NetPacket::read(&mut *self.reader.lock().await).await {
|
|
Ok(packet) => packet,
|
|
Err(DecodeError::IoError(_)) => break Ok(()),
|
|
Err(err) => break Err(SessionError::PacketDecode(err)),
|
|
};
|
|
|
|
match self.handle_packet(packet, &state).await {
|
|
Ok(()) => (),
|
|
Err(PacketHandlingError::Logout) => break Ok(()),
|
|
Err(err) => break Err(SessionError::PacketHandling(err)),
|
|
}
|
|
|
|
if let Some(uid) = self.player_uid.get() {
|
|
if (util::cur_timestamp() - last_save_time)
|
|
>= state.config.player_save_period_seconds
|
|
{
|
|
state.player_mgr.save(*uid).await;
|
|
last_save_time = util::cur_timestamp();
|
|
}
|
|
}
|
|
};
|
|
|
|
self.on_disconnect(&state).await;
|
|
result
|
|
}
|
|
|
|
async fn handle_packet(
|
|
&self,
|
|
mut packet: NetPacket,
|
|
state: &ServerState,
|
|
) -> Result<(), PacketHandlingError> {
|
|
self.xor_payload(packet.cmd_id, &mut packet.body);
|
|
let net_state = self.state.load(std::sync::atomic::Ordering::SeqCst);
|
|
|
|
if !net_state.is_command_allowed(packet.cmd_id) {
|
|
tracing::warn!(
|
|
"received cmd_id ({}) is not allowed in current state ({:?})",
|
|
packet.cmd_id,
|
|
self.state.load(std::sync::atomic::Ordering::SeqCst)
|
|
);
|
|
} else if net_state.is_auth() {
|
|
if !handlers::handle_auth_request(self, &packet, state).await? {
|
|
tracing::warn!(
|
|
"[LOGIN] packet with cmd_id={} wasn't handled, body: {}",
|
|
packet.cmd_id,
|
|
hex::encode(&packet.body)
|
|
);
|
|
}
|
|
} else if !handlers::handle_request(self, &packet, state).await? {
|
|
if !handlers::handle_notify(self, &packet, state).await? {
|
|
tracing::warn!(
|
|
"packet with cmd_id={} wasn't handled, body: {}",
|
|
packet.cmd_id,
|
|
hex::encode(&packet.body)
|
|
);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn on_disconnect(&self, state: &ServerState) {
|
|
state.session_mgr.remove(self.id);
|
|
if let Some(player_uid) = self.player_uid.get() {
|
|
state.player_mgr.save_and_remove(*player_uid).await;
|
|
}
|
|
}
|
|
|
|
pub async fn notify(&self, mut ntf: impl NapMessage) -> Result<(), std::io::Error> {
|
|
ntf.xor_fields();
|
|
|
|
self.send(NetPacket {
|
|
cmd_id: ntf.get_cmd_id(),
|
|
head: PacketHead {
|
|
packet_id: self.next_packet_id(),
|
|
..Default::default()
|
|
},
|
|
body: ntf.encode_to_vec().into_boxed_slice(),
|
|
})
|
|
.await
|
|
}
|
|
|
|
pub async fn send_rsp(
|
|
&self,
|
|
request_id: u32,
|
|
mut rsp: impl NapMessage,
|
|
) -> Result<(), std::io::Error> {
|
|
rsp.xor_fields();
|
|
|
|
self.send(NetPacket {
|
|
cmd_id: rsp.get_cmd_id(),
|
|
head: PacketHead {
|
|
packet_id: self.next_packet_id(),
|
|
request_id,
|
|
..Default::default()
|
|
},
|
|
body: rsp.encode_to_vec().into_boxed_slice(),
|
|
})
|
|
.await
|
|
}
|
|
|
|
async fn send(&self, mut packet: NetPacket) -> Result<(), std::io::Error> {
|
|
self.xor_payload(packet.cmd_id, &mut packet.body);
|
|
|
|
let buf = packet.encode();
|
|
self.writer.lock().await.write_all(&buf).await
|
|
}
|
|
|
|
pub fn id(&self) -> u64 {
|
|
self.id
|
|
}
|
|
|
|
pub fn set_session_key(&self, seed: u64) {
|
|
let _ = self.session_key.set(MhyXorpad::new::<BE>(seed));
|
|
}
|
|
|
|
pub fn account_uid(&self) -> Option<&String> {
|
|
self.account_uid.get()
|
|
}
|
|
|
|
pub fn set_account_uid(&self, uid: String) -> bool {
|
|
self.account_uid.set(uid).is_ok()
|
|
}
|
|
|
|
pub fn player_uid(&self) -> Option<&u32> {
|
|
self.player_uid.get()
|
|
}
|
|
|
|
pub fn set_player_uid(&self, uid: u32) -> bool {
|
|
self.player_uid.set(uid).is_ok()
|
|
}
|
|
|
|
fn xor_payload(&self, cmd_id: u16, buf: &mut [u8]) {
|
|
let key = match self.session_key.get() {
|
|
_ if cmd_id == proto::PlayerGetTokenScRsp::CMD_ID => &*SECRET_KEY,
|
|
Some(key) => key,
|
|
None => &*SECRET_KEY,
|
|
};
|
|
|
|
key.xor(buf);
|
|
}
|
|
|
|
fn next_packet_id(&self) -> u32 {
|
|
self.packet_id_counter
|
|
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
|
|
}
|
|
|
|
pub fn set_state(&self, state: NetSessionState) {
|
|
self.state.store(state, std::sync::atomic::Ordering::SeqCst);
|
|
}
|
|
}
|