• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 21100551564

17 Jan 2026 08:40PM UTC coverage: 60.504% (+0.01%) from 60.492%
21100551564

Pull #857

github

turleypol
fixed scope
Pull Request #857: ClangTidy readability-else-after-return

837 of 1874 new or added lines in 151 files covered. (44.66%)

48 existing lines in 26 files now uncovered.

44445 of 73458 relevant lines covered (60.5%)

515341.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.71
/pol-core/pol/network/clientthread.cpp
1
#include "clientthread.h"
2

3
#include <errno.h>
4
#include <exception>
5
#include <iterator>
6
#include <stddef.h>
7
#include <string>
8

9
#include "../../bscript/bobject.h"
10
#include "../../clib/esignal.h"
11
#include "../../clib/fdump.h"
12
#include "../../clib/logfacility.h"
13
#include "../../clib/network/singlepoller.h"
14
#include "../../clib/passert.h"
15
#include "../../clib/spinlock.h"
16
#include "../../clib/stlutil.h"
17
#include "../../plib/systemstate.h"
18
#include "../accounts/account.h"
19
#include "../clib/network/sockets.h"
20
#include "../core.h"
21
#include "../crypt/cryptbase.h"
22
#include "../mobile/charactr.h"
23
#include "../polclock.h"
24
#include "../polsem.h"
25
#include "../schedule.h"
26
#include "../scrdef.h"
27
#include "../scrsched.h"
28
#include "../uoscrobj.h"
29
#include "../uworld.h"
30
#include "cgdata.h"  // This might not be needed if the client has a clear_gd() method
31
#include "client.h"
32
#include "msgfiltr.h"  // Client could also have a method client->is_msg_allowed(), for example. Then this is not needed here.
33
#include "msghandl.h"
34
#include "packethelper.h"
35
#include "packets.h"
36
#include "pktboth.h"
37
#include "pktbothid.h"
38
#include "pktdef.h"
39
#include "pktinid.h"
40
#include "proxyprotocol.h"
41

42

43
#define CLIENT_CHECKPOINT( x ) client->session()->checkpoint = x
44
#define SESSION_CHECKPOINT( x ) session->checkpoint = x
45

