439 lines
No EOL
18 KiB
C#
439 lines
No EOL
18 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using PhoenixLib.Logging;
|
|
using WingsAPI.Communication;
|
|
using WingsAPI.Communication.Sessions;
|
|
using WingsAPI.Communication.Sessions.Model;
|
|
using WingsAPI.Communication.Sessions.Request;
|
|
using WingsAPI.Communication.Sessions.Response;
|
|
|
|
namespace WingsEmu.Master.Sessions
|
|
{
|
|
public class SessionService : ISessionService
|
|
{
|
|
private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
|
|
private readonly EncryptionKeyFactory _encryptionKeyFactory;
|
|
private readonly ISessionManager _sessionManager;
|
|
|
|
public SessionService(ISessionManager sessionManager, EncryptionKeyFactory encryptionKeyFactory)
|
|
{
|
|
_sessionManager = sessionManager;
|
|
_encryptionKeyFactory = encryptionKeyFactory;
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> CreateSession(CreateSessionRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session existingSession = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (existingSession is not null && existingSession.State != SessionState.Disconnected && existingSession.State != SessionState.ServerSelection)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CREATE_SESSION] A Session for account {request.AccountId} already exists");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
var session = new Session
|
|
{
|
|
Id = Guid.NewGuid().ToString().Replace("-", ""),
|
|
IpAddress = request.IpAddress,
|
|
AccountId = request.AccountId,
|
|
AccountName = request.AccountName,
|
|
Authority = request.AuthorityType,
|
|
State = SessionState.Disconnected,
|
|
EncryptionKey = _encryptionKeyFactory.CreateEncryptionKey()
|
|
};
|
|
|
|
bool created = await _sessionManager.Create(session);
|
|
if (!created)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CREATE_SESSION] Failed to save session of account {session.AccountId} with session id {session.Id} into redis");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
Log.Debug($"[SESSION_SERVICE][CREATE_SESSION] Successfully created session for account {session.AccountId} with session id {session.Id}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> GetSessionByAccountName(GetSessionByAccountNameRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountName(request.AccountName);
|
|
if (session is null)
|
|
{
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> GetSessionByAccountId(GetSessionByAccountIdRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (session is null)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][GET_SESSION_BY_ACCOUNT_ID] Couldn't find session with account id: {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
Log.Debug($"[SESSION_SERVICE][GET_SESSION_BY_ACCOUNT_ID] Successfully found a session with account id: {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> ConnectToLoginServer(ConnectToLoginServerRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (session is null)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Can't connect account with ID {request.AccountId} to login server (No session found)");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
if (session.State != SessionState.Disconnected && session.State != SessionState.CharacterSelection)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Can't session with ID {request.AccountId} to login server (Already connected)");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
session.State = SessionState.ServerSelection;
|
|
session.HardwareId = request.HardwareId;
|
|
session.ClientVersion = request.ClientVersion;
|
|
|
|
bool updated = await _sessionManager.Update(session);
|
|
if (!updated)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Failed to update session of {session.AccountId} into redis");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_LOGIN_SERVER] Successfully connected session of {session.AccountId} to login server");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> Disconnect(DisconnectSessionRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (session is null)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Can't disconnect session of account {request.AccountId} (Couldn't find session)");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
if (session.State == SessionState.CrossChannelAuthentication)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Can't disconnect session of account {request.AccountId} (CrossChannel or CharacterSelection)");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
if (request.ForceDisconnect == false)
|
|
{
|
|
if (session.EncryptionKey != request.EncryptionKey)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Can't disconnect session of account {request.AccountId} (CrossChannel or CharacterSelection)");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
}
|
|
|
|
session.State = SessionState.Disconnected;
|
|
session.CharacterId = 0;
|
|
session.ChannelId = 0;
|
|
session.ServerGroup = null;
|
|
|
|
bool saved = await _sessionManager.Update(session);
|
|
if (!saved)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Failed to update session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
Log.Debug($"[SESSION_SERVICE][DISCONNECT] Successfully updated session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> ConnectCharacter(ConnectCharacterRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (session is null || session.State != SessionState.CharacterSelection || session.ChannelId != request.ChannelId)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_CHARACTER] Can't connect character from session of account {request.AccountId} (Couldn't find session)");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
session.CharacterId = request.CharacterId;
|
|
session.State = SessionState.InGame;
|
|
|
|
bool updated = await _sessionManager.Update(session);
|
|
if (!updated)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_CHARACTER] Failed to update session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_CHARACTER] Successfully update session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> ActivateCrossChannelAuthentication(ActivateCrossChannelAuthenticationRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (session is null || session.State != SessionState.InGame)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][ACTIVE_CROSS_CHANNEL_AUTHENTICATION] Couldn't find session for account {request.AccountId} or account doesn't have correct state ({session?.State})");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
session.State = SessionState.CrossChannelAuthentication;
|
|
session.AllowedCrossChannelId = request.ChannelId;
|
|
|
|
bool updated = await _sessionManager.Update(session);
|
|
if (!updated)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][ACTIVE_CROSS_CHANNEL_AUTHENTICATION] Failed to update session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
|
|
Log.Debug($"[SESSION_SERVICE][ACTIVE_CROSS_CHANNEL_AUTHENTICATION] Successfully updated session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> Pulse(PulseRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (session is null || session.State != SessionState.InGame)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][PULSE] Couldn't find session of account or incorrect state {request.AccountId} ({session?.State})");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
bool pulsed = await _sessionManager.Pulse(session);
|
|
if (!pulsed)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][PULSE] Failed to pulse session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
session.LastPulse = DateTime.UtcNow;
|
|
|
|
bool updated = await _sessionManager.Update(session);
|
|
if (!updated)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][PULSE] Failed to update session of account {request.AccountId} after pulse");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
|
|
Log.Debug($"[SESSION_SERVICE][PULSE] Successfully pulsed session of account {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
|
|
public async ValueTask<SessionResponse> ConnectToWorldServer(ConnectToWorldServerRequest request)
|
|
{
|
|
await _semaphoreSlim.WaitAsync();
|
|
try
|
|
{
|
|
Session session = await _sessionManager.GetSessionByAccountId(request.AccountId);
|
|
if (session is null)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Couldn't find session of account or incorrect state {request.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
// Can't connect to world if not in server selection screen or cross channel
|
|
if (session.State != SessionState.ServerSelection && session.State != SessionState.CrossChannelAuthentication)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Incorrect session state {session.State}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
if (session.State == SessionState.CrossChannelAuthentication && session.AllowedCrossChannelId != request.ChannelId)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Incorrect cross channel ID {request.ChannelId} instead of {session.AllowedCrossChannelId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
session.LastChannelId = session.ChannelId;
|
|
session.ServerGroup = request.ServerGroup;
|
|
session.ChannelId = request.ChannelId;
|
|
session.State = SessionState.CharacterSelection;
|
|
session.AllowedCrossChannelId = 0;
|
|
|
|
bool updated = await _sessionManager.Update(session);
|
|
if (!updated)
|
|
{
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Failed to update session of account {session.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.GENERIC_SERVER_ERROR
|
|
};
|
|
}
|
|
|
|
Log.Debug($"[SESSION_SERVICE][CONNECT_TO_WORLD_SERVER] Successfully updated session of account {session.AccountId}");
|
|
return new SessionResponse
|
|
{
|
|
ResponseType = RpcResponseType.SUCCESS,
|
|
Session = session
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_semaphoreSlim.Release();
|
|
}
|
|
}
|
|
}
|
|
} |