You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
14 KiB
408 lines
14 KiB
// test_etcp_stress.c - Stress test for ETCP with packet loss, delay, and reordering |
|
#include "etcp.h" |
|
#include "u_async.h" |
|
#include "ll_queue.h" |
|
#include "simple_uasync.h" |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <stdlib.h> |
|
#include <stdint.h> |
|
#include <assert.h> |
|
#include <time.h> |
|
|
|
#define NUM_PACKETS 10000 |
|
#define MIN_PACKET_SIZE 1 |
|
#define MAX_PACKET_SIZE 1300 |
|
#define LOSS_PROBABILITY 0.0 // 0% packet loss for testing reordering |
|
#define REORDER_PROBABILITY 0.1 // 10% reordering for testing |
|
#define QUEUE_MAX_SIZE 5000 // Max packets in delay queue |
|
#define MAX_DELAY_MS 100 // Up to 100ms delay for reordering effect |
|
#define TIME_BASE_MS 0.1 // uasync timebase is 0.1ms |
|
|
|
// Packet in the network delay queue |
|
typedef struct delayed_packet { |
|
uint8_t* data; |
|
uint16_t len; |
|
uint32_t delivery_time; // When to deliver (in timebase units) |
|
struct delayed_packet* next; |
|
} delayed_packet_t; |
|
|
|
// Network emulator structure |
|
typedef struct { |
|
struct ETCP_CONN* sender; // ETCP instance that sends |
|
struct ETCP_CONN* receiver; // ETCP instance that receives |
|
delayed_packet_t* queue; // Delay queue (sorted by delivery time) |
|
int queue_size; |
|
uint32_t current_time; // Current time in timebase units |
|
uint32_t packets_sent; |
|
uint32_t packets_lost; |
|
uint32_t packets_reordered; |
|
uint32_t packets_delivered; |
|
uint8_t running; |
|
void* timer_id; |
|
} network_emulator_t; |
|
|
|
// Forward declarations |
|
static void sender_tx_callback(struct ETCP_CONN* epkt, uint8_t* data, uint16_t len, void* arg); |
|
static void receiver_tx_callback(struct ETCP_CONN* epkt, uint8_t* data, uint16_t len, void* arg); |
|
static void deliver_packets(network_emulator_t* net); |
|
static void free_delay_queue(delayed_packet_t* queue); |
|
|
|
// Random number generator (simple LCG) |
|
static uint32_t random_state = 123456789; |
|
static uint32_t random_next(void) { |
|
random_state = random_state * 1103515245 + 12345; |
|
return random_state; |
|
} |
|
|
|
static double random_double(void) { |
|
return (double)random_next() / (double)UINT32_MAX; |
|
} |
|
|
|
|
|
|
|
// Sender's TX callback - called when ETCP wants to send a packet |
|
static void sender_tx_callback(struct ETCP_CONN* epkt, uint8_t* data, uint16_t len, void* arg) { |
|
(void)epkt; |
|
network_emulator_t* net = (network_emulator_t*)arg; |
|
if (!net || !net->running) return; |
|
|
|
net->packets_sent++; |
|
|
|
// 10% packet loss - just ignore the packet, ETCP will free the data |
|
if (random_double() < LOSS_PROBABILITY) { |
|
net->packets_lost++; |
|
return; |
|
} |
|
|
|
// Calculate delivery time (current time + random delay up to MAX_DELAY_MS) |
|
uint32_t delay_ms = (uint32_t)(random_double() * MAX_DELAY_MS); |
|
uint32_t delivery_time = net->current_time + (delay_ms * 10); // Convert ms to timebase |
|
|
|
// Create delayed packet - need to copy data since ETCP owns the original |
|
delayed_packet_t* pkt = malloc(sizeof(delayed_packet_t)); |
|
if (!pkt) { |
|
return; // Memory allocation failed, packet is lost |
|
} |
|
|
|
pkt->data = malloc(len); |
|
if (!pkt->data) { |
|
free(pkt); |
|
net->packets_lost++; // Count as loss due to memory failure |
|
return; |
|
} |
|
|
|
memcpy(pkt->data, data, len); |
|
pkt->len = len; |
|
pkt->delivery_time = delivery_time; |
|
pkt->next = NULL; |
|
|
|
// Insert into delay queue |
|
delayed_packet_t** pp = &net->queue; |
|
|
|
// 30% chance to insert at random position (reordering) |
|
if (random_double() < REORDER_PROBABILITY && net->queue_size > 1) { |
|
net->packets_reordered++; |
|
int insert_pos = random_next() % (net->queue_size + 1); |
|
for (int i = 0; i < insert_pos && *pp; i++) { |
|
pp = &(*pp)->next; |
|
} |
|
} else { |
|
// Normal insertion (sorted by delivery time) |
|
while (*pp && (*pp)->delivery_time < delivery_time) { |
|
pp = &(*pp)->next; |
|
} |
|
} |
|
|
|
pkt->next = *pp; |
|
*pp = pkt; |
|
net->queue_size++; |
|
|
|
// Limit queue size (drop first packet if needed) |
|
if (net->queue_size > QUEUE_MAX_SIZE) { |
|
delayed_packet_t* first = net->queue; |
|
if (first) { |
|
net->queue = first->next; |
|
free(first->data); |
|
free(first); |
|
net->queue_size--; |
|
net->packets_lost++; // Count as loss due to queue overflow |
|
} |
|
} |
|
} |
|
|
|
// Receiver's TX callback - called when receiver wants to send ACKs or retransmission requests |
|
static void receiver_tx_callback(struct ETCP_CONN* epkt, uint8_t* data, uint16_t len, void* arg) { |
|
(void)epkt; |
|
network_emulator_t* net = (network_emulator_t*)arg; |
|
if (!net || !net->running) return; |
|
|
|
// Forward ACKs/retrans requests directly to sender with minimal delay (1 timebase unit) |
|
// This allows sender to receive ACKs and retransmit lost packets |
|
simple_uasync_advance_time(1); |
|
net->current_time = simple_uasync_get_time(); |
|
etcp_rx_input(net->sender, data, len); |
|
|
|
// Note: We don't track these in statistics since they're control packets |
|
} |
|
|
|
// Deliver packets whose delivery time has arrived |
|
static void deliver_packets(network_emulator_t* net) { |
|
while (net->queue && net->queue->delivery_time <= net->current_time) { |
|
delayed_packet_t* pkt = net->queue; |
|
net->queue = pkt->next; |
|
|
|
// Deliver to receiver |
|
etcp_rx_input(net->receiver, pkt->data, pkt->len); |
|
net->packets_delivered++; |
|
|
|
free(pkt->data); |
|
free(pkt); |
|
net->queue_size--; |
|
} |
|
} |
|
|
|
// Free delay queue |
|
static void free_delay_queue(delayed_packet_t* queue) { |
|
while (queue) { |
|
delayed_packet_t* next = queue->next; |
|
free(queue->data); |
|
free(queue); |
|
queue = next; |
|
} |
|
} |
|
|
|
// Generate random packet data |
|
static void generate_packet_data(uint8_t* buffer, uint16_t size, uint32_t seq) { |
|
// Fill with pattern: sequence number + random data |
|
for (uint16_t i = 0; i < size; i++) { |
|
if (i < 4) { |
|
// First 4 bytes: sequence number |
|
buffer[i] = (seq >> (8 * i)) & 0xFF; |
|
} else { |
|
// Rest: pseudo-random data based on sequence and position |
|
buffer[i] = (uint8_t)((seq * 7919 + i * 104729) % 256); |
|
} |
|
} |
|
} |
|
|
|
// Verify received packet |
|
static int verify_packet_data(const uint8_t* data, uint16_t size, uint32_t seq) { |
|
if (size < 4) return 0; |
|
|
|
// Check sequence number |
|
uint32_t received_seq = 0; |
|
for (int i = 0; i < 4; i++) { |
|
received_seq |= ((uint32_t)data[i]) << (8 * i); |
|
} |
|
|
|
if (received_seq != seq) return 0; |
|
|
|
// Verify the rest of the data |
|
for (uint16_t i = 4; i < size; i++) { |
|
uint8_t expected = (uint8_t)((seq * 7919 + i * 104729) % 256); |
|
if (data[i] != expected) return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
// Stress test main function |
|
int main(void) { |
|
printf("Starting ETCP stress test...\n"); |
|
printf("Parameters:\n"); |
|
printf(" Packets: %d\n", NUM_PACKETS); |
|
printf(" Size range: %d-%d bytes\n", MIN_PACKET_SIZE, MAX_PACKET_SIZE); |
|
printf(" Loss probability: %.1f%%\n", LOSS_PROBABILITY * 100); |
|
printf(" Reorder probability: %.1f%%\n", REORDER_PROBABILITY * 100); |
|
printf(" Max delay: %d ms\n", MAX_DELAY_MS); |
|
printf(" Queue size: %d packets\n", QUEUE_MAX_SIZE); |
|
|
|
// Seed random number generator |
|
random_state = (uint32_t)time(NULL); |
|
|
|
// Initialize uasync instance |
|
uasync_t* ua = uasync_create(); |
|
if (!ua) { |
|
fprintf(stderr, "Failed to create uasync instance\n"); |
|
return 1; |
|
} |
|
uasync_init_instance(ua); |
|
|
|
// Create network emulator |
|
network_emulator_t net = {0}; |
|
net.running = 1; |
|
net.current_time = 0; |
|
|
|
// Create ETCP instances |
|
net.sender = etcp_create(ua); |
|
net.receiver = etcp_create(ua); |
|
|
|
if (!net.sender || !net.receiver) { |
|
printf("ERROR: Failed to create ETCP instances\n"); |
|
return 1; |
|
} |
|
|
|
// Set up callbacks |
|
etcp_set_callback(net.sender, sender_tx_callback, &net); |
|
etcp_set_callback(net.receiver, receiver_tx_callback, &net); |
|
|
|
// Set reasonable bandwidth for stress test |
|
etcp_set_bandwidth(net.sender, 50000); // 50k bytes per timebase |
|
etcp_set_bandwidth(net.receiver, 50000); |
|
|
|
// Don't start network timer - we'll advance time manually |
|
|
|
printf("\nGenerating and sending %d packets...\n", NUM_PACKETS); |
|
|
|
// Generate and send packets |
|
uint32_t packets_generated = 0; |
|
uint32_t bytes_generated = 0; |
|
uint32_t last_time_print = 0; |
|
|
|
while (packets_generated < NUM_PACKETS) { |
|
// Generate random packet size |
|
uint16_t size = MIN_PACKET_SIZE + (random_next() % (MAX_PACKET_SIZE - MIN_PACKET_SIZE + 1)); |
|
|
|
// Allocate and fill packet data |
|
uint8_t* data = malloc(size); |
|
if (!data) { |
|
printf("ERROR: Memory allocation failed\n"); |
|
break; |
|
} |
|
|
|
generate_packet_data(data, size, packets_generated); |
|
|
|
// Send via ETCP |
|
if (etcp_tx_put(net.sender, data, size) != 0) { |
|
printf("ERROR: Failed to queue packet %u\n", packets_generated); |
|
free(data); |
|
break; |
|
} |
|
|
|
free(data); // etcp_tx_put makes its own copy |
|
packets_generated++; |
|
bytes_generated += size; |
|
|
|
// Periodically print progress |
|
if (packets_generated % 1000 == 0) { |
|
printf(" Sent %u packets, %u bytes\n", packets_generated, bytes_generated); |
|
} |
|
|
|
// Advance time to allow ETCP to send packets (bandwidth limiting) |
|
// and process any expired timers (retransmissions, etc.) |
|
simple_uasync_advance_time(1); // Advance by 1 timebase unit (0.1ms) |
|
net.current_time = simple_uasync_get_time(); // Keep in sync |
|
|
|
// Deliver any packets whose time has come |
|
deliver_packets(&net); |
|
|
|
// Periodically print time progress |
|
if (net.current_time - last_time_print >= 1000) { // Every 100ms |
|
printf(" Time: %.1f ms, Queue: %d packets\n", |
|
net.current_time / 10.0, net.queue_size); |
|
last_time_print = net.current_time; |
|
} |
|
} |
|
|
|
printf("Finished sending %u packets (%u bytes)\n", packets_generated, bytes_generated); |
|
|
|
// Let network deliver remaining packets and wait for retransmissions |
|
printf("\nDelivering remaining packets and waiting for retransmissions...\n"); |
|
uint32_t start_time = net.current_time; |
|
for (int i = 0; i < 200000 && (net.queue_size > 0 || i < 1000); i++) { |
|
simple_uasync_advance_time(10); // Advance 1ms |
|
net.current_time = simple_uasync_get_time(); // Keep in sync |
|
deliver_packets(&net); |
|
|
|
// Periodically advance more time to speed up retransmission timeouts |
|
if (i % 10 == 0) { |
|
simple_uasync_advance_time(100); // Additional 10ms |
|
net.current_time = simple_uasync_get_time(); |
|
deliver_packets(&net); |
|
} |
|
|
|
if (i % 500 == 0) { |
|
uint32_t elapsed = net.current_time - start_time; |
|
printf(" Queue: %d, Time: %.1f ms (elapsed: %.1f ms)\n", |
|
net.queue_size, net.current_time / 10.0, elapsed / 10.0); |
|
} |
|
} |
|
|
|
// No network timer to stop since we're not using one |
|
net.running = 0; |
|
|
|
// Free any remaining packets in queue |
|
free_delay_queue(net.queue); |
|
net.queue = NULL; |
|
net.queue_size = 0; |
|
|
|
// Collect and verify received packets |
|
printf("\nVerifying received packets...\n"); |
|
|
|
ll_queue_t* output_queue = etcp_get_output_queue(net.receiver); |
|
uint32_t packets_received = 0; |
|
uint32_t bytes_received = 0; |
|
uint32_t correct_packets = 0; |
|
uint32_t max_received_seq = 0; |
|
|
|
ll_entry_t* entry; |
|
while ((entry = queue_entry_get(output_queue)) != NULL) { |
|
uint8_t* data = ll_entry_data(entry); |
|
uint16_t size = ll_entry_size(entry); |
|
|
|
if (size >= 4) { |
|
uint32_t seq = 0; |
|
for (int i = 0; i < 4; i++) { |
|
seq |= ((uint32_t)data[i]) << (8 * i); |
|
} |
|
|
|
if (verify_packet_data(data, size, seq)) { |
|
correct_packets++; |
|
if (seq > max_received_seq) { |
|
max_received_seq = seq; |
|
} |
|
} |
|
} |
|
|
|
packets_received++; |
|
bytes_received += size; |
|
queue_entry_free(entry); |
|
} |
|
|
|
// Print statistics |
|
printf("\n=== Statistics ===\n"); |
|
printf("Packets generated: %u\n", packets_generated); |
|
printf("Packets sent: %u (via ETCP)\n", net.packets_sent); |
|
printf("Packets lost: %u (%.1f%%)\n", net.packets_lost, |
|
(net.packets_sent > 0) ? (100.0 * net.packets_lost / net.packets_sent) : 0.0); |
|
printf("Packets reordered: %u (%.1f%% of delivered)\n", net.packets_reordered, |
|
(net.packets_delivered > 0) ? (100.0 * net.packets_reordered / net.packets_delivered) : 0.0); |
|
printf("Packets delivered: %u (to receiver)\n", net.packets_delivered); |
|
printf("Packets received: %u (in output queue)\n", packets_received); |
|
printf("Correct packets: %u (%.1f%%)\n", correct_packets, |
|
(packets_received > 0) ? (100.0 * correct_packets / packets_received) : 0.0); |
|
printf("Bytes generated: %u\n", bytes_generated); |
|
printf("Bytes received: %u\n", bytes_received); |
|
|
|
// Check for missing packets |
|
uint32_t expected_received = packets_generated - net.packets_lost; |
|
if (packets_received < expected_received) { |
|
printf("\nWARNING: Received %u packets, expected ~%u (some may be in flight)\n", |
|
packets_received, expected_received); |
|
} else if (packets_received > expected_received) { |
|
printf("\nWARNING: Received %u packets, expected ~%u (duplicates?)\n", |
|
packets_received, expected_received); |
|
} |
|
|
|
// Cleanup |
|
etcp_free(net.sender); |
|
etcp_free(net.receiver); |
|
uasync_destroy(ua); |
|
|
|
printf("\nStress test completed.\n"); |
|
|
|
// Consider test successful if we received at least some packets |
|
// (with losses, we won't get all of them) |
|
return (correct_packets > 0) ? 0 : 1; |
|
} |