46
namespace Pol::Core
47
{
48
// This function below is defined (for now) in pol.cpp. That's ugly.
49
void call_chr_scripts( Mobile::Character* chr, const std::string& root_script_ecl,
50
                       const std::string& pkg_script_ecl, bool offline = false );
51

52

53
void report_weird_packet( Network::ThreadedClient* session,
54
                          const std::string& why );  // Defined below
55

56
void set_polling_timeouts( Clib::SinglePoller& poller, bool single_threaded_login )
17✔
57
{
58
  poller.set_timeout(
32✔
59
      single_threaded_login ? Plib::systemstate.config.loginserver_select_timeout_msecs : 2000 );
15✔
60
}
17✔
61

62
// Taking a reference to SinglePoller is ugly here. But io_step, io_loop and clientpoller will
63
// eventually move into the same class.
64
bool threadedclient_io_step( Network::ThreadedClient* session, Clib::SinglePoller& clientpoller,
214✔
65
                             int& nidle )
66
{
67
  SESSION_CHECKPOINT( 1 );
214✔
68
  if ( !clientpoller.prepare( session->have_queued_data() ) )
214✔
69
  {
70
    POLLOG_INFO( "Client#{}: ERROR - couldn't poll socket={}\n", session->myClient.instance_,
×
71
                 session->csocket );
×
72

73
    if ( session->csocket != INVALID_SOCKET )
×
74
      session->forceDisconnect();
×
75

76
    return false;
×
77
  }
78

79
  int res = 0;
214✔
80
  do
81
  {
82
    SESSION_CHECKPOINT( 2 );
214✔
83
    res = clientpoller.wait_for_events();
214✔
84
    SESSION_CHECKPOINT( 3 );
214✔
85
  } while ( res < 0 && !Clib::exit_signalled && socket_errno == SOCKET_ERRNO( EINTR ) );
214✔
86

87
  if ( res < 0 )
214✔
88
  {
89
    int sckerr = socket_errno;
×
90
    POLLOGLN( "Client#{}: select res={}, sckerr={}", session->myClient.instance_, res, sckerr );
×
91
    return false;
×
92
  }
93
  if ( res == 0 )
214✔
94
  {
95
    if ( session->myClient.should_check_idle() )
52✔
96
    {
97
      ++nidle;
52✔
98
      if ( nidle == 30 * Plib::systemstate.config.inactivity_warning_timeout )
52✔
99
      {
100
        SESSION_CHECKPOINT( 4 );
×
101
        PolLock lck;  // multithread
×
102
        session->myClient.warn_idle();
×
103
      }
×
104
      else if ( nidle == 30 * Plib::systemstate.config.inactivity_disconnect_timeout )
52✔
105
      {
106
        session->forceDisconnect();
×
107
      }
108
    }
109
  }
110

111
  SESSION_CHECKPOINT( 19 );
214✔
112
  if ( !session->isReallyConnected() )
214✔
113
    return false;
×
114

115
  if ( clientpoller.error() )
214✔
116
  {
117
    session->forceDisconnect();
×
118
    return false;
×
119
  }
120

121
  // region Speedhack
122
  if ( session->has_delayed_packets() )  // not empty then process the first packet
214✔
123
  {
124
    PolLock lck;  // multithread
×
125
    session->process_delayed_packets();
×
126
  }
×
127
  // endregion Speedhack
128

129
  if ( clientpoller.incoming() )
214✔
130
  {
131
    SESSION_CHECKPOINT( 6 );
162✔
132
    if ( process_data( session ) )
162✔
133
    {
134
      SESSION_CHECKPOINT( 17 );
154✔
135
      PolLock lck;
154✔
136

137
      // reset packet timer
138
      session->last_packet_at = polclock();
154✔
139
      if ( !check_inactivity( session ) )
154✔
140
      {
141
        nidle = 0;
111✔
142
        session->last_activity_at = polclock();
111✔
143
      }
144

145
      SESSION_CHECKPOINT( 7 );
154✔
146
      send_pulse();
154✔
147
      if ( TaskScheduler::is_dirty() )
154✔
148
        wake_tasks_thread();
×
149
    }
154✔
150
  }
151

152
  polclock_t polclock_now = polclock();
214✔
153
  if ( ( ( polclock_now - session->last_packet_at ) / POLCLOCKS_PER_SEC ) >= 120 )  // 2 mins
214✔
154
  {
155
    session->forceDisconnect();
×
156
    return false;
×
157
  }
158

159
  if ( session->have_queued_data() && clientpoller.writable() )
214✔
160
  {
161
    PolLock lck;
×
162
    SESSION_CHECKPOINT( 8 );
×
163
    session->send_queued_data();
×
164
  }
×
165
  SESSION_CHECKPOINT( 21 );
214✔
166

167
  return true;
214✔
168
}  // namespace Pol::Core
169

170
void threadedclient_io_loop( Network::ThreadedClient* session, bool login )
17✔
171
{
172
  int nidle = 0;
17✔
173
  session->last_packet_at = polclock();
17✔
174
  session->last_activity_at = polclock();
17✔
175

176
  Clib::SinglePoller clientpoller( session->csocket );
17✔
177
  set_polling_timeouts( clientpoller, login );
17✔
178

179
  while ( !Clib::exit_signalled && session->isReallyConnected() )
216✔
180
  {
181
    if ( !threadedclient_io_step( session, clientpoller, nidle ) || login )
214✔
182
      break;
15✔
183
  }
184
}
17✔
185

186
// Sleeps taking exit_signalled into account
187
void threadedclient_sleep_until( polclock_t when_logoff )
×
188
{
189
  while ( !Clib::exit_signalled )
×
190
  {
191
    if ( polclock() >= when_logoff )
×
192
      break;
×
193
    pol_sleep_ms( 2000 );  // min(2000, when_logoff - polclock()) ?
×
194
  }
195
}
×
196

197
void threadedclient_io_finalize( Network::ThreadedClient* session )
4✔
198
{
199
  int seconds_wait = 0;
4✔
200
  {
201
    SESSION_CHECKPOINT( 9 );
4✔
202
    PolLock lck;
4✔
203
    seconds_wait = session->myClient.on_close();
4✔
204
  }
4✔
205

206
  SESSION_CHECKPOINT( 10 );
4✔
207
  if ( seconds_wait > 0 )
4✔
208
  {
209
    polclock_t when_logoff = session->last_activity_at + seconds_wait * POLCLOCKS_PER_SEC;
×
210
    threadedclient_sleep_until( when_logoff );
×
211
  }
212

213
  SESSION_CHECKPOINT( 15 );
4✔
214
  if ( session->myClient.chr )
4✔
215
  {
216
    PolLock lck;
2✔
217
    session->myClient.on_logoff();
2✔
218
  }
2✔
219
}
4✔
220

221
bool client_io_thread( Network::Client* client, bool login )
17✔
222
{
223
  if ( !login && Plib::systemstate.config.loglevel >= 11 )
17✔
224
  {
225
    POLLOGLN( "Network::Client#{} i/o thread starting", client->instance_ );
2✔
226
  }
227

228
  CLIENT_CHECKPOINT( 0 );
17✔
229
  try
230
  {
231
    threadedclient_io_loop( client->session(), login );
17✔
232
  }
233
  catch ( std::string& str )
×
234
  {
235
    POLLOG_ERRORLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={})", client->instance_,
×
236
                    str, client->session()->checkpoint );
×
237
  }
