mirror of
https://github.com/bdnugget/nbnet.git
synced 2026-11-07 05:27:27 +00:00
* 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>
408 lines
12 KiB
C
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
|
|
}
|