示例#1
0
 /// <inheritdoc />
 public ZoneRegistryController([FromServices] IZoneServerRepository zoneRepository,
                               IClaimsPrincipalReader claimsReader,
                               ILogger <AuthorizationReadyController> logger)
     : base(claimsReader, logger)
 {
     ZoneRepository = zoneRepository ?? throw new ArgumentNullException(nameof(zoneRepository));
 }
示例#2
0
        public static async Task Test_Controller_Produces_AlreadyHasActiveSession_When_Session_Has()
        {
            //arrange
            IServiceProvider            serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 1);
            CharacterSessionController  controller      = serviceProvider.GetService <CharacterSessionController>();
            ICharacterRepository        characterRepo   = serviceProvider.GetService <ICharacterRepository>();
            ICharacterSessionRepository sessionRepo     = serviceProvider.GetService <ICharacterSessionRepository>();

            ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>();
            IZoneServerRepository        zoneRepository        = serviceProvider.GetService <IZoneServerRepository>();

            await characterRepo.TryCreateAsync(new CharacterEntryModel(1, "Testing"));

            await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, 0));

            //We can't create the claimed session through this interface because it's a stored procedure.
            //Raw SQL can't execute. So we must interact directly with the DbSet
            //await sessionRepo.TryClaimUnclaimedSession(1, 1);
            CharacterDatabaseContext context = serviceProvider.GetService <CharacterDatabaseContext>();
            await context.ClaimedSession.AddAsync(new ClaimedSessionsModel(1));

            await context.SaveChangesAsync();

            //act
            CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository);

            //assert
            Assert.False(response.isSuccessful, $"Characters that already have ");
            Assert.AreEqual(CharacterSessionEnterResponseCode.AccountAlreadyHasCharacterSession, response.ResultCode);
        }
示例#3
0
        public static async Task Test_Controller_Produces_InvalidId_When_Empty()
        {
            //arrange
            IServiceProvider           serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 1);
            CharacterSessionController controller      = serviceProvider.GetService <CharacterSessionController>();

            ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>();
            IZoneServerRepository        zoneRepository        = serviceProvider.GetService <IZoneServerRepository>();

            //act
            CharacterSessionEnterResponse response = await controller.EnterSession(5, characterLocationRepo, zoneRepository);

            //assert
            Assert.False(response.isSuccessful);
            Assert.AreEqual(CharacterSessionEnterResponseCode.InvalidCharacterIdError, response.ResultCode);
        }
        public async Task Test_ZoneServer_GetEndpoint_Succeeds_On_Known_Id(string endpoint, int port)
        {
            //arrange
            IServiceProvider      provider   = BuildServiceProvider <ZoneServerController>("Test", 1);
            ZoneServerController  controller = provider.GetService <ZoneServerController>();
            IZoneServerRepository repo       = provider.GetService <IZoneServerRepository>();
            await repo.TryCreateAsync(new ZoneInstanceEntryModel(endpoint, (short)port, 1));

            //assert
            ResolveServiceEndpointResponse result = GetActionResultObject <ResolveServiceEndpointResponse>(await controller.GetServerEndpoint(1));

            //assert
            Assert.True(result.isSuccessful);
            Assert.AreEqual(ResolveServiceEndpointResponseCode.Success, result.ResultCode);
            Assert.AreEqual(endpoint, result.Endpoint.EndpointAddress);
            Assert.AreEqual(port, result.Endpoint.EndpointPort);
        }
        public async Task Test_ZoneServer_GetEndpoint_ReturnsFail_On_NoExistingZoneId(int zoneId)
        {
            //arrange
            IServiceProvider      provider   = BuildServiceProvider <ZoneServerController>("Test", 1);
            ZoneServerController  controller = provider.GetService <ZoneServerController>();
            IZoneServerRepository repo       = provider.GetService <IZoneServerRepository>();
            await repo.TryCreateAsync(new ZoneInstanceEntryModel("127.0.0.1", 1080, 1));

            await repo.TryCreateAsync(new ZoneInstanceEntryModel("127.0.0.1", 1080, 1));

            await repo.TryCreateAsync(new ZoneInstanceEntryModel("127.0.0.1", 1080, 1));

            //assert
            ResolveServiceEndpointResponse result = GetActionResultObject <ResolveServiceEndpointResponse>(await controller.GetServerEndpoint(25));

            //assert
            Assert.False(result.isSuccessful);
        }