×
238
  catch ( const char* msg )
×
239
  {
240
    POLLOG_ERRORLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={})", client->instance_,
×
241
                    msg, client->session()->checkpoint );
×
242
  }
×
243
  catch ( std::exception& ex )
×
244
  {
245
    POLLOG_ERRORLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={})", client->instance_,
×
246
                    ex.what(), client->session()->checkpoint );
×
247
  }
×
248
  CLIENT_CHECKPOINT( 20 );
17✔
249

250
  if ( login && client->isConnected() )
17✔
251
    return true;
13✔
252

253
  POLLOGLN( "Client#{} ({}): disconnected (account {})", client->instance_,
4✔
254
            client->ipaddrAsString(),
4✔
255
            ( ( client->acct != nullptr ) ? client->acct->name() : "unknown" ) );
4✔
256

257
  try
258
  {
259
    threadedclient_io_finalize( client->session() );
4✔
260
  }
261
  catch ( std::exception& ex )
×
262
  {
263
    POLLOGLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={})", client->instance_,
×
264
              ex.what(), client->session()->checkpoint );
×
265
  }
×
266

267
  // queue delete of client ptr see method doc for reason
268
  Core::networkManager.clientTransmit->QueueDelete( client );
4✔
269
  return false;
4✔
270
}
271

272
bool valid_message_length( Network::ThreadedClient* session, unsigned int length )
18✔
273
{
274
  if ( length > sizeof session->buffer )
18✔
275
  {
276
    handle_humongous_packet( session, session->message_length );
×
277
    return false;
×
278
  }
279
  if ( length < 3 )
18✔
280
  {
281
    report_weird_packet( session, "Too-short message" );
×
282
    return false;
×
283
  }
284
  return true;
18✔
285
}
286

287
// bool - return true when a message was processed.
288
bool process_data( Network::ThreadedClient* session )
162✔
289
{
290
  // NOTE: This is coded such that for normal messages, which are completely available,
291
  // this function will get the type, then the length, then the data, without having
292
  // to wait for a second or third call.
293
  // Also, the abnormal state, RECV_STATE_CRYPTSEED_WAIT, is handled at the end, so in
294
  // normal processing its code doesn't see the code path.
295
  passert( session->bufcheck1_AA == 0xAA );
162✔
296
  passert( session->bufcheck2_55 == 0x55 );
162✔
297
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT )
162✔
298
  {
299
    session->bytes_received = 0;
158✔
300
    session->recv_remaining( 1 );
158✔
301
    SESSION_CHECKPOINT( 22 );
158✔
302
    if ( session->bytes_received < 1 )  // this really should never happen.
158✔
303
    {
304
      session->forceDisconnect();
4✔
305
      return false;
4✔
306
    }
307

308
    unsigned char msgtype = session->buffer[0];
154✔
309
    session->last_msgtype = msgtype;  // CNXBUG
154✔
310
    if ( Plib::systemstate.config.verbose )
154✔
311
      INFO_PRINTLN( "Incoming msg type: {:#x}", (int)msgtype );
×
312

313
    if ( !Network::PacketRegistry::is_defined( msgtype ) )
154✔
314
    {
315
      handle_undefined_packet( session );
×
316
      if ( !session->myClient.chr && Plib::systemstate.config.loginserver_disconnect_unknown_pkts )
×
317
        session->forceDisconnect();
×
318
      return false;  // remain in RECV_STATE_MSGTYPE_WAIT
×
319
    }
320
    // during login if it should disconnect no need to receive the data before killing the
321
    // connection
322
    if ( !session->myClient.chr && Plib::systemstate.config.loginserver_disconnect_unknown_pkts &&
154✔
323
         !session->msgtype_filter->msgtype_allowed[msgtype] )
8✔
324
    {
325
      POLLOG_ERRORLN( "Client#{} ({}, Acct {}) sent non-allowed message type {:#x}.",
×
326
                      session->myClient.instance_, session->ipaddrAsString(),
×
327
                      ( session->myClient.acct ? session->myClient.acct->name() : "unknown" ),
×
328
                      (int)msgtype );
×
329
      session->forceDisconnect();
×
330
      return false;
×
331
    }
332

333
    Network::MSG_HANDLER packetHandler =
334
        Network::PacketRegistry::find_handler( msgtype, &session->myClient );
154✔
335
    if ( packetHandler.msglen == MSGLEN_2BYTELEN_DATA )
154✔
336
    {
337
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGLEN_WAIT;
18✔
338
    }
339
    else
340
    {
341
      passert( packetHandler.msglen > 0 );
136✔
342

343
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGDATA_WAIT;
136✔
344
      session->message_length = packetHandler.msglen;
136✔
345
    }
346

347
  } /* endif of RECV_STATE_MSGTYPE_WAIT */
