libvwad: updated -- vwadwrite: free file buffers on close (otherwise archive creation...
[k8vavoom.git] / source / net / net_channel_object_map.cpp
blobdae8f1d5be5758701a5431f79d25acb7f9f01efa
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
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.
16 //**
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.
21 //**
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/>.
24 //**
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "../text.h"
28 #include "network.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 //==========================================================================
37 // hash2str
39 //==========================================================================
40 VVA_OKUNUSED static VStr hash2str (const vuint8 hash[K8VNET_DIGEST_SIZE]) {
41 const char *hex = "0123456789abcdef";
42 VStr res;
43 for (int f = 0; f < K8VNET_DIGEST_SIZE; ++f) {
44 res += hex[(hash[f]>>4)&0x0fu];
45 res += hex[hash[f]&0x0fu];
47 return res;
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)
61 , cprBufferSize(0)
62 , cprBufferPos(0)
63 , cprBuffer(nullptr)
64 , InitialDataDone(false)
65 , NextNameToSend(0)
67 // do it on channel creation, why not
68 if (Connection->IsServer()) CompressNames();
72 //==========================================================================
74 // VObjectMapChannel::~VObjectMapChannel
76 //==========================================================================
77 VObjectMapChannel::~VObjectMapChannel () {
78 ClearCprBuffer();
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 {
99 return
100 OutListBits >= 64000*8 ? -1 : // oversaturated
101 OutListBits >= 60000*8 ? 1 : // full
102 0; // ok
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) {
121 if (!cls) continue;
122 // class name
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()));
133 // return type
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));
141 // flags
142 crypto_blake2b_update(&hashctx, (const uint8_t *)&mt->ParamFlags, sizeof(mt->ParamFlags[0])*mt->NumParams);
143 // param types
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 () {
160 delete cprBuffer;
161 cprBuffer = nullptr;
162 cprBufferSize = cprBufferPos = 0;
166 //==========================================================================
168 // VObjectMapChannel::CompressNames
170 //==========================================================================
171 void VObjectMapChannel::CompressNames () {
172 ClearCprBuffer(); // just in case
174 // write to buffer
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++));
185 // compress buffer
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);
193 if (res != MZ_OK) {
194 GCon->Logf(NAME_DevNet, "%s: cannot compress names", *GetDebugName());
195 Connection->Close();
196 return;
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);
214 // decompress buffer
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());
221 Connection->Close();
222 return;
225 // read from buffer
226 char buf[NAME_SIZE+1];
227 int pos = 0;
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());
231 Connection->Close();
232 return;
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);
237 Connection->Close();
238 return;
240 if (pos+len > nameBuf.length()) {
241 GCon->Logf(NAME_DevNet, "%s: cannot decompress names", *GetDebugName());
242 Connection->Close();
243 return;
245 memcpy(buf, nameBuf.ptr()+pos, len);
246 buf[len] = 0;
247 pos += len;
249 VName Name(buf);
250 Connection->ObjMap->ReceivedName(f, Name);
253 if (pos != nameBuf.length()) {
254 GCon->Logf(NAME_DevNet, "%s: cannot decompress names", *GetDebugName());
255 Connection->Close();
256 return;
259 //ClearCprBuffer();
260 // completion signal
261 delete cprBuffer;
262 cprBuffer = nullptr;
263 CurrName = Connection->ObjMap->NameLookup.length();
267 //==========================================================================
269 // VObjectMapChannel::ReceivedCloseAck
271 // sets `ObjMapSent` flag
273 //==========================================================================
274 void VObjectMapChannel::ReceivedCloseAck () {
275 ClearCprBuffer();
276 if (Connection) Connection->ObjMapSent = true;
277 VChannel::ReceivedCloseAck(); // just in case
281 //==========================================================================
283 // VObjectMapChannel::Tick
285 //==========================================================================
286 void VObjectMapChannel::Tick () {
287 VChannel::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)
322 UpdateSendPBar();
323 return;
326 if (CurrClass == Connection->ObjMap->ClassLookup.length()) {
327 // everything has been sent
328 Close(); // just in case
329 return;
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);
337 UpdateSendPBar();
338 return;
341 VMessageOut outmsg(this);
342 // send counters in the first message
343 if (needOpenMessage) {
344 needOpenMessage = false;
345 outmsg.bOpen = true;
346 RNet_PBarReset();
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)) {
372 FlushMsg(&outmsg);
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
375 // is queue full?
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);
378 UpdateSendPBar();
379 return;
382 PutStream(&outmsg, strm);
383 ++cprBufferPos;
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)) {
392 FlushMsg(&outmsg);
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
395 // is queue full?
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);
398 UpdateSendPBar();
399 return;
402 PutStream(&outmsg, strm);
403 ++CurrClass;
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);
409 FlushMsg(&outmsg);
411 // nope, don't close, we'll transmit any new names here
412 //Close();
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);
420 // demo?
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);
447 ++NextNameToSend;
448 if (PutStream(&Msg, strm)) {
449 if (!CanSendData()) break; // stop right here
452 FlushMsg(&Msg);
456 //==========================================================================
458 // VObjectMapChannel::LiveParse
460 //==========================================================================
461 void VObjectMapChannel::LiveParse (VMessageIn &Msg) {
462 int nameAck = 0;
463 char buf[NAME_SIZE+1];
464 // got new name(s)
465 while (!Msg.AtEnd()) {
466 vint32 len = 0;
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);
470 Connection->Close();
471 return;
473 Msg.Serialise(buf, len);
474 buf[len] = 0;
475 Connection->ObjMap->ReceivedName(CurrName, VName(buf));
476 nameAck = CurrName++;
478 // send ack
479 if (nameAck) {
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()) {
498 vint32 NameAck = 0;
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());
502 Connection->Close();
503 return;
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
513 if (Msg.bClose) {
514 GCon->Logf(NAME_DevNet, "%s: remote closed object map channel, dropping the connection", *GetDebugName());
515 Connection->Close();
517 return;
520 if (InitialDataDone) { LiveParse(Msg); return; }
522 // read counters from opening message
523 if (Msg.bOpen) {
524 RNet_PBarReset();
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);
534 Connection->Close();
535 return;
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);
543 Connection->Close();
544 return;
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);
555 Connection->Close();
556 return;
558 cprBuffer = new vuint8[cprBufferSize];
561 if (cprBuffer) {
562 while (!Msg.AtEnd() && cprBufferPos < cprBufferSize) {
563 Msg << cprBuffer[cprBufferPos];
564 if (Msg.IsError()) {
565 GCon->Logf(NAME_Debug, "%s: error reading compressed data", *GetDebugName());
566 Connection->Close();
567 return;
569 ++cprBufferPos;
571 if (cprBufferPos == cprBufferSize) DecompressNames();
574 // read classes
575 while (!Msg.AtEnd() && CurrClass < Connection->ObjMap->ClassLookup.length()) {
576 VName Name;
577 Connection->ObjMap->SerialiseName(Msg, Name);
578 VClass *C = VMemberBase::StaticFindClass(Name);
579 vassert(C);
580 Connection->ObjMap->ClassLookup[CurrClass] = C;
581 Connection->ObjMap->ClassMap.put(C, CurrClass);
582 ++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());
596 Connection->Close();
597 Host_Error("invalid replication data hash (incompatible progs)");
599 Connection->ObjMapSent = true;
600 InitialDataDone = true;
601 CurrName = Connection->ObjMap->NameLookup.length();
603 // send ack
604 vint32 ackn = CurrName;
605 VMessageOut outmsg(this);
606 outmsg << STRM_INDEX(ackn);
607 SendMessage(&outmsg);