示例#6
0
        public static async Task Test_Controller_Creates_UnclaimedSession_On_OnEnterSession(int accountId)
        {
            //arrange
            IServiceProvider           serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", accountId);
            CharacterSessionController controller      = serviceProvider.GetService <CharacterSessionController>();
            ICharacterRepository       characterRepo   = serviceProvider.GetService <ICharacterRepository>();

            ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>();
            IZoneServerRepository        zoneRepository        = serviceProvider.GetService <IZoneServerRepository>();

            await characterRepo.TryCreateAsync(new CharacterEntryModel(accountId, "Testing"));

            //act: We also test that we can do it multiple times
            CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository);

            //assert
            Assert.True(response.isSuccessful);
            Assert.AreEqual(CharacterSessionEnterResponseCode.Success, response.ResultCode);
        }
示例#7
0
        public async Task <IActionResult> UpdateCharacterLocation(
            [FromRoute(Name = "id")] int characterId,
            [FromBody] ZoneServerCharacterLocationSaveRequest saveRequest,
            [NotNull][FromServices] ICharacterLocationRepository locationRepository,
            [NotNull][FromServices] IZoneServerRepository zoneRepository)
        {
            if (locationRepository == null)
            {
                throw new ArgumentNullException(nameof(locationRepository));
            }
            if (zoneRepository == null)
            {
                throw new ArgumentNullException(nameof(zoneRepository));
            }

            //TODO: For HTTP callers we should maybe include better information. Though with message queue we can't respond.
            if (!await CheckZoneAuthorizedToModifyCharacterData(characterId))
            {
                return(Forbid());
            }

            //TODO: This could fail if we unregistered. More gracefully handle that case.
            //Get world, we need it for location
            ZoneInstanceEntryModel zoneEntry = await zoneRepository.RetrieveAsync(ClaimsReader.GetAccountIdInt(User));

            //If world was deleted then they won't have location.
            //TODO: Is this the best way to deal with this?
            if (await locationRepository.ContainsAsync(characterId).ConfigureAwaitFalse())
            {
                await locationRepository.UpdateAsync(characterId, BuildCharacterLocationFromSave(characterId, saveRequest, zoneEntry.WorldId))
                .ConfigureAwaitFalseVoid();
            }
            else
            {
                await locationRepository.TryCreateAsync(BuildCharacterLocationFromSave(characterId, saveRequest, zoneEntry.WorldId))
                .ConfigureAwaitFalse();
            }

            return(Ok());
        }
示例#8
0
        public static async Task Test_Controller_Produces_InvalidId_When_Wrong_AccountId()
        {
            //arrange
            IServiceProvider            serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", 2);
            CharacterSessionController  controller      = serviceProvider.GetService <CharacterSessionController>();
            ICharacterRepository        characterRepo   = serviceProvider.GetService <ICharacterRepository>();
            ICharacterSessionRepository sessionRepo     = serviceProvider.GetService <ICharacterSessionRepository>();

            ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>();
            IZoneServerRepository        zoneRepository        = serviceProvider.GetService <IZoneServerRepository>();

            await characterRepo.TryCreateAsync(new CharacterEntryModel(1, "Testing"));

            await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, 0));

            //act
            CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository);

            //assert
            Assert.False(response.isSuccessful, $"Characters should not be able to create sessions when the accountid doesn't match.");
            Assert.AreEqual(response.ResultCode, CharacterSessionEnterResponseCode.InvalidCharacterIdError);
        }
示例#9
0
        public async Task <IActionResult> SaveFullCharacterDataAsync([FromRoute(Name = "id")] int characterId,
                                                                     [FromBody][NotNull] FullCharacterDataSaveRequest request,
                                                                     [NotNull][FromServices] ICharacterLocationRepository locationRepository,
                                                                     [NotNull][FromServices] IZoneServerRepository zoneRepository,
                                                                     [FromServices][NotNull] ICharacterDataRepository characterDataRepository)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }
            if (characterId <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(characterId));
            }

            //TODO: Is it safe to not do this as a transaction??
            //TODO: For HTTP callers we should maybe include better information. Though with message queue we can't respond.
            if (!await CheckZoneAuthorizedToModifyCharacterData(characterId))
            {
                return(Forbid());
            }

            //Don't always want to save the position of the user.
            if (request.isPositionSaved)
            {
                await UpdateCharacterLocation(characterId, request.CharacterLocationData, locationRepository, zoneRepository);
            }

            //TODO: Probably need to handle this abit better and more data than just experience.
            //Entity data can now be saved.
            await UpdatePlayerData(characterId, new CharacterDataInstance(request.PlayerDataSnapshot.GetFieldValue <int>(PlayerObjectField.PLAYER_TOTAL_EXPERIENCE)), characterDataRepository);

            if (request.ShouldReleaseCharacterSession)
            {
                await CharacterSessionRepository.TryDeleteClaimedSession(characterId);
            }

            return(Ok());
        }