348

349
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_MSGLEN_WAIT )
158✔
350
  {
351
    session->recv_remaining( 3 );
18✔
352
    SESSION_CHECKPOINT( 23 );
18✔
353
    if ( session->bytes_received == 3 )  // the length bytes were available.
18✔
354
    {
355
      // MSG is [MSGTYPE] [LENHI] [LENLO] [DATA ... ]
356
      session->message_length = ( session->buffer[1] << 8 ) + session->buffer[2];
18✔
357

358
      if ( !valid_message_length( session, session->message_length ) )
18✔
359
      {
360
        // If the reported length is too short (less than 3 bytes) or
361
        // too big (larger than the client buffer), something very odd
362
        // happened.
363
        session->forceDisconnect();
×
364
        return false;
×
365
      }
366
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGDATA_WAIT;
18✔
367
    }
368
    // else keep waiting.
369
  } /* endif of RECV_STATE_MSGLEN_WAIT */
370

371
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_MSGDATA_WAIT )
158✔
372
  {
373
    SESSION_CHECKPOINT( 24 );
154✔
374
    session->recv_remaining( session->message_length );
154✔
375
    SESSION_CHECKPOINT( 25 );
154✔
376
    if ( session->bytes_received == session->message_length )  // we have the whole message
154✔
377
    {
378
      unsigned char msgtype = session->buffer[0];
154✔
379
      networkManager.iostats.received[msgtype].count++;
154✔
380
      networkManager.iostats.received[msgtype].bytes += session->message_length;
154✔
381
      {
382
        // Consider if the client should do the logging via client->log_incoming(...) instead. Also,
383
        // can we avoid the SpinLock here?
384
        Clib::SpinLockGuard guard( session->_fpLog_lock );
154✔
385
        if ( !session->fpLog.empty() )
154✔
386
        {
387
          std::string tmp = fmt::format( "Client -> Server: {:#x}, {} bytes\n", msgtype,
388
                                         session->message_length );
×
389
          Clib::fdump( std::back_inserter( tmp ), &session->buffer, session->message_length );
×
390
          FLEXLOGLN( session->fpLog, tmp );
×
391
        }
×
392
      }
154✔
393

394
      if ( Plib::systemstate.config.verbose )
154✔
395
        INFO_PRINTLN( "Message Received: Type {:#x}, Length {} bytes", (int)msgtype,
×
396
                      session->message_length );
×
397

398
      if ( session->msgtype_filter->msgtype_allowed[msgtype] )
154✔
399
      {
400
        PolLock lck;  // multithread
154✔
401
        // it can happen that a client gets disconnected while waiting for the lock.
402
        if ( !session->isConnected() )
154✔
403
          return false;
×
404

405
        // region Speedhack
406
        if ( ( settingsManager.ssopt.speedhack_prevention ) && ( msgtype == PKTIN_02_ID ) )
154✔
407
        {
408
          if ( !session->myClient.SpeedHackPrevention() )
×
409
          {
410
            // client->SpeedHackPrevention() added packet to queue
411
            session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
×
412
            SESSION_CHECKPOINT( 28 );
×
413
            return true;
×
414
          }
415
        }
416
        // endregion Speedhack
417

418
        session->myClient.handle_msg( session->buffer, session->bytes_received );
154✔
419
        session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
154✔
420
        SESSION_CHECKPOINT( 28 );
154✔
421
        return true;
154✔
422
      }
154✔
423

424
      // Such combinations of instance and acct happen quite often. Maybe this should become
425
      // Client->full_id() or something.
NEW
426
      POLLOG_ERRORLN( "Client#{} ({}, Acct {}) sent non-allowed message type {:#x}.",
×
NEW
427
                      session->myClient.instance_, session->ipaddrAsString(),
×
NEW
428
                      ( session->myClient.acct ? session->myClient.acct->name() : "unknown" ),
×
NEW
429
                      (int)msgtype );
×
430

431
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
×
432
      SESSION_CHECKPOINT( 28 );
×
433
      return false;
×
434
    }
435
    // else keep waiting
436
  } /* endif RECV_STATE_MSGDATA_WAIT */
437
  else if ( session->recv_state == Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT )
