Cuberite
A lightweight, fast and extensible game server for Minecraft
ProtocolRecognizer.cpp
Go to the documentation of this file.
1 
2 // ProtocolRecognizer.cpp
3 
4 // Implements the cProtocolRecognizer class representing the meta-protocol that recognizes possibly multiple
5 // protocol versions and redirects everything to them
6 
7 #include "Globals.h"
8 
9 #include "ProtocolRecognizer.h"
10 #include "Protocol_1_8.h"
11 #include "Protocol_1_9.h"
12 #include "Protocol_1_10.h"
13 #include "Protocol_1_11.h"
14 #include "Protocol_1_12.h"
15 #include "Protocol_1_13.h"
16 #include "Protocol_1_14.h"
17 #include "../ClientHandle.h"
18 #include "../Root.h"
19 #include "../Server.h"
20 #include "../World.h"
21 #include "../JsonUtils.h"
22 #include "../Bindings/PluginManager.h"
23 
24 
25 
26 
27 
28 struct TriedToJoinWithUnsupportedProtocolException : public std::runtime_error
29 {
30  explicit TriedToJoinWithUnsupportedProtocolException(const std::string & a_Message) :
31  std::runtime_error(a_Message)
32  {
33  }
34 };
35 
36 
37 
38 
39 
41  m_Buffer(32 KiB),
42  m_WaitingForData(true)
43 {
44 }
45 
46 
47 
48 
49 
51 {
52  switch (a_ProtocolVersion)
53  {
54  case cProtocol::Version::v1_8_0: return "1.8";
55  case cProtocol::Version::v1_9_0: return "1.9";
56  case cProtocol::Version::v1_9_1: return "1.9.1";
57  case cProtocol::Version::v1_9_2: return "1.9.2";
58  case cProtocol::Version::v1_9_4: return "1.9.4";
59  case cProtocol::Version::v1_10_0: return "1.10";
60  case cProtocol::Version::v1_11_0: return "1.11";
61  case cProtocol::Version::v1_11_1: return "1.11.1";
62  case cProtocol::Version::v1_12: return "1.12";
63  case cProtocol::Version::v1_12_1: return "1.12.1";
64  case cProtocol::Version::v1_12_2: return "1.12.2";
65  case cProtocol::Version::v1_13: return "1.13";
66  case cProtocol::Version::v1_13_1: return "1.13.1";
67  case cProtocol::Version::v1_13_2: return "1.13.2";
68  case cProtocol::Version::v1_14: return "1.14";
69  case cProtocol::Version::v1_14_1: return "1.14.1";
70  case cProtocol::Version::v1_14_2: return "1.14.2";
71  case cProtocol::Version::v1_14_3: return "1.14.3";
72  case cProtocol::Version::v1_14_4: return "1.14.4";
73  }
74 
75  ASSERT(!"Unknown protocol version");
76  return fmt::format(FMT_STRING("Unknown protocol ({})"), a_ProtocolVersion);
77 }
78 
79 
80 
81 
82 
84 {
85  // NOTE: If a new protocol is added or an old one is removed, adjust MCS_CLIENT_VERSIONS and MCS_PROTOCOL_VERSIONS macros in the header file
86 
87  /* Write all incoming data unmodified into m_Buffer.
88  Writing everything is always okay to do:
89  1. We can be sure protocol encryption hasn't started yet since m_Protocol hasn't been called, hence no decryption needs to take place
90  2. The extra data are processed at the end of this function */
91  if (!m_Buffer.Write(a_Data.data(), a_Data.size()))
92  {
93  a_Client.PacketBufferFull();
94  return;
95  }
96 
97  if (TryHandleHTTPRequest(a_Client, a_Data))
98  {
99  return;
100  }
101 
102  // TODO: recover from git history
103  // Unlengthed protocol, ...
104 
105  // Lengthed protocol, try if it has the entire initial handshake packet:
106  if (
107  UInt32 PacketLen;
108 
109  // If not enough bytes for the packet length, keep waiting
110  !m_Buffer.ReadVarInt(PacketLen) ||
111 
112  // If not enough bytes for the packet, keep waiting
113  // (More of a sanity check to make sure no one tries anything funny, since ReadXXX can wait for data themselves)
114  !m_Buffer.CanReadBytes(PacketLen)
115  )
116  {
118  return;
119  }
120 
121  /* Figure out the client's version.
122  1. m_Protocol != nullptr: the protocol is supported and we have a handler
123  2. m_Protocol == nullptr: the protocol is unsupported, handling is a special case done by ourselves
124  3. Exception: the data sent were garbage, the client handle deals with it by disconnecting */
126 
127  // Version recognised. Cause HandleIncomingData to stop calling us to handle data:
128  m_WaitingForData = false;
129 
130  // Explicitly process any remaining data (already written to m_Buffer) with the new handler:
131  {
132  ContiguousByteBuffer Empty;
133  HandleIncomingData(a_Client, Empty);
134  }
135 }
136 
137 
138 
139 
140 
142 {
143  if (!m_Buffer.Write(a_Data.data(), a_Data.size()))
144  {
145  a_Client.PacketBufferFull();
146  return;
147  }
148 
149  // Handle server list ping packets
150  for (;;)
151  {
152  UInt32 PacketLen;
153  UInt32 PacketID;
154  if (
155  !m_Buffer.ReadVarInt32(PacketLen) ||
156  !m_Buffer.CanReadBytes(PacketLen) ||
157  !m_Buffer.ReadVarInt32(PacketID)
158  )
159  {
160  // Not enough data
162  break;
163  }
164 
165  if ((PacketID == 0x00) && (PacketLen == 1)) // Request packet
166  {
167  HandlePacketStatusRequest(a_Client);
168  }
169  else if ((PacketID == 0x01) && (PacketLen == 9)) // Ping packet
170  {
171  HandlePacketStatusPing(a_Client);
172  }
173  else
174  {
175  a_Client.PacketUnknown(PacketID);
176  return;
177  }
178 
180  }
181 }
182 
183 
184 
185 
186 
188 {
189  if (m_WaitingForData)
190  {
191  HandleIncomingDataInRecognitionStage(a_Client, a_Data);
192  }
193  else if (m_Protocol == nullptr)
194  {
195  // Got a Handshake for an unrecognised version, process future data accordingly:
197  }
198  else
199  {
200  // The protocol recogniser succesfully identified a supported version, direct data to that protocol:
201  m_Protocol->DataReceived(m_Buffer, a_Data);
202  }
203 }
204 
205 
206 
207 
208 
210 {
211  // Normally only the protocol sends data, so outgoing data are only present when m_Protocol != nullptr.
212  // However, for unrecognised protocols we send data too, and that's when m_Protocol == nullptr. Check to avoid crashing (GH #5260).
213 
214  if (m_Protocol != nullptr)
215  {
216  m_Protocol->DataPrepared(a_Data);
217  }
218 }
219 
220 
221 
222 
223 
225 {
226  if (m_Protocol != nullptr)
227  {
228  m_Protocol->SendDisconnect(a_Reason);
229  return;
230  }
231 
232  const AString Message = JsonUtils::SerializeSingleValueJsonObject("text", a_Reason);
233  const auto PacketID = GetPacketID(cProtocol::ePacketType::pktDisconnectDuringLogin);
234  cByteBuffer Out(
235  cByteBuffer::GetVarIntSize(PacketID) +
236  cByteBuffer::GetVarIntSize(static_cast<UInt32>(Message.size())) + Message.size()
237  );
238 
239  VERIFY(Out.WriteVarInt32(PacketID));
240  VERIFY(Out.WriteVarUTF8String(Message));
241  SendPacket(a_Client, Out);
242 }
243 
244 
245 
246 
247 
249 {
250  const auto RedirectUrl = cRoot::Get()->GetServer()->GetCustomRedirectUrl();
251 
252  if (RedirectUrl.empty())
253  {
254  return false;
255  }
256 
257  ContiguousByteBuffer Buffer;
258  m_Buffer.ReadSome(Buffer, 10U);
260 
261  // The request line, hastily decoded with the hope that it's encoded in US-ASCII.
262  const std::string_view Value(reinterpret_cast<const char *>(Buffer.data()), Buffer.size());
263 
264  if (Value == u8"GET / HTTP")
265  {
266  const auto Response = fmt::format(u8"HTTP/1.0 303 See Other\r\nLocation: {}\r\n\r\n", cRoot::Get()->GetServer()->GetCustomRedirectUrl());
267  a_Client.SendData({ reinterpret_cast<const std::byte *>(Response.data()), Response.size() });
268  a_Client.Destroy();
269  return true;
270  }
271 
272  return false;
273 }
274 
275 
276 
277 
278 
280 {
281  UInt32 PacketType;
282  UInt32 ProtocolVersion;
283  AString ServerAddress;
284  UInt16 ServerPort;
285  UInt32 NextStateValue;
286 
287  if (!m_Buffer.ReadVarInt(PacketType) || (PacketType != 0x00))
288  {
289  // Not an initial handshake packet, we don't know how to talk to them:
290  LOGD("Client \"%s\" uses an unsupported protocol (lengthed, initial packet %u)",
291  a_Client.GetIPString().c_str(), PacketType
292  );
293 
294  throw TriedToJoinWithUnsupportedProtocolException("Your client isn't supported.\nTry connecting with Minecraft " MCS_CLIENT_VERSIONS);
295  }
296 
297  if (
298  !m_Buffer.ReadVarInt(ProtocolVersion) ||
299  !m_Buffer.ReadVarUTF8String(ServerAddress) ||
300  !m_Buffer.ReadBEUInt16(ServerPort) ||
301  !m_Buffer.ReadVarInt(NextStateValue)
302  )
303  {
304  // TryRecognizeProtocol guarantees that we will have as much
305  // data to read as the client claims in the protocol length field:
306  throw TriedToJoinWithUnsupportedProtocolException("Incorrect amount of data received - hacked client?");
307  }
308 
309  const auto NextState = [NextStateValue]
310  {
311  switch (NextStateValue)
312  {
313  case 1: return cProtocol::State::Status;
314  case 2: return cProtocol::State::Login;
315  case 3: return cProtocol::State::Game;
316  default: throw TriedToJoinWithUnsupportedProtocolException("Your client isn't supported.\nTry connecting with Minecraft " MCS_CLIENT_VERSIONS);
317  }
318  }();
319 
320  // TODO: this should be a protocol property, not ClientHandle:
321  a_Client.SetProtocolVersion(ProtocolVersion);
322 
323  // All good, eat up the data:
325 
326  switch (ProtocolVersion)
327  {
328  case static_cast<UInt32>(cProtocol::Version::v1_8_0): return std::make_unique<cProtocol_1_8_0> (&a_Client, ServerAddress, NextState);
329  case static_cast<UInt32>(cProtocol::Version::v1_9_0): return std::make_unique<cProtocol_1_9_0> (&a_Client, ServerAddress, NextState);
330  case static_cast<UInt32>(cProtocol::Version::v1_9_1): return std::make_unique<cProtocol_1_9_1> (&a_Client, ServerAddress, NextState);
331  case static_cast<UInt32>(cProtocol::Version::v1_9_2): return std::make_unique<cProtocol_1_9_2> (&a_Client, ServerAddress, NextState);
332  case static_cast<UInt32>(cProtocol::Version::v1_9_4): return std::make_unique<cProtocol_1_9_4> (&a_Client, ServerAddress, NextState);
333  case static_cast<UInt32>(cProtocol::Version::v1_10_0): return std::make_unique<cProtocol_1_10_0>(&a_Client, ServerAddress, NextState);
334  case static_cast<UInt32>(cProtocol::Version::v1_11_0): return std::make_unique<cProtocol_1_11_0>(&a_Client, ServerAddress, NextState);
335  case static_cast<UInt32>(cProtocol::Version::v1_11_1): return std::make_unique<cProtocol_1_11_1>(&a_Client, ServerAddress, NextState);
336  case static_cast<UInt32>(cProtocol::Version::v1_12): return std::make_unique<cProtocol_1_12> (&a_Client, ServerAddress, NextState);
337  case static_cast<UInt32>(cProtocol::Version::v1_12_1): return std::make_unique<cProtocol_1_12_1>(&a_Client, ServerAddress, NextState);
338  case static_cast<UInt32>(cProtocol::Version::v1_12_2): return std::make_unique<cProtocol_1_12_2>(&a_Client, ServerAddress, NextState);
339  case static_cast<UInt32>(cProtocol::Version::v1_13): return std::make_unique<cProtocol_1_13> (&a_Client, ServerAddress, NextState);
340  case static_cast<UInt32>(cProtocol::Version::v1_13_1): return std::make_unique<cProtocol_1_13_1>(&a_Client, ServerAddress, NextState);
341  case static_cast<UInt32>(cProtocol::Version::v1_13_2): return std::make_unique<cProtocol_1_13_2>(&a_Client, ServerAddress, NextState);
342  case static_cast<UInt32>(cProtocol::Version::v1_14): return std::make_unique<cProtocol_1_14> (&a_Client, ServerAddress, NextState);
343  case static_cast<UInt32>(cProtocol::Version::v1_14_1): return std::make_unique<cProtocol_1_14_1>(&a_Client, ServerAddress, NextState);
344  case static_cast<UInt32>(cProtocol::Version::v1_14_2): return std::make_unique<cProtocol_1_14_2>(&a_Client, ServerAddress, NextState);
345  case static_cast<UInt32>(cProtocol::Version::v1_14_3): return std::make_unique<cProtocol_1_14_3>(&a_Client, ServerAddress, NextState);
346  case static_cast<UInt32>(cProtocol::Version::v1_14_4): return std::make_unique<cProtocol_1_14_4>(&a_Client, ServerAddress, NextState);
347 
348  default:
349  {
350  LOGD("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))",
351  a_Client.GetIPString(), ProtocolVersion, ProtocolVersion
352  );
353 
354  if (NextState != cProtocol::State::Status)
355  {
357  fmt::format(FMT_STRING("Unsupported protocol version {}.\nTry connecting with Minecraft {}"),
358  ProtocolVersion, MCS_CLIENT_VERSIONS)
359  );
360  }
361 
362  // No cProtocol can handle the client:
363  return nullptr;
364  }
365  }
366 }
367 
368 
369 
370 
371 
372 void cMultiVersionProtocol::SendPacket(cClientHandle & a_Client, cByteBuffer & a_OutPacketBuffer)
373 {
374  // Writes out the packet normally.
375  UInt32 PacketLen = static_cast<UInt32>(a_OutPacketBuffer.GetUsedSpace());
376  cByteBuffer OutPacketLenBuffer(cByteBuffer::GetVarIntSize(PacketLen));
377 
378  // Compression doesn't apply to this state, send raw data:
379  VERIFY(OutPacketLenBuffer.WriteVarInt32(PacketLen));
380  ContiguousByteBuffer LengthData;
381  OutPacketLenBuffer.ReadAll(LengthData);
382  a_Client.SendData(LengthData);
383 
384  // Send the packet's payload:
385  ContiguousByteBuffer PacketData;
386  a_OutPacketBuffer.ReadAll(PacketData);
387  a_OutPacketBuffer.CommitRead();
388  a_Client.SendData(PacketData);
389 }
390 
391 
392 
393 
394 
396 {
397  switch (a_PacketType)
398  {
399  case cProtocol::ePacketType::pktDisconnectDuringLogin: return 0x00;
400  case cProtocol::ePacketType::pktStatusResponse: return 0x00;
401  case cProtocol::ePacketType::pktPingResponse: return 0x01;
402  default:
403  {
404  ASSERT(!"GetPacketID() called for an unhandled packet");
405  return 0;
406  }
407  }
408 }
409 
410 
411 
412 
413 
415 {
416  cServer * Server = cRoot::Get()->GetServer();
417  AString ServerDescription = Server->GetDescription();
418  auto NumPlayers = static_cast<signed>(Server->GetNumPlayers());
419  auto MaxPlayers = static_cast<signed>(Server->GetMaxPlayers());
420  AString Favicon = Server->GetFaviconData();
421  cRoot::Get()->GetPluginManager()->CallHookServerPing(a_Client, ServerDescription, NumPlayers, MaxPlayers, Favicon);
422 
423  // Version:
424  Json::Value Version;
425  Version["name"] = "Cuberite " MCS_CLIENT_VERSIONS;
426  Version["protocol"] = MCS_LATEST_PROTOCOL_VERSION;
427 
428  // Players:
429  Json::Value Players;
430  Players["online"] = NumPlayers;
431  Players["max"] = MaxPlayers;
432  // TODO: Add "sample"
433 
434  // Description:
435  Json::Value Description;
436  Description["text"] = ServerDescription.c_str();
437 
438  // Create the response:
439  Json::Value ResponseValue;
440  ResponseValue["version"] = Version;
441  ResponseValue["players"] = Players;
442  ResponseValue["description"] = Description;
443  if (!Favicon.empty())
444  {
445  ResponseValue["favicon"] = "data:image/png;base64," + Favicon;
446  }
447  AString Response = JsonUtils::WriteFastString(ResponseValue);
448 
449  // Send the response in a packet:
450  cByteBuffer out(Response.size() + 12); // String + 2x VarInt + extra space for safety
451  VERIFY(out.WriteVarInt32(GetPacketID(cProtocol::ePacketType::pktStatusResponse)));
452  VERIFY(out.WriteVarUTF8String(Response));
453  SendPacket(a_Client, out);
454 }
455 
456 
457 
458 
459 
461 {
462  Int64 Timestamp;
463  if (!m_Buffer.ReadBEInt64(Timestamp))
464  {
465  return;
466  }
467 
468  // Send the ping response packet:
469  cByteBuffer out(16); // VarInt + Int64 + extra space for safety
470  VERIFY(out.WriteVarInt32(GetPacketID(cProtocol::ePacketType::pktPingResponse)));
471  VERIFY(out.WriteBEInt64(Timestamp));
472  SendPacket(a_Client, out);
473 }
#define VERIFY(x)
Definition: Globals.h:280
unsigned int UInt32
Definition: Globals.h:157
signed long long Int64
Definition: Globals.h:151
std::basic_string_view< std::byte > ContiguousByteBufferView
Definition: Globals.h:376
#define KiB
Allows arithmetic expressions like "32 KiB" (but consider using parenthesis around it,...
Definition: Globals.h:234
#define ASSERT(x)
Definition: Globals.h:276
unsigned short UInt16
Definition: Globals.h:158
std::basic_string< std::byte > ContiguousByteBuffer
Definition: Globals.h:375
#define LOGD
Definition: LoggerSimple.h:83
#define MCS_CLIENT_VERSIONS
#define MCS_LATEST_PROTOCOL_VERSION
std::string AString
Definition: StringUtils.h:11
AString SerializeSingleValueJsonObject(const AString &a_Key, const AString &a_Value)
Creates a Json string representing an object with the specified single value.
Definition: JsonUtils.cpp:47
AString WriteFastString(const Json::Value &a_Root)
Definition: JsonUtils.cpp:12
Definition: FastNBT.h:132
bool CallHookServerPing(cClientHandle &a_ClientHandle, AString &a_ServerDescription, int &a_OnlinePlayersCount, int &a_MaxPlayersCount, AString &a_Favicon)
An object that can store incoming bytes and lets its clients read the bytes sequentially The bytes ar...
Definition: ByteBuffer.h:32
bool ReadBEUInt16(UInt16 &a_Value)
Definition: ByteBuffer.cpp:299
size_t GetUsedSpace(void) const
Returns the number of bytes that are currently in the ringbuffer.
Definition: ByteBuffer.cpp:185
bool CanReadBytes(size_t a_Count) const
Returns true if the specified amount of bytes are available for reading.
Definition: ByteBuffer.cpp:235
void CommitRead(void)
Removes the bytes that have been read from the ringbuffer.
void ReadAll(ContiguousByteBuffer &a_Data)
Reads all available data into a_Data.
Definition: ByteBuffer.cpp:977
bool ReadBEInt64(Int64 &a_Value)
Definition: ByteBuffer.cpp:343
bool WriteBEInt64(Int64 a_Value)
Definition: ByteBuffer.cpp:681
bool ReadVarInt32(UInt32 &a_Value)
Definition: ByteBuffer.cpp:414
bool ReadVarInt(T &a_Value)
Reads VarInt, assigns it to anything that can be assigned from an UInt64 (unsigned short,...
Definition: ByteBuffer.h:88
bool WriteVarUTF8String(const AString &a_Value)
Definition: ByteBuffer.cpp:789
bool WriteVarInt32(UInt32 a_Value)
Definition: ByteBuffer.cpp:745
static size_t GetVarIntSize(UInt32 a_Value)
Gets the number of bytes that are needed to represent the given VarInt.
bool ReadVarUTF8String(AString &a_Value)
Definition: ByteBuffer.cpp:458
bool ReadSome(ContiguousByteBuffer &a_String, size_t a_Count)
Reads a_Count bytes into a_String; returns true if successful.
Definition: ByteBuffer.cpp:927
void ResetRead(void)
Restarts next reading operation at the start of the ringbuffer.
bool Write(const void *a_Bytes, size_t a_Count)
Writes the bytes specified to the ringbuffer.
Definition: ByteBuffer.cpp:111
void SetProtocolVersion(UInt32 a_ProtocolVersion)
Called by the protocol recognizer when the protocol version is known.
Definition: ClientHandle.h:419
void PacketUnknown(UInt32 a_PacketType)
const AString & GetIPString(void) const
Definition: ClientHandle.h:72
void Destroy(void)
void PacketBufferFull(void)
void SendData(ContiguousByteBufferView a_Data)
ePacketType
Logical types of outgoing packets.
Definition: Protocol.h:57
Version
The protocol version number, received from the client in the Handshake packet.
Definition: Protocol.h:335
TriedToJoinWithUnsupportedProtocolException(const std::string &a_Message)
bool TryHandleHTTPRequest(cClientHandle &a_Client, ContiguousByteBuffer &a_Data)
cByteBuffer m_Buffer
Buffer for received protocol data.
void HandlePacketStatusPing(cClientHandle &a_Client)
void HandleOutgoingData(ContiguousByteBuffer &a_Data)
Allows the protocol (if any) to do a final pass on outgiong data, possibly modifying the provided buf...
void HandleIncomingData(cClientHandle &a_Client, ContiguousByteBuffer &a_Data)
Directs incoming protocol data along the correct pathway, depending on the state of the version recog...
bool m_WaitingForData
If we're still waiting for data required for version recognition to arrive.
void HandleIncomingDataInOldPingResponseStage(cClientHandle &a_Client, ContiguousByteBufferView a_Data)
Handles and responds to unsupported clients sending pings.
static UInt32 GetPacketID(cProtocol::ePacketType a_PacketType)
Returns the protocol-specific packet ID given the protocol-agnostic packet enum.
static void SendPacket(cClientHandle &a_Client, cByteBuffer &a_OutPacketBuffer)
Sends one packet inside a cByteBuffer.
void SendDisconnect(cClientHandle &a_Client, const AString &a_Reason)
Sends a disconnect to the client as a result of a recognition error.
void HandlePacketStatusRequest(cClientHandle &a_Client)
static AString GetVersionTextFromInt(cProtocol::Version a_ProtocolVersion)
Translates protocol version number into protocol version text: 49 -> "1.4.4".
std::unique_ptr< cProtocol > m_Protocol
The actual protocol implementation.
std::unique_ptr< cProtocol > TryRecognizeLengthedProtocol(cClientHandle &a_Client)
Tries to recognize a protocol in the lengthed family (1.7+), based on m_Buffer.
void HandleIncomingDataInRecognitionStage(cClientHandle &a_Client, ContiguousByteBuffer &a_Data)
Handles data reception in a newly-created client handle that doesn't yet have a known protocol.
cServer * GetServer(void)
Definition: Root.h:71
static cRoot * Get()
Definition: Root.h:52
cPluginManager * GetPluginManager(void)
Definition: Root.h:111
Definition: Server.h:56
size_t GetMaxPlayers(void) const
Definition: Server.h:70
const AString & GetDescription(void) const
Definition: Server.h:65
std::string_view GetCustomRedirectUrl(void)
Definition: Server.h:102
size_t GetNumPlayers(void) const
Definition: Server.h:71
const AString & GetFaviconData(void) const
Returns base64 encoded favicon data (obtained from favicon.png)
Definition: Server.h:141