示例#10
0
        public static async Task Test_Controller_Produces_SessionGranted_With_Zone_Id_If_UnclaimedSession_Exists(int accountId, int zoneid)
        {
            //arrange
            IServiceProvider            serviceProvider = ControllerTestsHelpers.BuildServiceProvider <CharacterSessionController>("Test", accountId);
            CharacterSessionController  controller      = serviceProvider.GetService <CharacterSessionController>();
            ICharacterRepository        characterRepo   = serviceProvider.GetService <ICharacterRepository>();
            ICharacterSessionRepository sessionRepo     = serviceProvider.GetService <ICharacterSessionRepository>();

            ICharacterLocationRepository characterLocationRepo = serviceProvider.GetService <ICharacterLocationRepository>();
            IZoneServerRepository        zoneRepository        = serviceProvider.GetService <IZoneServerRepository>();

            await characterRepo.TryCreateAsync(new CharacterEntryModel(accountId, "Testing"));

            await sessionRepo.TryCreateAsync(new CharacterSessionModel(1, zoneid));

            //act
            CharacterSessionEnterResponse response = await controller.EnterSession(1, characterLocationRepo, zoneRepository);

            //assert
            Assert.True(response.isSuccessful, $"Created sessions should be granted if no active account session or character session is claimed.");
            Assert.AreEqual(CharacterSessionEnterResponseCode.Success, response.ResultCode);
            Assert.AreEqual(zoneid, response.ZoneId, $"Provided zone id was not the same as the session.");
        }
        //[AuthorizeJwt(GuardianApplicationRole.ZoneServer)] //TODO: Eventually we'll need to auth these zoneservers.
        public async Task <IActionResult> RegisterZoneServer([FromBody] ZoneServerRegisterationRequest registerationRequest,
                                                             [FromServices] IZoneInstanceWorkQueue instanceWorkQueue, [FromServices] IZoneServerRepository zoneRepo)
        {
            //TODO: JSON
            if (!ModelState.IsValid)
            {
                return(BadRequest());
            }

            //Conceptually, just because there is no work doesn't mean this is an error
            //Requesting users who were trying to make an instance could have abandoned that request.
            //Instances may eventually free themselves after inactivity and attempt to reregister instead of shutting down at first
            //So we just want to say "Nothing to do right now" so they can sleep and maybe manually shutdown after a timeout period.
            if (instanceWorkQueue.isEmpty)
            {
                return(NoWorkForInstanceResponse());
            }

            //The specification says this could complete immediately
            //with null if no works exists, there is technically a data race condition between checking isEmpty
            //and trying to dequeue so the result may not be predictible.
            ZoneInstanceWorkEntry zoneInstanceWorkEntry = await instanceWorkQueue.DequeueAsync()
                                                          .ConfigureAwait(false);

            //TODO: If anything here fails after dequeueing we could lose CRITICAL data to keep things running
            //We need VERY good failure handling, and to reenter this work request into the queue somehow.
            //Otherwise the request for the instance will be lost and unhandled forever.
            ProjectVersionStage.AssertAlpha();

            if (zoneInstanceWorkEntry == null)
            {
                return(NoWorkForInstanceResponse());
            }

            //TODO: Validate endpoint
            //Since there IS work to do, we can't just tell the zone instance
            //We must register it into the zone server repo
            if (!await zoneRepo.TryCreateAsync(new ZoneInstanceEntryModel(registerationRequest.ZoneServerEndpoint.EndpointAddress, (short)registerationRequest.ZoneServerEndpoint.EndpointPort, zoneInstanceWorkEntry.WorldId)))
            {
                //As stated above, we need good handling for this else
                //we will encounter MAJOR issues.
                return(NoWorkForInstanceResponse());
            }

            //Success
            return(Ok(new ZoneServerRegisterationResponse(zoneInstanceWorkEntry.WorldId)));
        }
