Skip to content

Commit f872032

Browse files
authored
Merge pull request #1931 from DevOpsOfChaos/sidequest/validate-troop-limit-command
Validate troop limit commands
2 parents d90bf9a + e317c43 commit f872032

4 files changed

Lines changed: 72 additions & 2 deletions

File tree

libs/s25main/GameCommands.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "gameTypes/PactTypes.h"
1818
#include "gameTypes/SettingsTypes.h"
1919
#include "gameTypes/TempleProductionMode.h"
20+
#include "gameData/MilitaryConsts.h"
2021
#include "s25util/Serializer.h"
2122
#include <cstdint>
2223
#include <utility>
@@ -25,6 +26,16 @@ class GameWorld;
2526

2627
namespace gc {
2728

29+
namespace detail {
30+
inline uint8_t popSoldierRank(Serializer& ser)
31+
{
32+
const auto rank = ser.PopUnsignedChar();
33+
if(rank > MAX_MILITARY_RANK)
34+
throw helpers::makeOutOfRange(rank, MAX_MILITARY_RANK);
35+
return rank;
36+
}
37+
} // namespace detail
38+
2839
/// Basisklasse für sämtliche GameCommands mit Koordinaten
2940
class Coords : public GameCommand
3041
{
@@ -220,7 +231,7 @@ class SetTroopLimit : public Coords
220231
: Coords(GCType::SetTroopLimit, pt), rank(rank), count(count)
221232
{}
222233
SetTroopLimit(Serializer& ser)
223-
: Coords(GCType::SetTroopLimit, ser), rank(ser.PopUnsignedChar()), count(ser.PopUnsignedInt())
234+
: Coords(GCType::SetTroopLimit, ser), rank(detail::popSoldierRank(ser)), count(ser.PopUnsignedInt())
224235
{}
225236

226237
public:
@@ -529,7 +540,7 @@ class ChangeReserve : public Coords
529540
: Coords(GCType::ChangeReserve, pt), rank(rank), count(count)
530541
{}
531542
ChangeReserve(Serializer& ser)
532-
: Coords(GCType::ChangeReserve, ser), rank(ser.PopUnsignedChar()), count(ser.PopUnsignedInt())
543+
: Coords(GCType::ChangeReserve, ser), rank(detail::popSoldierRank(ser)), count(ser.PopUnsignedInt())
533544
{}
534545

535546
public:

libs/s25main/buildings/nobMilitary.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,7 @@ unsigned nobMilitary::CalcRequiredNumTroops(FrontierDistance assumedFrontierDist
696696

697697
void nobMilitary::SetTroopLimit(const unsigned rank, const unsigned limit)
698698
{
699+
RTTR_Assert(rank < troop_limits.size());
699700
troop_limits[rank] = limit;
700701
RegulateTroops();
701702
}

tests/s25Main/integration/testAttacking.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,28 @@ BOOST_FIXTURE_TEST_CASE(StartAttack, AttackFixture<>)
418418
TestFailingAttack(gwv, milBld1Pos, attackSrc, 1u);
419419
}
420420

421+
BOOST_FIXTURE_TEST_CASE(TroopLimitCommandRejectsInvalidSerializedRank, AttackFixture<>)
422+
{
423+
std::array<unsigned, NUM_SOLDIER_RANKS> expectedLimits{};
424+
const auto firstLimit = rttr::test::randomValue(1u, 20u);
425+
const auto limitStep = rttr::test::randomValue(1u, 5u);
426+
// Use unique values to detect overwriting
427+
for(unsigned rank = 0; rank < expectedLimits.size(); ++rank)
428+
{
429+
expectedLimits[rank] = firstLimit + rank * limitStep;
430+
this->SetTroopLimit(milBld0Pos, rank, expectedLimits[rank]);
431+
}
432+
433+
for(unsigned rank = 0; rank < expectedLimits.size(); ++rank)
434+
BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(rank) == expectedLimits[rank]);
435+
436+
BOOST_REQUIRE_THROW(this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, expectedLimits.back() + limitStep),
437+
std::range_error);
438+
439+
for(unsigned rank = 0; rank < expectedLimits.size(); ++rank)
440+
BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(rank) == expectedLimits[rank]);
441+
}
442+
421443
BOOST_FIXTURE_TEST_CASE(ConquerBld, AttackFixture<>)
422444
{
423445
AddSoldiers(milBld0Pos, 1, 5);

tests/s25Main/integration/testGameCommands.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//
33
// SPDX-License-Identifier: GPL-2.0-or-later
44

5+
#include "GameCommands.h"
56
#include "GamePlayer.h"
67
#include "PointOutput.h"
78
#include "RttrForeachPt.h"
@@ -12,6 +13,8 @@
1213
#include "enum_cast.hpp"
1314
#include "factories/BuildingFactory.h"
1415
#include "figures/nofPassiveSoldier.h"
16+
#include "helpers/serializeEnums.h"
17+
#include "helpers/serializePoint.h"
1518
#include "postSystem/PostBox.h"
1619
#include "worldFixtures/WorldWithGCExecution.h"
1720
#include "worldFixtures/initGameRNG.hpp"
@@ -53,8 +56,41 @@ static void dummySuppressUnused(std::ostream& out)
5356
}
5457
// LCOV_EXCL_STOP
5558

59+
namespace {
60+
// Expose the protected constructors only to serialize the actual command format in this test,
61+
// rather than duplicating the production Serialize() implementation.
62+
class TestSetTroopLimit : public gc::SetTroopLimit
63+
{
64+
public:
65+
TestSetTroopLimit(const MapPoint pt, const uint8_t rank, const uint32_t count) : gc::SetTroopLimit(pt, rank, count)
66+
{}
67+
};
68+
69+
class TestChangeReserve : public gc::ChangeReserve
70+
{
71+
public:
72+
TestChangeReserve(const MapPoint pt, const uint8_t rank, const uint32_t count) : gc::ChangeReserve(pt, rank, count)
73+
{}
74+
};
75+
} // namespace
76+
5677
BOOST_AUTO_TEST_SUITE(GameCommandSuite)
5778

79+
BOOST_AUTO_TEST_CASE(SoldierRankCommandsRejectInvalidSerializedRank)
80+
{
81+
constexpr auto invalidRank = static_cast<uint8_t>(MAX_MILITARY_RANK + 1u);
82+
const auto pt = rttr::test::randomPoint<MapPoint>();
83+
const auto count = rttr::test::randomValue<unsigned>();
84+
85+
gc::Deserializer troopLimitCommand;
86+
TestSetTroopLimit(pt, invalidRank, count).Serialize(troopLimitCommand);
87+
BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(troopLimitCommand), std::range_error);
88+
89+
gc::Deserializer reserveCommand;
90+
TestChangeReserve(pt, invalidRank, count).Serialize(reserveCommand);
91+
BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(reserveCommand), std::range_error);
92+
}
93+
5894
BOOST_FIXTURE_TEST_CASE(PlaceFlagTest, WorldWithGCExecution2P)
5995
{
6096
(void)&dummySuppressUnused;

0 commit comments

Comments
 (0)