1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
26 #include "../gamedefs.h"
29 #include "net_message.h"
32 static VCvarB
net_debug_dump_chan_objmap("net_debug_dump_chan_objmap", false, "Dump objectmap communication?", CVAR_NoShadow
);
35 //==========================================================================
39 //==========================================================================
40 VVA_OKUNUSED
static VStr
hash2str (const vuint8 hash
[K8VNET_DIGEST_SIZE
]) {
41 const char *hex
= "0123456789abcdef";
43 for (int f
= 0; f
< K8VNET_DIGEST_SIZE
; ++f
) {
44 res
+= hex
[(hash
[f
]>>4)&0x0fu
];
45 res
+= hex
[hash
[f
]&0x0fu
];
51 //==========================================================================
53 // VObjectMapChannel::VObjectMapChannel
55 //==========================================================================
56 VObjectMapChannel::VObjectMapChannel (VNetConnection
*AConnection
, vint32 AIndex
, vuint8 AOpenedLocally
)
57 : VChannel(AConnection
, CHANNEL_ObjectMap
, AIndex
, AOpenedLocally
)
58 , CurrName(1) // NAME_None is implicit
59 , CurrClass(1) // `None` class is implicit
60 , needOpenMessage(true)
64 , InitialDataDone(false)
67 // do it on channel creation, why not
68 if (Connection
->IsServer()) CompressNames();
72 //==========================================================================
74 // VObjectMapChannel::~VObjectMapChannel
76 //==========================================================================
77 VObjectMapChannel::~VObjectMapChannel () {
79 if (Connection
) Connection
->ObjMapSent
= true; // why not?
83 //==========================================================================
85 // VObjectMapChannel::GetName
87 //==========================================================================
88 VStr
VObjectMapChannel::GetName () const noexcept
{
89 return VStr(va("ompchan #%d(%s)", Index
, GetTypeName()));
93 //==========================================================================
95 // VObjectMapChannel::IsQueueFull
97 //==========================================================================
98 int VObjectMapChannel::IsQueueFull () noexcept
{
100 OutListBits
>= 64000*8 ? -1 : // oversaturated
101 OutListBits
>= 60000*8 ? 1 : // full
106 //==========================================================================
108 // VObjectMapChannel::BuildNetFieldsHash
110 // build map of replicated fields and RPC methods.
111 // this is used to check if both sides have the same progs.
112 // it doesn't matter what exactly this thing contains, we only
113 // interested if it is equal. so we won't even send it over the net,
114 // we will only check its hash.
116 //==========================================================================
117 void VObjectMapChannel::BuildNetFieldsHash (vuint8 hash
[K8VNET_DIGEST_SIZE
]) {
118 crypto_blake2b_ctx hashctx
;
119 crypto_blake2b_init(&hashctx
, K8VNET_DIGEST_SIZE
);
120 for (auto &&cls
: Connection
->ObjMap
->ClassLookup
) {
123 crypto_blake2b_update(&hashctx
, (const uint8_t *)cls
->GetName(), strlen(cls
->GetName()));
124 // class replication field names and types
125 for (VField
*fld
= cls
->NetFields
; fld
; fld
= fld
->NextNetField
) {
126 crypto_blake2b_update(&hashctx
, (const uint8_t *)fld
->GetName(), strlen(fld
->GetName()));
127 VStr tp
= fld
->Type
.GetName();
128 crypto_blake2b_update(&hashctx
, (const uint8_t *)*tp
, tp
.length());
130 // class replication methods
131 for (VMethod
*mt
= cls
->NetMethods
; mt
; mt
= mt
->NextNetMethod
) {
132 crypto_blake2b_update(&hashctx
, (const uint8_t *)mt
->GetName(), strlen(mt
->GetName()));
135 VStr tp
= mt
->ReturnType
.GetName();
136 crypto_blake2b_update(&hashctx
, (const uint8_t *)*tp
, tp
.length());
138 // number of arguments
139 crypto_blake2b_update(&hashctx
, (const uint8_t *)&mt
->NumParams
, sizeof(mt
->NumParams
));
140 crypto_blake2b_update(&hashctx
, (const uint8_t *)&mt
->ParamsSize
, sizeof(mt
->ParamsSize
));
142 crypto_blake2b_update(&hashctx
, (const uint8_t *)&mt
->ParamFlags
, sizeof(mt
->ParamFlags
[0])*mt
->NumParams
);
144 for (int f
= 0; f
< mt
->NumParams
; ++f
) {
145 VStr tp
= mt
->ParamTypes
[f
].GetName();
146 crypto_blake2b_update(&hashctx
, (const uint8_t *)*tp
, tp
.length());
150 crypto_blake2b_final(&hashctx
, hash
);
154 //==========================================================================
156 // VObjectMapChannel::ClearCprBuffer
158 //==========================================================================
159 void VObjectMapChannel::ClearCprBuffer () {
162 cprBufferSize
= cprBufferPos
= 0;
166 //==========================================================================
168 // VObjectMapChannel::CompressNames
170 //==========================================================================
171 void VObjectMapChannel::CompressNames () {
172 ClearCprBuffer(); // just in case
175 const int nameCount
= Connection
->ObjMap
->NameLookup
.length();
176 TArray
<vuint8
> nameBuf
;
177 for (int f
= 1; f
< nameCount
; ++f
) {
178 const char *text
= *VName::CreateWithIndex(f
);
179 const int len
= VStr::Length(text
);
180 vassert(len
> 0 && len
<= NAME_SIZE
);
181 nameBuf
.append((vuint8
)len
);
182 while (*text
) nameBuf
.append((vuint8
)(*text
++));
186 mz_ulong resbufsz
= mz_compressBound((mz_ulong
)nameBuf
.length());
187 if (resbufsz
> 0x1fffffff) Sys_Error("too many names!");
188 if (!resbufsz
) resbufsz
= 1;
190 cprBuffer
= new vuint8
[(unsigned)resbufsz
];
191 mz_ulong destlen
= (mz_ulong
)resbufsz
;
192 int res
= mz_compress2((unsigned char *)cprBuffer
, &destlen
, (const unsigned char *)nameBuf
.ptr(), (mz_ulong
)nameBuf
.length(), 9);
194 GCon
->Logf(NAME_DevNet
, "%s: cannot compress names", *GetDebugName());
199 cprBufferSize
= (int)destlen
;
200 unpDataSize
= nameBuf
.length();
201 GCon
->Logf(NAME_DevNet
, "%s: compressed %d names from %d to %d", *GetDebugName(), nameCount
, unpDataSize
, cprBufferSize
);
205 //==========================================================================
207 // VObjectMapChannel::DecompressNames
209 //==========================================================================
210 void VObjectMapChannel::DecompressNames () {
211 if (unpDataSize
== 0) return; // nothing to do
212 vassert(unpDataSize
> 0);
215 TArray
<vuint8
> nameBuf
;
216 nameBuf
.setLength(unpDataSize
);
217 mz_ulong destlen
= unpDataSize
;
218 int res
= mz_uncompress((unsigned char *)nameBuf
.ptr(), &destlen
, (const unsigned char *)cprBuffer
, (mz_ulong
)cprBufferSize
);
219 if (res
!= MZ_OK
|| destlen
!= (unsigned)unpDataSize
) {
220 GCon
->Logf(NAME_DevNet
, "%s: cannot decompress names", *GetDebugName());
226 char buf
[NAME_SIZE
+1];
228 for (int f
= 1; f
< Connection
->ObjMap
->NameLookup
.length(); ++f
) {
229 if (pos
>= nameBuf
.length()) {
230 GCon
->Logf(NAME_DevNet
, "%s: cannot decompress names", *GetDebugName());
234 int len
= (int)nameBuf
[pos
++];
235 if (len
< 1 || len
> NAME_SIZE
) {
236 GCon
->Logf(NAME_Debug
, "%s: invalid name length (%d)", *GetDebugName(), len
);
240 if (pos
+len
> nameBuf
.length()) {
241 GCon
->Logf(NAME_DevNet
, "%s: cannot decompress names", *GetDebugName());
245 memcpy(buf
, nameBuf
.ptr()+pos
, len
);
250 Connection
->ObjMap
->ReceivedName(f
, Name
);
253 if (pos
!= nameBuf
.length()) {
254 GCon
->Logf(NAME_DevNet
, "%s: cannot decompress names", *GetDebugName());
263 CurrName
= Connection
->ObjMap
->NameLookup
.length();
267 //==========================================================================
269 // VObjectMapChannel::ReceivedCloseAck
271 // sets `ObjMapSent` flag
273 //==========================================================================
274 void VObjectMapChannel::ReceivedCloseAck () {
276 if (Connection
) Connection
->ObjMapSent
= true;
277 VChannel::ReceivedCloseAck(); // just in case
281 //==========================================================================
283 // VObjectMapChannel::Tick
285 //==========================================================================
286 void VObjectMapChannel::Tick () {
288 if (IsLocalChannel()) Update();
292 //==========================================================================
294 // VObjectMapChannel::UpdateSendPBar
296 //==========================================================================
297 void VObjectMapChannel::UpdateSendPBar () {
298 RNet_PBarUpdate("sending names and classes", cprBufferPos
+CurrClass
, cprBufferSize
+Connection
->ObjMap
->ClassLookup
.length());
302 //==========================================================================
304 // VObjectMapChannel::UpdateRecvPBar
306 //==========================================================================
307 void VObjectMapChannel::UpdateRecvPBar (bool forced
) {
308 RNet_PBarUpdate("loading names and classes", cprBufferPos
+CurrClass
, cprBufferSize
+Connection
->ObjMap
->ClassLookup
.length(), forced
);
312 //==========================================================================
314 // VObjectMapChannel::Update
316 //==========================================================================
317 void VObjectMapChannel::Update () {
318 if (InitialDataDone
) { LiveUpdate(); return; }
320 if (!OpenAcked
&& !needOpenMessage
) {
321 // nothing to do yet (we sent open message, and waiting for the ack)
326 if (CurrClass
== Connection
->ObjMap
->ClassLookup
.length()) {
327 // everything has been sent
328 Close(); // just in case
332 //GCon->Logf(NAME_DevNet, "%s:000: qbytes=%d; outbytes=%d; sum=%d", *GetDebugName(), Connection->SaturaDepth, Connection->Out.GetNumBytes(), Connection->SaturaDepth+Connection->Out.GetNumBytes());
334 // do not overflow queue
335 if (!CanSendData()) {
336 //GCon->Logf(NAME_DevNet, "%s:666: qbytes=%d; outbytes=%d; sum=%d; queued=%d (%d)", *GetDebugName(), Connection->SaturaDepth, Connection->Out.GetNumBytes(), Connection->SaturaDepth+Connection->Out.GetNumBytes(), GetSendQueueSize(), OutListBits);
341 VMessageOut
outmsg(this);
342 // send counters in the first message
343 if (needOpenMessage
) {
344 needOpenMessage
= false;
347 GCon
->Logf(NAME_DevNet
, "opened class/name channel for %s", *Connection
->GetAddress());
348 // write replication data hash
349 vuint8 hash
[K8VNET_DIGEST_SIZE
];
350 BuildNetFieldsHash(hash
);
351 outmsg
.Serialise(hash
, (int)sizeof(hash
));
352 //GCon->Logf(NAME_DevNet, "%s: rephash=%s", *GetDebugName(), *hash2str(hash));
353 // send number of names
354 vint32 NumNames
= Connection
->ObjMap
->NameLookup
.length();
355 outmsg
.WriteUInt((unsigned)NumNames
);
356 GCon
->Logf(NAME_DevNet
, "sending total %d names", NumNames
);
357 // send number of classes
358 vint32 NumClasses
= Connection
->ObjMap
->ClassLookup
.length();
359 outmsg
.WriteUInt((unsigned)NumClasses
);
360 GCon
->Logf(NAME_DevNet
, "sending total %d classes", NumClasses
);
361 outmsg
.WriteUInt((unsigned)cprBufferSize
);
362 outmsg
.WriteUInt((unsigned)unpDataSize
);
363 vassert(cprBufferPos
== 0);
366 VBitStreamWriter
strm(MAX_MSG_SIZE_BITS
+64, true); // allow expand, why not?
368 // send packed names while we have anything to send
369 while (cprBufferPos
< cprBufferSize
) {
370 strm
<< cprBuffer
[cprBufferPos
];
371 if (WillOverflowMsg(&outmsg
, strm
)) {
373 //if (net_debug_dump_chan_objmap) GCon->Logf(NAME_DevNet, " ...names: [%d/%d] (%d)", cprBufferPos, cprBufferSize, GetSendQueueSize());
374 if (!OpenAcked
) { UpdateSendPBar(); return; } // if not opened, don't spam with packets yet
376 if (!CanSendData()) {
377 //GCon->Logf(NAME_DevNet, "%s:000: qbytes=%d; outbytes=%d; sum=%d; queued=%d (%d)", *GetDebugName(), Connection->SaturaDepth, Connection->Out.GetNumBytes(), Connection->SaturaDepth+Connection->Out.GetNumBytes(), GetSendQueueSize(), OutListBits);
382 PutStream(&outmsg
, strm
);
386 // send classes while we have anything to send
387 while (CurrClass
< Connection
->ObjMap
->ClassLookup
.length()) {
388 VName Name
= Connection
->ObjMap
->ClassLookup
[CurrClass
]->GetVName();
389 Connection
->ObjMap
->SerialiseName(strm
, Name
);
390 // send message if this class will not fit
391 if (WillOverflowMsg(&outmsg
, strm
)) {
393 //if (net_debug_dump_chan_objmap) GCon->Logf(NAME_DevNet, " ...classes: [%d/%d] (%d)", CurrClass+1, Connection->ObjMap->ClassLookup.length(), GetSendQueueSize());
394 if (!OpenAcked
) { UpdateSendPBar(); return; } // if not opened, don't spam with packets yet
396 if (!CanSendData()) {
397 //GCon->Logf(NAME_DevNet, "%s:001: qbytes=%d; outbytes=%d; sum=%d; queued=%d (%d)", *GetDebugName(), Connection->SaturaDepth, Connection->Out.GetNumBytes(), Connection->SaturaDepth+Connection->Out.GetNumBytes(), GetSendQueueSize(), OutListBits);
402 PutStream(&outmsg
, strm
);
404 //if (net_debug_dump_chan_objmap) GCon->Logf(NAME_DevNet, " :class: [%d/%d]: <%s>", CurrClass, Connection->ObjMap->ClassLookup.length(), *Name);
407 // this is the last message
408 PutStream(&outmsg
, strm
);
411 // nope, don't close, we'll transmit any new names here
413 InitialDataDone
= true;
414 CurrName
= Connection
->ObjMap
->NameLookup
.length();
415 NextNameToSend
= CurrName
;
416 // now it will be waiting for the ack
418 GCon
->Logf(NAME_DevNet
, "done writing initial objects (%d) and names (%d)", CurrClass
, CurrName
);
421 if (Connection
->AutoAck
) {
422 Connection
->ObjMapSent
= true;
427 //==========================================================================
429 // VObjectMapChannel::LiveUpdate
431 //==========================================================================
432 void VObjectMapChannel::LiveUpdate () {
433 if (NextNameToSend
>= VName::GetNumNames()) return; // no new names
434 if (!CanSendData()) return; // not yet
436 // use bitstream and split it to the messages here
437 VMessageOut
Msg(this);
438 VBitStreamWriter
strm(MAX_MSG_SIZE_BITS
+64, false); // no expand
440 while (NextNameToSend
< VName::GetNumNames()) {
441 const char *text
= *VName::CreateWithIndex(NextNameToSend
);
442 const int len
= VStr::Length(text
);
443 if (net_debug_dump_chan_objmap
) GCon
->Logf(NAME_DevNet
, "%s: sending new name #%d (%s) (CurrName=%d; names=%d; known=%d)", *GetDebugName(), NextNameToSend
, text
, CurrName
, VName::GetNumNames(), Connection
->ObjMap
->NameLookup
.length());
444 vassert(len
> 0 && len
<= NAME_SIZE
);
445 strm
<< STRM_INDEX(len
);
446 strm
.Serialise((char *)text
, len
);
448 if (PutStream(&Msg
, strm
)) {
449 if (!CanSendData()) break; // stop right here
456 //==========================================================================
458 // VObjectMapChannel::LiveParse
460 //==========================================================================
461 void VObjectMapChannel::LiveParse (VMessageIn
&Msg
) {
463 char buf
[NAME_SIZE
+1];
465 while (!Msg
.AtEnd()) {
467 Msg
<< STRM_INDEX(len
);
468 if (Msg
.IsError() || len
< 1 || len
> NAME_SIZE
) {
469 GCon
->Logf(NAME_DevNet
, "%s: invalid remote name lengh (%d) or read error", *GetDebugName(), len
);
473 Msg
.Serialise(buf
, len
);
475 Connection
->ObjMap
->ReceivedName(CurrName
, VName(buf
));
476 nameAck
= CurrName
++;
480 VMessageOut
outmsg(this);
481 outmsg
<< STRM_INDEX(nameAck
);
482 SendMessage(&outmsg
);
483 if (net_debug_dump_chan_objmap
) GCon
->Logf(NAME_DevNet
, "%s: sent ack for name #%d", *GetDebugName(), nameAck
);
488 //==========================================================================
490 // VObjectMapChannel::ParseMessage
492 //==========================================================================
493 void VObjectMapChannel::ParseMessage (VMessageIn
&Msg
) {
494 // if this channel is opened locally, it is used to send data, and to receive acks
495 if (IsLocalChannel()) {
496 // this must be client ack message
497 while (!Msg
.AtEnd()) {
499 Msg
<< STRM_INDEX(NameAck
);
500 if (NameAck
> VName::GetNumNames()) {
501 GCon
->Logf(NAME_DevNet
, "%s: remote sent invalid name ack %d (max is %d), dropping the connection", *GetDebugName(), NameAck
, VName::GetNumNames());
505 if (net_debug_dump_chan_objmap
) GCon
->Logf(NAME_DevNet
, "%s: got ack for name #%d", *GetDebugName(), NameAck
);
506 // mark initial data completion
507 if (NameAck
>= CurrName
) Connection
->ObjMapSent
= true;
508 // internalise acked names
509 Connection
->ObjMap
->AckNameWithIndex(NameAck
);
510 CurrName
= NameAck
+1;
512 // remote cannot close the channel
514 GCon
->Logf(NAME_DevNet
, "%s: remote closed object map channel, dropping the connection", *GetDebugName());
520 if (InitialDataDone
) { LiveParse(Msg
); return; }
522 // read counters from opening message
525 RNet_OSDMsgShow("receiving names and classes");
527 // read replication data hash (it will be checked later)
528 Msg
.Serialise(serverReplicationHash
, (int)sizeof(serverReplicationHash
));
529 //GCon->Logf(NAME_DevNet, "%s: rephash=%s", *GetDebugName(), *hash2str(serverReplicationHash));
531 vint32 NumNames
= (int)Msg
.ReadUInt();
532 if (NumNames
< 0 || NumNames
> 1024*1024*32) {
533 GCon
->Logf(NAME_Debug
, "%s: invalid number of names (%d)", *GetDebugName(), NumNames
);
537 Connection
->ObjMap
->SetNumberOfKnownNames(NumNames
);
538 GCon
->Logf(NAME_DevNet
, "expecting %d names", NumNames
);
540 vint32 NumClasses
= (int)Msg
.ReadUInt();
541 if (NumClasses
< 0 || NumClasses
> 1024*1024) {
542 GCon
->Logf(NAME_Debug
, "%s: invalid number of classes (%d)", *GetDebugName(), NumClasses
);
546 Connection
->ObjMap
->ClassLookup
.setLength(NumClasses
);
547 GCon
->Logf(NAME_DevNet
, "expecting %d classes", NumClasses
);
549 ClearCprBuffer(); // just in case
550 cprBufferSize
= (int)Msg
.ReadUInt();
551 unpDataSize
= (int)Msg
.ReadUInt();
552 vassert(cprBufferPos
== 0);
553 if (cprBufferSize
<= 0 || unpDataSize
<= 0 || cprBufferSize
> 1024*1024*64 || unpDataSize
> 1024*1024*64) {
554 GCon
->Logf(NAME_Debug
, "%s: invalid packed data sizes (%d/%d)", *GetDebugName(), cprBufferSize
, unpDataSize
);
558 cprBuffer
= new vuint8
[cprBufferSize
];
562 while (!Msg
.AtEnd() && cprBufferPos
< cprBufferSize
) {
563 Msg
<< cprBuffer
[cprBufferPos
];
565 GCon
->Logf(NAME_Debug
, "%s: error reading compressed data", *GetDebugName());
571 if (cprBufferPos
== cprBufferSize
) DecompressNames();
575 while (!Msg
.AtEnd() && CurrClass
< Connection
->ObjMap
->ClassLookup
.length()) {
577 Connection
->ObjMap
->SerialiseName(Msg
, Name
);
578 VClass
*C
= VMemberBase::StaticFindClass(Name
);
580 Connection
->ObjMap
->ClassLookup
[CurrClass
] = C
;
581 Connection
->ObjMap
->ClassMap
.put(C
, CurrClass
);
583 //if (net_debug_dump_chan_objmap) GCon->Logf(NAME_DevNet, " :class: [%d/%d]: <%s : %s>", CurrClass, Connection->ObjMap->ClassLookup.length(), *Name, C->GetName());
586 UpdateRecvPBar(Msg
.bClose
);
588 if (CurrClass
!= Connection
->ObjMap
->ClassLookup
.length()) return;
590 GCon
->Logf(NAME_DevNet
, "%s: received initial names (%d) and classes (%d)", *GetDebugName(), CurrName
, CurrClass
);
591 // check replication data hash
592 vuint8 hash
[K8VNET_DIGEST_SIZE
];
593 BuildNetFieldsHash(hash
);
594 if (memcmp(hash
, serverReplicationHash
, K8VNET_DIGEST_SIZE
) != 0) {
595 GCon
->Logf(NAME_DevNet
, "%s: invalid replication data hash", *GetDebugName());
597 Host_Error("invalid replication data hash (incompatible progs)");
599 Connection
->ObjMapSent
= true;
600 InitialDataDone
= true;
601 CurrName
= Connection
->ObjMap
->NameLookup
.length();
604 vint32 ackn
= CurrName
;
605 VMessageOut
outmsg(this);
606 outmsg
<< STRM_INDEX(ackn
);
607 SendMessage(&outmsg
);