示例#12
0
		public async Task<CharacterSessionEnterResponse> EnterSession([FromRoute(Name = "id")] int characterId,
			[FromServices] ICharacterLocationRepository characterLocationRepository,
			[FromServices] IZoneServerRepository zoneServerRepository)
		{
			if(!await IsCharacterIdValidForUser(characterId, CharacterRepository))
				return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.InvalidCharacterIdError);

			int accountId = ClaimsReader.GetAccountIdInt(User);

			//This checks to see if the account, not just the character, has an active session.
			//We do this before we check anything to reject quick even though the query behind this
			//may be abit more expensive
			//As a note, this checks (or should) CLAIMED SESSIONS. So, it won't prevent multiple session entries for an account
			//This is good because we actually use the left over session data to re-enter the instances on disconnect.
			if(await CharacterSessionRepository.AccountHasActiveSession(accountId))
				return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.AccountAlreadyHasCharacterSession);

			//They may have a session entry already, which is ok. So long as they don't have an active claimed session
			//which the above query checks for.
			bool hasSession = await CharacterSessionRepository.ContainsAsync(characterId);

			//We need to check active or not
			if (hasSession)
			{
				//It's possible that the session no longer matches the character's
				//persisted location. We should check their location and put them in the correct zone.

				//If it's active we can just retrieve the data and send them off on their way
				CharacterSessionModel sessionModel = await CharacterSessionRepository.RetrieveAsync(characterId, true);

				if (await characterLocationRepository.ContainsAsync(characterId))
				{
					CharacterLocationModel locationModel = await characterLocationRepository.RetrieveAsync(characterId);
					//They have a location, verify it matches the session
					if (locationModel.WorldId != sessionModel.ZoneEntry.WorldId)
					{
						//The location world and the session's world do not match, so remove the session.
						await CharacterSessionRepository.TryDeleteAsync(sessionModel.CharacterId);
					}
					else
						return new CharacterSessionEnterResponse(sessionModel.ZoneId);
				}
				else
					//TODO: Handle case when we have an inactive session that can be claimed
					return new CharacterSessionEnterResponse(sessionModel.ZoneId);
			}

			//If we didn't return above then we should be in a state where the below can handle this now.

			try
			{
				int targetSessionZoneId = 0;

				//TO know what zone we should connect to we need to check potential
				//character location.
				if (await characterLocationRepository.ContainsAsync(characterId))
				{
					CharacterLocationModel locationModel = await characterLocationRepository.RetrieveAsync(characterId);

					//We have no session so we need to find a zone that matches this server.
					ZoneInstanceEntryModel firstWithWorldId = await zoneServerRepository.FindFirstWithWorldId(locationModel.WorldId);

					//TODO: Should we request one be created for the user??
					//There is NO instance available for this world.
					if (firstWithWorldId == null)
					{
						//Location is basically invalid since there is no running world
						await characterLocationRepository.TryDeleteAsync(locationModel.CharacterId);
					}
					else
					{
						targetSessionZoneId = firstWithWorldId.ZoneId;
					}
				}

				//Try to get into any zone
				if (targetSessionZoneId == 0)
				{
					ZoneInstanceEntryModel entryModel = await zoneServerRepository.AnyAsync();

					if (entryModel != null)
						targetSessionZoneId = entryModel.ZoneId;
				}

				//Still zero means literally no zone servers are available.
				if(targetSessionZoneId == 0)
					return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.GeneralServerError);

				if(await CharacterSessionRepository.TryCreateAsync(new CharacterSessionModel(characterId, targetSessionZoneId)))
					return new CharacterSessionEnterResponse(targetSessionZoneId);
				else
					return new CharacterSessionEnterResponse(CharacterSessionEnterResponseCode.GeneralServerError);

			}
			catch (Exception e)
			{
				if(Logger.IsEnabled(LogLevel.Error))
					Logger.LogError($"Character with ID: {characterId} failed to create session. Potentially no default world assigned or World with session was deleted and orphaned. Reason: {e.Message}");
				throw;
			}
		}