4✔
438
  {  // The abnormal case.
439
    // The first four bytes after connection are the
440
    // crypto seed
441
    session->recv_remaining_nocrypt( 4 );
4✔
442

443
    if ( session->bytes_received == 4 )
4✔
444
    {
445
      /* The first four bytes transmitted are the encryption seed */
446
      unsigned char cstype = session->buffer[0];
4✔
447

448
      if ( ( session->buffer[0] == 0xff ) && ( session->buffer[1] == 0xff ) &&
4✔
449
           ( session->buffer[2] == 0xff ) && ( session->buffer[3] == 0xff ) )
×
450
      {
451
        if ( Plib::systemstate.config.verbose )
×
452
        {
453
          INFO_PRINTLN( "UOKR Seed Message Received: Type {:#x}", (int)cstype );
×
454
        }
455
        session->myClient.send_KR_encryption_response();
×
456
        session->myClient.setClientType( Network::CLIENTTYPE_UOKR );  // UO:KR logging in
×
457
        session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
×
458
      }
459
      else if ( session->buffer[0] == PKTIN_EF_ID )
4✔
460
      {
461
        // new seed since 6.0.5.0 (0xef should never appear in normal ipseed)
462
        if ( Plib::systemstate.config.verbose )
2✔
463
        {
464
          INFO_PRINTLN( "6.0.5.0+ Crypt Seed Message Received: Type {:#x}", (int)cstype );
×
465
        }
466
        session->recv_state = Network::ThreadedClient::RECV_STATE_CLIENTVERSION_WAIT;
2✔
467
      }
468
      else
469
      {
470
        session->cryptengine->Init( session->buffer, Crypt::CCryptBase::typeAuto );
2✔
471
        session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
2✔
472
      }
473
    }
474
    // Else keep waiting for IP address.
475
  }
476
  else if ( session->recv_state == Network::ThreadedClient::RECV_STATE_PROXYPROTOCOLHEADER_WAIT )
×
477
  {
478
    session->recv_remaining_nocrypt( sizeof( pp_header_v2 ) );
×
479

480
    if ( session->bytes_received == sizeof( pp_header_v2 ) )
×
481
    {
482
      pp_header_v2* pp_header = reinterpret_cast<pp_header_v2*>( &session->buffer );
×
483

484
      if ( !pp_header->is_valid() )
×
485
      {
486
        POLLOGLN( "Client#{} ({}): disconnected due to invalid proxy header",
×
487
                  session->myClient.instance_, session->ipaddrAsString() );
×
488

489
        // invalid header
490
        session->forceDisconnect();
×
491
        return false;
×
492
      }
493

494
      if ( pp_header->command() == PP_CMD_LOCAL && pp_header->payload_size() == 0 )
×
495
      {
496
        // for local command with zero payload there is nothing else to read => continue with the
497
        // stream
498
        session->recv_state = Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT;
×
499
        session->bytes_received = 0;
×
500
      }
501
      else
502
      {
503
        // other cases need to read payload first
504
        session->recv_state = Network::ThreadedClient::RECV_STATE_PROXYPROTOCOLPAYLOAD_WAIT;
×
505
      }
506
    }
507
    // Else keep waiting
508
  }
509

510
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_PROXYPROTOCOLPAYLOAD_WAIT )
4✔
511
  {
512
    pp_header_v2* pp_header = reinterpret_cast<pp_header_v2*>( &session->buffer );
×
513
    session->recv_remaining_nocrypt( sizeof( pp_header_v2 ) + pp_header->payload_size() );
×
514

515
    if ( session->bytes_received == sizeof( pp_header_v2 ) + pp_header->payload_size() )
×
516
    {
517
      pp_payload_v2* pp_payload =
×
518
          reinterpret_cast<pp_payload_v2*>( &session->buffer[sizeof( pp_header_v2 )] );
519

520
      if ( pp_header->command() == PP_CMD_LOCAL )
×
521
      {
522
        // local commands shouldn't have payload, but in case they do it should be skipped
523
        session->recv_state = Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT;
×
524
        return true;
×
525
      }
526

527
      if ( pp_header->command() == PP_CMD_PROXY && pp_header->protocol() == PP_TP_STREAM )
×
528
      {
529
        bool was_proxied = false;
×
530

531
        if ( pp_header->address_family() == PP_AF_INET &&
×
532
             pp_header->payload_size() >= sizeof( pp_payload->ipv4_addr ) )
×
533
        {
534
          memcpy( &session->ipaddr_proxy, &session->ipaddr, sizeof( session->ipaddr_proxy ) );
×
535
          memset( &session->ipaddr, 0, sizeof( session->ipaddr ) );
×
536

537
          auto ipaddr_in = reinterpret_cast<sockaddr_in*>( &session->ipaddr );
×
538
          ipaddr_in->sin_family = AF_INET;
×
539
          ipaddr_in->sin_port = pp_payload->ipv4_addr.src_port;
×
540
          memcpy( &ipaddr_in->sin_addr, &pp_payload->ipv4_addr.src_addr,
×
541
                  sizeof( ipaddr_in->sin_addr ) );
542

543
          was_proxied = true;
×
544
        }
545

546
        /* IPv6 isn't supported
547
        if ( pp_header->address_family() == PP_AF_INET6 &&
548
             pp_header->payload_size() >= sizeof( pp_payload->ipv6_addr ) )
549
        {
550
          memcpy( &session->ipaddr_proxy, &session->ipaddr, sizeof( session->ipaddr_proxy ) );
551
          memset( &session->ipaddr, 0, sizeof( session->ipaddr ) );
552

553
          auto ipaddr_in6 = reinterpret_cast<sockaddr_in6*>( &session->ipaddr );
554
          ipaddr_in6->sin6_family = AF_INET6;
555
          ipaddr_in6->sin6_port = pp_payload->ipv6_addr.src_port;
556
          memcpy( &ipaddr_in6->sin6_addr, &pp_payload->ipv6_addr.src_addr,
557
                  sizeof(ipaddr_in6->sin6_addr) );
558

559
          was_proxied = true;
560
        }
561
        */
562

563
        if ( was_proxied )
×
564
        {
565
          POLLOGLN( "Client#{} ({}): connected through proxy {}", session->myClient.instance_,
×
566
                    session->ipaddrAsString(), session->ipaddrProxyAsString() );
×
567

568
          session->recv_state = Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT;
×
569
          session->bytes_received = 0;
×
570
          return true;
×
571
        }
572
      }
573

574
      // unsupported combination
575
      POLLOGLN( "Client#{} ({}): disconnected due to unsupported proxy payload",
×
576
                session->myClient.instance_, session->ipaddrAsString() );
×
577

578
      session->forceDisconnect();
×
579
      return false;
×
580
    }
581
    // Else keep waiting
582
  }
