Files
Volodymyr FialkoandBIAGINI Nathan 15d823dab6 Enable build flags and fix compilation warnings (gcc 14.1.0) (#66)
* Fix rpc build with other examples

CPP_COMPILE is an option, a cache variable, thus regular unset() is not
working for it since it tries to unset the scope variable.

This causing cmake to skip compilation of rpc.
Unset CPP_COMPILE from CACHE to make it work as intended.

* Fix compiler flags with build targets

Currently tests, soak and examples are build without extra flags,
because add_compile_options is specified after defining build targets.

From add_compile_options() docs:
Adds options to the compiler command line for targets in the current
directory and below that are added *after* this command is invoked.

* Pass debug callbacks as struct

Fixes:
warning: ISO C forbids conversion of function pointer to object pointer
type

* Move global variables under NBNET_IMPL

Nb net could be included multiple times, to prevent creation of global
variables in each file move them under NBNET_IMPL.

Also use designated initializer to initiate a driver structure to
remove warnings about partial initialization. Designated initializer
would initialize other members to zero.

* Resolve unused variables warnings.

Cast unused function arguments to (void) to suspend warnings.

* Resolve signed vs unsigned warnings

Remove unnecessary lt 0 checks and sync types to remove signed vs
unsigned warnings.

* Resolve string related warnings

Fixes:
nbnet/tests/string_tests.c:18:9: error: ‘strncpy’
output truncated before terminating nul copying 5 bytes from a string of
the same length
[-Werror=stringop-truncation]
   18 |         strncpy(res + pos, b, len_b);
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

nbnet/tests/string_tests.c:24:9: error: ‘strncpy’
specified bound depends on the length of the source argument
[-Werror=stringop-truncation]

* Resolve function defined but not used warnings

Mark GameServer_GetClientCount() as inline.

* Attempt to fix warning in ASSERT_VALUE_IN_RANGE

In case when value in unsigned type and min is 0, this commit just
obfuscates types, maybe there's better solution.

Fixes:
warning: comparison of unsigned expression in ‘>= 0’ is always true
[-Wtype-limits]

* Ensure that read len is less than word size

In release mode(-DCMAKE_BUILD_TYPE=Release) tests returning following
error:
nbnet/tests/../nbnet.h:2272:9: error: ‘memcpy’ forming offset [4, 7] is
out of the bound s [0, 4] of object ‘word’ with type ‘Word’ {aka
‘unsigned int’} [-Werror=array-bounds=]
 2272 |         memcpy(bytes, &word, length); |
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Add check to verify that length is less than Word size.

* add -Wno-unknown-pragmas flag

* fix examples compilation on windows

* fix examples compilation on windows

* fix examples compilation on windows

* fix unit tests compilation on windows

* fix soak test compilation on windows

* fix unit tests compilation on windows

* fix unit tests compilation on windows

---------

Co-authored-by: BIAGINI Nathan <nath.biagini@gmail.com>
2024-08-10 12:02:42 +02:00

408 lines
12 KiB
C

/*
Copyright (C) 2024 BIAGINI Nathan
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#define NBNET_IMPL
#include "soak.h"
#ifdef __EMSCRIPTEN__
#include "../net_drivers/webrtc.h"
#else
#include "../net_drivers/udp.h"
#ifdef WEBRTC_NATIVE
#include "../net_drivers/webrtc_c.h"
#endif
#endif // __EMSCRIPTEN__
typedef struct
{
uint8_t data[SOAK_MESSAGE_MAX_DATA_LENGTH];
uint8_t channel_id;
unsigned int length;
bool free;
} Soak_MessageEntry;
typedef struct
{
unsigned int message_count;
unsigned int sent_message_count;
unsigned int next_msg_id;
unsigned int last_recved_message_id;
unsigned int last_sent_message_id;
Soak_MessageEntry messages[SOAK_CLIENT_MAX_PENDING_MESSAGES];
} SoakChannel;
static bool connected = false;
static unsigned int done_channel_count = 0;
static void GenerateRandomBytes(uint8_t *data, unsigned int length)
{
for (unsigned int i = 0; i < length; i++)
data[i] = rand() % 255 + 1;
}
static int SendSoakMessages(SoakChannel *channel, uint8_t channel_id)
{
unsigned int msg_count = channel->message_count;
if (channel->sent_message_count < msg_count)
{
// number of messages yet to be sent
unsigned int remaining_message_count = msg_count - channel->sent_message_count;
// number of messages sent but not yet to be acked
unsigned int pending_message_count = channel->last_sent_message_id - channel->last_recved_message_id;
Soak_LogInfo("Compute number of soak messages to send (sent: %d, pending: %d, remaining: %d)",
channel->sent_message_count, pending_message_count, remaining_message_count);
// don't send anything on this tick if we have reached the max number of unacked messages
if (pending_message_count >= SOAK_CLIENT_MAX_PENDING_MESSAGES)
{
Soak_LogInfo("Max number of pending messages has been reached, not sending anything this tick");
return 0;
}
// number of messages to send on this tick
unsigned int send_message_count = MIN(
SOAK_CLIENT_MAX_PENDING_MESSAGES - pending_message_count, remaining_message_count);
Soak_LogInfo("Will send %d soak messages this tick", send_message_count);
for (unsigned int i = 0; i < send_message_count; i++)
{
SoakMessage *msg = SoakMessage_CreateOutgoing();
if (msg == NULL)
{
Soak_LogError("Failed to create soak message");
return -1;
}
int percent = rand() % 100 + 1;
if (percent <= SOAK_BIG_MESSAGE_PERCENTAGE)
{
// chunked
msg->data_length = rand() % (SOAK_MESSAGE_MAX_DATA_LENGTH - 1024) + 1024;
}
else
{
// not chuncked
msg->data_length = rand() % (200 - SOAK_MESSAGE_MIN_DATA_LENGTH) + SOAK_MESSAGE_MIN_DATA_LENGTH;
}
msg->id = channel->next_msg_id++;
GenerateRandomBytes(msg->data, msg->data_length);
Soak_MessageEntry *entry = &channel->messages[(msg->id - 1) % SOAK_CLIENT_MAX_PENDING_MESSAGES];
assert(entry->free);
entry->length = msg->data_length;
entry->free = false;
entry->channel_id = channel_id;
memcpy(entry->data, msg->data, msg->data_length);
Soak_LogInfo("Send soak message (id: %d, data length: %d)", msg->id, msg->data_length);
if (NBN_GameClient_SendMessage(SOAK_MESSAGE, channel_id, msg) < 0)
return -1;
channel->sent_message_count++;
channel->last_sent_message_id = msg->id;
}
}
return 0;
}
static int HandleReceivedSoakMessage(SoakMessage *msg, uint8_t channel_id, SoakChannel *channels)
{
SoakChannel *channel = &channels[channel_id];
if (msg->id != channel->last_recved_message_id + 1)
{
Soak_LogError("Expected to receive message %d but received message %d (channel_id: %d)", channel->last_recved_message_id + 1, msg->id, channel_id);
return -1;
}
Soak_MessageEntry *entry = &channel->messages[(msg->id - 1) % SOAK_CLIENT_MAX_PENDING_MESSAGES];
assert(!entry->free);
assert(entry->channel_id == channel_id);
if (memcmp(msg->data, entry->data, msg->data_length) != 0)
{
Soak_LogError("Received invalid data for message %d (data length: %d, channel_id: %d)", msg->id, msg->data_length, channel_id);
return -1;
}
entry->free = true;
channel->last_recved_message_id = msg->id;
SoakOptions options = Soak_GetOptions();
unsigned int channel_count = options.channel_count;
Soak_LogInfo("Received soak message (length: %d, %d/%d) on channel %d", msg->data_length, msg->id, channel->message_count, channel_id);
SoakMessage_Destroy(msg);
if (channel->last_recved_message_id == channel->message_count)
{
Soak_LogInfo("Received all soak message echoes on channel %d", channel_id);
done_channel_count++;
}
if (done_channel_count >= channel_count)
{
Soak_LogInfo("Received all soak message echoes on all channels");
Soak_Stop();
return SOAK_DONE;
}
return 0;
}
static int HandleReceivedMessage(SoakChannel *channels)
{
NBN_MessageInfo msg = NBN_GameClient_GetMessageInfo();
switch (msg.type)
{
case SOAK_MESSAGE:
return HandleReceivedSoakMessage((SoakMessage *)msg.data, msg.channel_id, channels);
default:
Soak_LogError("Received unexpected message (type: %d, channel_id: %d)", msg.type, msg.channel_id);
return -1;
}
return 0;
}
static int Tick(void *data)
{
SoakChannel *channels = (SoakChannel *)data;
int ev;
while ((ev = NBN_GameClient_Poll()) != NBN_NO_EVENT)
{
if (ev < 0)
return -1;
switch (ev)
{
case NBN_DISCONNECTED:
connected = false;
Soak_LogInfo("Disconnected from server (code: %d)", NBN_GameClient_GetServerCloseCode());
Soak_Stop();
return 0;
case NBN_CONNECTED:
Soak_LogInfo("Connected to server");
connected = true;
break;
case NBN_MESSAGE_RECEIVED:
if (HandleReceivedMessage(channels) < 0)
return -1;
break;
}
}
if (connected)
{
unsigned int channel_count = Soak_GetOptions().channel_count;
for (unsigned int c = 0; c < channel_count; c++)
{
SoakChannel *channel = &channels[c];
if (SendSoakMessages(channel, c) < 0)
{
Soak_LogError("An error occured while sending messages on channel %d", c);
return -1;
}
}
}
if (NBN_GameClient_SendPackets() < 0)
{
Soak_LogError("Failed to flush game client send queue. Exit");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
Soak_SetLogLevel(LOG_TRACE);
if (Soak_ReadCommandLine(argc, argv) < 0)
return -1;
SoakOptions options = Soak_GetOptions();
#ifdef __EMSCRIPTEN__
NBN_WebRTC_Register((NBN_WebRTC_Config){.enable_tls = false}); // Register the WebRTC driver
#else
#ifdef WEBRTC_NATIVE
if (options.webrtc)
{
// Register native WebRTC driver
const char *ice_servers[] = { "stun:stun01.sipphone.com" };
NBN_WebRTC_C_Config cfg = {
.ice_servers = ice_servers,
.ice_servers_count = 1,
.enable_tls = false,
.cert_path = NULL,
.key_path = NULL,
.passphrase = NULL,
.log_level = RTC_LOG_VERBOSE};
NBN_WebRTC_C_Register(cfg);
}
else
{
NBN_UDP_Register();
}
#else
NBN_UDP_Register();
#endif // WEBRTC_NATIVE
#endif // __EMSCRIPTEN__
if (NBN_GameClient_Start(SOAK_PROTOCOL_NAME, "127.0.0.1", SOAK_PORT) < 0)
{
Soak_LogError("Failed to start game client. Exit");
#ifdef __EMSCRIPTEN__
emscripten_force_exit(1);
#else
return 1;
#endif
}
if (Soak_Init(argc, argv) < 0)
{
Soak_LogError("Failed to initialize soak test");
return 1;
}
unsigned int channel_count = options.channel_count;
unsigned int message_count = options.message_count;
unsigned int message_per_channel = message_count / channel_count;
unsigned int leftover_message_count = message_count % channel_count;
SoakChannel *channels = (SoakChannel *)malloc(sizeof(SoakChannel) * channel_count);
for (unsigned int c = 0; c < channel_count; c++)
{
SoakChannel *channel = &channels[c];
channel->next_msg_id = 1;
channel->sent_message_count = 0;
channel->last_recved_message_id = 0;
channel->last_sent_message_id = 0;
channel->message_count = message_per_channel;
for (int i = 0; i < SOAK_CLIENT_MAX_PENDING_MESSAGES; i++)
{
channel->messages[i].free = true;
}
}
channels[channel_count - 1].message_count += leftover_message_count;
NBN_ConnectionDebugCallback cbs = {
.OnMessageAddedToRecvQueue = Soak_Debug_PrintAddedToRecvQueue,
};
NBN_GameClient_Debug_RegisterCallback(cbs);
int ret = Soak_MainLoop(Tick, channels);
NBN_GameClient_Stop();
free(channels);
unsigned int created_outgoing_message_count = Soak_GetCreatedOutgoingSoakMessageCount();
unsigned int destroyed_outgoing_message_count = Soak_GetDestroyedOutgoingSoakMessageCount();
unsigned int created_incoming_message_count = Soak_GetCreatedIncomingSoakMessageCount();
unsigned int destroyed_incoming_message_count = Soak_GetDestroyedIncomingSoakMessageCount();
Soak_LogInfo("Outgoing soak messages created: %d", created_outgoing_message_count);
Soak_LogInfo("Outgoing soak messages destroyed: %d", destroyed_outgoing_message_count);
Soak_LogInfo("Incoming soak messages created: %d", created_incoming_message_count);
Soak_LogInfo("Incoming soak messages destroyed: %d", destroyed_incoming_message_count);
if (created_outgoing_message_count != destroyed_outgoing_message_count)
{
Soak_LogError("created_outgoing_message_count != destroyed_outgoing_message_count (potential memory leak !)");
return 1;
}
if (created_incoming_message_count != destroyed_incoming_message_count)
{
Soak_LogError("created_incoming_message_count != destroyed_incoming_message_count (potential memory leak !)");
return 1;
}
Soak_LogInfo("No memory leak detected! Cool... cool cool cool");
#ifdef WEBRTC_NATIVE
if (options.webrtc)
{
NBN_WebRTC_C_Unregister();
}
#endif // WEBRTC_NATIVE
#ifdef __EMSCRIPTEN__
emscripten_force_exit(ret);
#else
return ret;
#endif
}