diff --git a/dCommon/dEnums/MessageType/Master.h b/dCommon/dEnums/MessageType/Master.h index 4aca357de..953215a5c 100644 --- a/dCommon/dEnums/MessageType/Master.h +++ b/dCommon/dEnums/MessageType/Master.h @@ -102,5 +102,8 @@ namespace MessageType { WORLD_RELOAD, // Master -> dashboard: each running zone's files, whether they changed on disk, and which instances are stale WORLD_FILES_STATUS, + + // Dashboard -> master: what the update check found about this build (see UpdateStatus.h), for master's log + UPDATE_STATUS, }; } diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 696d58e0c..ab1abb679 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -63,6 +63,7 @@ #include "CDClientReloader.h" #include "WorldReloader.h" #include "master/WorldFiles.h" +#include "master/UpdateStatus.h" #include "BuildInfo.h" #ifdef DARKFLAME_PLATFORM_UNIX @@ -1211,6 +1212,14 @@ namespace { Game::lastSignal = -1; } + // The dashboard's update check: logged when what it says changes (so the first one after master starts, and news) + void OnUpdateStatus(const UpdateStatus& status, const SystemAddress& sysAddr) { + static std::string lastSummary; + if (sysAddr != dashboardServerMasterPeerSysAddr || status.summary.empty() || status.summary == lastSummary) return; + lastSummary = status.summary; + LOG("%s", status.summary.c_str()); + } + void OnMessageCaptureControl(const MessageCaptureControl& control, const SystemAddress& sysAddr) { // Only the dashboard starts message captures; every world gets it, and the one with the player acts on it if (sysAddr != dashboardServerMasterPeerSysAddr) { @@ -1270,6 +1279,7 @@ namespace { handlers.On(Master::CONFIG_RELOAD, OnConfigReload); handlers.On(Master::INSTANCE_SHUTDOWN, OnInstanceShutdown); handlers.On(Master::DASHBOARD_SHUTDOWN, OnDashboardShutdown); + handlers.On(Master::UPDATE_STATUS, OnUpdateStatus); // Only world servers report game writes; pass them on unchanged handlers.On(Master::DATA_CHANGED, ForwardWorldToDashboard); handlers.On(Master::MESSAGE_CAPTURE_CONTROL, OnMessageCaptureControl); diff --git a/dNet/master/UpdateStatus.h b/dNet/master/UpdateStatus.h new file mode 100644 index 000000000..b1b6a6954 --- /dev/null +++ b/dNet/master/UpdateStatus.h @@ -0,0 +1,35 @@ +#ifndef __UPDATESTATUS__H__ +#define __UPDATESTATUS__H__ + +#include +#include + +#include "BitStream.h" +#include "BitStreamUtils.h" +#include "DashboardMessages.h" +#include "MessageType/Master.h" + +/** + * UPDATE_STATUS (dashboard -> master): what the dashboard's update check found, sent after each check and when the + * link to master comes up, so master's log says when a newer release or newer commits are out. Nothing is updated. + */ +struct UpdateStatus : public LUBitStream { + UpdateStatus() : LUBitStream(ServiceType::MASTER, MessageType::Master::UPDATE_STATUS) {} + + static constexpr uint16_t MAX_SUMMARY = 1024; + + // UpdateCheck::eState: 0 not checked, 1 up to date, 2 update available, 3 the check failed + uint8_t state{}; + std::string summary; // one line for the log + + void Serialize(RakNet::BitStream& stream) const override { + stream.Write(state); + DashboardMessages::WriteText(stream, summary, MAX_SUMMARY); + } + + bool Deserialize(RakNet::BitStream& stream) override { + return stream.Read(state) && DashboardMessages::ReadText(stream, summary, MAX_SUMMARY); + } +}; + +#endif //!__UPDATESTATUS__H__ diff --git a/tests/dCommonTests/MessageIdPinTests.cpp b/tests/dCommonTests/MessageIdPinTests.cpp index 0a041e65d..a52218b31 100644 --- a/tests/dCommonTests/MessageIdPinTests.cpp +++ b/tests/dCommonTests/MessageIdPinTests.cpp @@ -1816,6 +1816,7 @@ static_assert(static_cast(MessageType::Master::CDCLIENT_RELOAD) == 46); static_assert(static_cast(MessageType::Master::WORLD_FILES) == 47); static_assert(static_cast(MessageType::Master::WORLD_RELOAD) == 48); static_assert(static_cast(MessageType::Master::WORLD_FILES_STATUS) == 49); +static_assert(static_cast(MessageType::Master::UPDATE_STATUS) == 50); // MessageType::Server: 3 enumerators static_assert(static_cast(MessageType::Server::VERSION_CONFIRM) == 0); diff --git a/tests/dGameTests/dNetTests/MasterPacketsTests.cpp b/tests/dGameTests/dNetTests/MasterPacketsTests.cpp index 0fe7d8fc9..cd80aa584 100644 --- a/tests/dGameTests/dNetTests/MasterPacketsTests.cpp +++ b/tests/dGameTests/dNetTests/MasterPacketsTests.cpp @@ -6,6 +6,7 @@ #include "master/WorldFiles.h" #include "master/MessageCapture.h" #include "master/PlayerAction.h" +#include "master/UpdateStatus.h" #include "PacketDispatcher.h" #include "PacketTestUtils.h" #include "Game.h" @@ -672,6 +673,31 @@ TEST(MasterPacketsTests, WorldFilesStatus) { EXPECT_EQ(read.zones, status.zones); } +TEST(MasterPacketsTests, UpdateStatus) { + UpdateStatus status; + status.state = 2; + status.summary = "Update available: release v3.1.0 (this server is running release v3.0.0)"; + ExpectHeaderThenSerialize(status, MessageType::Master::UPDATE_STATUS); + + RakNet::BitStream stream; + status.Serialize(stream); + UpdateStatus read; + ASSERT_TRUE(read.Deserialize(stream)); + EXPECT_EQ(read.state, 2); + EXPECT_EQ(read.summary, status.summary); + + // Longer than the cap: cut on the way out, refused on the way in + status.summary.assign(UpdateStatus::MAX_SUMMARY + 10, 'x'); + RakNet::BitStream cut; + status.Serialize(cut); + ASSERT_TRUE(read.Deserialize(cut)); + EXPECT_EQ(read.summary.size(), UpdateStatus::MAX_SUMMARY); + RakNet::BitStream tooLong; + tooLong.Write(1); + tooLong.Write(UpdateStatus::MAX_SUMMARY + 1); + EXPECT_FALSE(read.Deserialize(tooLong)); +} + // A master without the outdated flags (written after the endpoints) still reads: nothing is outdated TEST(MasterPacketsTests, ServerListWithoutOutdatedFlags) { MasterPackets::ServerListResponse response;