583

584
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_CLIENTVERSION_WAIT )
4✔
585
  {
586
    // receive and send to handler to get directly the version
587
    session->recv_remaining_nocrypt( 21 );
2✔
588
    if ( session->bytes_received == 21 )
2✔
589
    {
590
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
2✔
591
      unsigned char tempseed[4];
592
      tempseed[0] = session->buffer[1];
2✔
593
      tempseed[1] = session->buffer[2];
2✔
594
      tempseed[2] = session->buffer[3];
2✔
595
      tempseed[3] = session->buffer[4];
2✔
596
      session->cryptengine->Init( tempseed, Crypt::CCryptBase::typeLogin );
2✔
597
      Network::PacketRegistry::handle_msg( PKTIN_EF_ID, &session->myClient, session->buffer );
2✔
598
    }
599
  }
600

601
  return false;
4✔
602
}
603

604
// TODO: We may want to take a buffer directly here instead of a ThreadedClient
605
bool check_inactivity( Network::ThreadedClient* session )
154✔
606
{
607
  switch ( session->buffer[0] )
154✔
608
  {
609
  case PKTBI_73_ID:
43✔
610
  // Fallthrough
611
  case PKTIN_09_ID:
612
  // Fallthrough
613
  case PKTBI_D6_IN_ID:
614
    return true;
43✔
615
  case PKTBI_BF_ID:
8✔
616
    if ( ( session->buffer[3] == 0 ) && ( session->buffer[4] == PKTBI_BF::TYPE_SESPAM ) )
8✔
617
      return true;
×
618
    break;
8✔
619
  default:
103✔
620
    return false;
103✔
621
  }
622

623
  return false;
8✔
624
}
625

626
// Something to consider: this could become Client::report_weird_packet(char* buffer, int
627
// bytes_received, const std::string& why)
628
void report_weird_packet( Network::ThreadedClient* session, const std::string& why )
×
629
{
630
  std::string tmp = fmt::format(
631
      "Client#{}: {} type {:#x}, {} bytes (IP: {}, Account: {})\n", session->myClient.instance_,
×
632
      why, (int)session->buffer[0], session->bytes_received, session->ipaddrAsString(),
×
633
      ( session->myClient.acct != nullptr ) ? session->myClient.acct->name() : "None" );
×
634

635
  if ( session->bytes_received <= 64 )
×
636
  {
637
    Clib::fdump( std::back_inserter( tmp ), session->buffer, session->bytes_received );
×
638
    POLLOG_INFO( tmp );
×
639
  }
640
  else
641
  {
642
    INFO_PRINT( tmp );
×
643
    Clib::fdump( std::back_inserter( tmp ), session->buffer, session->bytes_received );
×
644
    POLLOGLN( tmp );
×
645
  }
646
}
×
647

648
// Called when a packet size is registered but the
649
// packet has no handler in the core
650
void handle_unknown_packet( Network::ThreadedClient* session )
×
651
{
652
  if ( Plib::systemstate.config.display_unknown_packets )
×
653
    report_weird_packet( session, "Unknown packet" );
×
654
}
×
655

656
// Called when POL receives an undefined packet.
657
// Those have no registered size, so we must guess.
658
void handle_undefined_packet( Network::ThreadedClient* session )
×
659
{
660
  int msgtype = (int)session->buffer[0];
×
661
  INFO_PRINTLN( "Undefined message type {:#x}", msgtype );
×
662

663
  // Tries to read as much of it out as possible
664
  session->recv_remaining( sizeof session->buffer / 2 );
×
665

666
  report_weird_packet( session, "Unexpected message" );
×
667
}
×
668

669
// Handles variable-sized packets whose declared size is much larger than
670
// the receive buffer. This packet is most likely a client error, because
671
// the buffer should be big enough to handle anything sent by the known
672
// clients.
673
void handle_humongous_packet( Network::ThreadedClient* session, unsigned int reported_size )
×
674
{
675
  // Tries to read as much of it out as possible
676
  // (the client will be disconnected, but this may
677
  // be useful for debugging)
678
  session->recv_remaining( sizeof session->buffer / 2 );
×
679

680
  report_weird_packet( session, fmt::format( "Humongous packet (length {})", reported_size ) );
×
681
}
×
682
}  // namespace Pol::Core
683

684
namespace Pol::Network
685
{
686
// on_close determines how long to wait until on_logoff is called. An alternative would be to call
687
// test_logoff directly in the threadedclient_io_finalize.
688
int Client::on_close()
4✔
689
{
690
  unregister();
4✔
691
  INFO_PRINTLN( "Client disconnected from {} ({}/{} connections)", ipaddrAsString(),
4✔
692
                Core::networkManager.clients.size(),
4✔
693
                Core::networkManager.getNumberOfLoginClients() );
4✔
694

695
  Core::CoreSetSysTrayToolTip(
4✔
696
      Clib::tostring( Core::networkManager.clients.size() ) + " clients connected",
8✔
697
      Core::ToolTipPrioritySystem );
698

699
  if ( chr )
4✔
700
  {
701
    chr->disconnect_cleanup();
2✔
702
    gd->clear();
2✔
703
    chr->connected( false );
2✔
704
  }
705

706
  return test_logoff();
4✔
707
}
708

709
int Client::test_logoff()
4✔
710
{
711
  if ( !chr )
4✔
712
    return 0;
2✔
713

714
  int seconds_wait = 0;
2✔
715
  Core::ScriptDef sd;
2✔
716
  sd.quickconfig( "scripts/misc/logofftest.ecl" );
2✔
717
  if ( sd.exists() )
2✔
718
  {
719
    Bscript::BObject bobj( run_script_to_completion( sd, new Module::ECharacterRefObjImp( chr ) ) );
×
720
    if ( bobj.isa( Bscript::BObjectImp::OTLong ) )
×
721
    {
722
      const Bscript::BLong* blong = bobj.impptr<Bscript::BLong>();
×
723
      seconds_wait = blong->value();
×
724
    }
725
  }
×
726
  return seconds_wait;
2✔
727
}
2✔
728

729
void Client::on_logoff()
2✔
730
{
731
  if ( chr )
2✔
732
  {
733
    call_chr_scripts( chr, "scripts/misc/logoff.ecl", "logoff.ecl" );
2✔
734
    if ( chr->realm() )
2✔
735
    {
736
      chr->realm()->notify_left( *chr );
2✔
737
    }
738
  }
739
}
2✔
740

741
void Client::warn_idle()
×
742
{
743
  Network::PktHelper::PacketOut<Network::PktOut_53> msg;
×
744
  msg->Write<u8>( PKTOUT_53_WARN_CHARACTER_IDLE );
×
745
  msg.Send( this );
×
746

747
  if ( pause_count )
×
748
    restart();
×
749
}
×
750

751
bool Client::should_check_idle()
52✔
752
{
753
  return ( !chr || chr->cmdlevel() < Plib::systemstate.config.min_cmdlvl_ignore_inactivity ) &&
103✔
754
         Plib::systemstate.config.inactivity_warning_timeout &&
104✔
755
         Plib::systemstate.config.inactivity_disconnect_timeout && !disable_inactivity_timeout();
156✔
756
}
757

758
void Client::handle_msg( unsigned char* pktbuffer, int pktlen )
154✔
759
{
760
  const unsigned char msgtype = pktbuffer[0];
154✔
761
  try
762
  {
763
    INFO_PRINTLN_TRACE( 10 )( "Client#{}: message {:#x}", instance_, msgtype );
154✔
764

765
    // TODO: use PacketRegistry::handle_msg(...) ?
766
    MSG_HANDLER packetHandler = Network::PacketRegistry::find_handler( msgtype, this );
154✔
767
    passert( packetHandler.msglen != 0 );
154✔
768
    packetHandler.func( this, pktbuffer );
154✔
769
    Core::restart_all_clients();
154✔
770
  }
771
  catch ( std::exception& ex )
×
772
  {
773
    POLLOG_ERRORLN( "Client#{}: Exception in message handler {:#x}: {}", instance_, (int)msgtype,
×
774
                    ex.what() );
×
775

776
    std::string tmp;
×
777
    Clib::fdump( std::back_inserter( tmp ), pktbuffer, pktlen );
×
778
    POLLOGLN( tmp );
×
779

780
    Core::restart_all_clients();
×
781
    throw;
×
782
  }
×
783
}
154✔
784

785
void Client::send_KR_encryption_response()
×
786
{
787
  Network::PktHelper::PacketOut<Network::PktOut_E3> msg;
×
788
  msg->WriteFlipped<u16>( 77u );
×
789
  msg->WriteFlipped<u32>( 0x03u );
×
790
  msg->Write<u8>( 0x02u );
×
791
  msg->Write<u8>( 0x01u );
×
792
  msg->Write<u8>( 0x03u );
×
793
  msg->WriteFlipped<u32>( 0x13u );
×
794
  msg->Write<u8>( 0x02u );
×
795
  msg->Write<u8>( 0x11u );
×
796
  msg->Write<u8>( 0x00u );
×
797
  msg->Write<u8>( 0xfcu );
×
798
  msg->Write<u8>( 0x2fu );
×
799
  msg->Write<u8>( 0xe3u );
×
800
  msg->Write<u8>( 0x81u );
×
801
  msg->Write<u8>( 0x93u );
×
802
  msg->Write<u8>( 0xcbu );
×
803
  msg->Write<u8>( 0xafu );
×
804
  msg->Write<u8>( 0x98u );
×
805
  msg->Write<u8>( 0xddu );
×
806
  msg->Write<u8>( 0x83u );
×
807
  msg->Write<u8>( 0x13u );
×
808
  msg->Write<u8>( 0xd2u );
×
809
  msg->Write<u8>( 0x9eu );
×
810
  msg->Write<u8>( 0xeau );
×
811
  msg->Write<u8>( 0xe4u );
×
812
  msg->Write<u8>( 0x13u );
×
813
  msg->WriteFlipped<u32>( 0x10u );
×
814
  msg->Write<u8>( 0x78u );
×
815
  msg->Write<u8>( 0x13u );
×
816
  msg->Write<u8>( 0xb7u );
×
817
  msg->Write<u8>( 0x7bu );
×
818
  msg->Write<u8>( 0xceu );
×
819
  msg->Write<u8>( 0xa8u );
×
820
  msg->Write<u8>( 0xd7u );
×
821
  msg->Write<u8>( 0xbcu );
×
822
  msg->Write<u8>( 0x52u );
×
823
  msg->Write<u8>( 0xdeu );
×
824
  msg->Write<u8>( 0x38u );
×
825
  msg->Write<u8>( 0x30u );
×
826
  msg->Write<u8>( 0xeau );
×
827
  msg->Write<u8>( 0xe9u );
×
828
  msg->Write<u8>( 0x1eu );
×
829
  msg->Write<u8>( 0xa3u );
×
830
  msg->WriteFlipped<u32>( 0x20u );
×
831
  msg->WriteFlipped<u32>( 0x10u );
×
832
  msg->Write<u8>( 0x5au );
×
833
  msg->Write<u8>( 0xceu );
×
834
  msg->Write<u8>( 0x3eu );
×
835
  msg->Write<u8>( 0xe3u );
×
836
  msg->Write<u8>( 0x97u );
×
837
  msg->Write<u8>( 0x92u );
×
838
  msg->Write<u8>( 0xe4u );
×
839
  msg->Write<u8>( 0x8au );
×
840
  msg->Write<u8>( 0xf1u );
×
841
  msg->Write<u8>( 0x9au );
×
842
  msg->Write<u8>( 0xd3u );
×
843
  msg->Write<u8>( 0x04u );
×
844
  msg->Write<u8>( 0x41u );
×
845
  msg->Write<u8>( 0x03u );
×
846
  msg->Write<u8>( 0xcbu );
×
847
  msg->Write<u8>( 0x53u );
×
848
  msg.Send( this );
×
849
}
×
850

851

852
//
853

854

855
// ThreadedClient stuff
856
void ThreadedClient::process_delayed_packets()
×
857
{
858
  PacketThrottler pkt = myClient.movementqueue.front();
×
859

860
  if ( myClient.SpeedHackPrevention( false ) )
×
861
  {
862
    if ( isReallyConnected() )
×
863
    {
864
      myClient.handle_msg( pkt.pktbuffer, sizeof pkt.pktbuffer );
×
865
    }
866
    myClient.movementqueue.pop();
×
867
  }
868
}
×
869

870
}  // namespace Pol::Network
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc