1
0
Fork 0
Univerxel/deps/picoquic/sim_link.c

299 lines
9.3 KiB
C

/*
* Author: Christian Huitema
* Copyright (c) 2017, Private Octopus, Inc.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Really basic network simulator, only simulates a simple link using a
* packet structure.
* Init: link creation. Returns a link structure with defined bandwidth,
* latency, loss pattern and initial time. The link is empty. The loss
* pattern is a 64 bit bit mask.
* Submit packet of length L at time t. The packet is queued to the link.
* Get packet out of link at time T + L + Queue.
*/
#include "picoquic_internal.h"
#include "picoquic_utils.h"
#include <stdlib.h>
#include <string.h>
picoquictest_sim_link_t* picoquictest_sim_link_create(double data_rate_in_gps,
uint64_t microsec_latency, uint64_t* loss_mask, uint64_t queue_delay_max, uint64_t current_time)
{
picoquictest_sim_link_t* link = (picoquictest_sim_link_t*)malloc(sizeof(picoquictest_sim_link_t));
if (link != 0) {
double pico_d = (data_rate_in_gps <= 0) ? 0 : (8000.0 / data_rate_in_gps);
pico_d *= (1.024 * 1.024); /* account for binary units */
link->next_send_time = current_time;
link->queue_time = current_time;
link->queue_delay_max = queue_delay_max;
link->picosec_per_byte = (uint64_t)pico_d;
link->microsec_latency = microsec_latency;
link->packets_dropped = 0;
link->packets_sent = 0;
link->first_packet = NULL;
link->last_packet = NULL;
link->loss_mask = loss_mask;
link->jitter_seed = 0xDEADBEEFBABAC001ull;
link->jitter = 0;
link->path_mtu = PICOQUIC_MAX_PACKET_SIZE;
link->red_drop_mask = 0;
link->red_queue_max = 0;
link->bucket_increase_per_microsec = 0;
link->bucket_max = 0;
link->bucket_current = 0;
link->bucket_arrival_last = current_time;
}
return link;
}
void picoquictest_sim_link_delete(picoquictest_sim_link_t* link)
{
picoquictest_sim_packet_t* packet;
while ((packet = link->first_packet) != NULL) {
link->first_packet = packet->next_packet;
free(packet);
}
free(link);
}
picoquictest_sim_packet_t* picoquictest_sim_link_create_packet()
{
picoquictest_sim_packet_t* packet = (picoquictest_sim_packet_t*)malloc(sizeof(picoquictest_sim_packet_t));
if (packet != NULL) {
packet->next_packet = NULL;
packet->arrival_time = 0;
packet->length = 0;
}
return packet;
}
uint64_t picoquictest_sim_link_next_arrival(picoquictest_sim_link_t* link, uint64_t current_time)
{
picoquictest_sim_packet_t* packet = link->first_packet;
if (packet != NULL && packet->arrival_time < current_time) {
current_time = packet->arrival_time;
}
return current_time;
}
picoquictest_sim_packet_t* picoquictest_sim_link_dequeue(picoquictest_sim_link_t* link,
uint64_t current_time)
{
picoquictest_sim_packet_t* packet = link->first_packet;
if (packet != NULL && packet->arrival_time <= current_time) {
link->first_packet = packet->next_packet;
if (link->first_packet == NULL) {
link->last_packet = NULL;
}
} else {
packet = NULL;
}
return packet;
}
static int picoquictest_sim_link_testloss(uint64_t* loss_mask)
{
uint64_t loss_bit = 0;
if (loss_mask != NULL) {
/* Last bit indicates loss or not */
loss_bit = (uint64_t)((*loss_mask) & 1ull);
/* Rotate loss mask by 1 to prepare next round */
*loss_mask >>= 1;
*loss_mask |= (loss_bit << 63);
}
return (int)loss_bit;
}
static uint64_t picoquictest_sim_link_jitter(picoquictest_sim_link_t* link)
{
uint64_t jitter = link->jitter;
double x = picoquic_test_gauss_random(&link->jitter_seed);
if (x < -3.0) {
x = -3.0;
}
x /= 3.0;
jitter += (int64_t)(x * (double)jitter);
return jitter;
}
void picoquictest_sim_link_submit(picoquictest_sim_link_t* link, picoquictest_sim_packet_t* packet,
uint64_t current_time)
{
uint64_t queue_delay = (current_time > link->queue_time) ? 0 : link->queue_time - current_time;
uint64_t transmit_time = ((link->picosec_per_byte * ((uint64_t)packet->length)) >> 20);
uint64_t should_drop = 0;
if (transmit_time <= 0)
transmit_time = 1;
if (link->bucket_increase_per_microsec > 0) {
/* Simulate a rate limiter based on classic leaky bucket algorithm */
uint64_t delta_microsec = current_time - link->bucket_arrival_last;
link->bucket_arrival_last = current_time;
link->bucket_current += ((double)delta_microsec) * link->bucket_increase_per_microsec;
if (link->bucket_current > (double)link->bucket_max) {
link->bucket_current = (double)link->bucket_max;
}
if (link->bucket_current > (double)packet->length) {
link->bucket_current -= (double)packet->length;
}
else {
should_drop = 1;
}
} else if (link->queue_delay_max > 0 && queue_delay >= link->queue_delay_max) {
if (link->red_drop_mask == 0 || queue_delay >= link->red_queue_max) {
should_drop = 1;
}
else {
should_drop = link->red_drop_mask & 1;
link->red_drop_mask >>= 1;
link->red_drop_mask |= (should_drop << 63);
}
}
if (!should_drop) {
link->queue_time = current_time + queue_delay + transmit_time;
if (packet->length > link->path_mtu || picoquictest_sim_link_testloss(link->loss_mask) != 0) {
link->packets_dropped++;
free(packet);
} else {
link->packets_sent++;
if (link->last_packet == NULL) {
link->first_packet = packet;
} else {
link->last_packet->next_packet = packet;
}
link->last_packet = packet;
packet->next_packet = NULL;
packet->arrival_time = link->queue_time + link->microsec_latency;
if (link->jitter != 0) {
packet->arrival_time += picoquictest_sim_link_jitter(link);
}
}
} else {
/* simulate congestion loss or random drop on queue full */
link->packets_dropped++;
free(packet);
}
}
int sim_link_one_test(uint64_t* loss_mask, uint64_t queue_delay_max, uint64_t nb_losses)
{
int ret = 0;
uint64_t current_time = 0;
uint64_t departure_time = 0;
picoquictest_sim_link_t* link = picoquictest_sim_link_create(0.01, 10000, loss_mask, queue_delay_max, current_time);
uint64_t dequeued = 0;
uint64_t queued = 0;
const uint64_t nb_packets = 16;
if (link == NULL) {
ret = -1;
}
else {
while (ret == 0) {
if (queued >= nb_packets) {
departure_time = (uint64_t)((int64_t)-1);
}
current_time = picoquictest_sim_link_next_arrival(link, departure_time);
picoquictest_sim_packet_t* packet = picoquictest_sim_link_dequeue(link, current_time);
if (packet != NULL) {
dequeued++;
free(packet);
}
else if (queued < nb_packets) {
packet = picoquictest_sim_link_create_packet();
if (packet == NULL) {
ret = -1;
}
else {
packet->length = sizeof(packet->bytes);
picoquictest_sim_link_submit(link, packet, departure_time);
departure_time += 250;
queued++;
}
}
else {
break;
}
}
if ((dequeued + nb_losses) != nb_packets) {
ret = -1;
}
picoquictest_sim_link_delete(link);
}
return ret;
}
int sim_link_test()
{
int ret = 0;
uint64_t loss_mask = 0;
ret = sim_link_one_test(&loss_mask, 0, 0);
if (ret == 0) {
loss_mask = 8;
ret = sim_link_one_test(&loss_mask, 0, 1);
}
if (ret == 0) {
loss_mask = 0x18;
ret = sim_link_one_test(&loss_mask, 0, 2);
}
return ret;
}
void picoquic_set_test_address(struct sockaddr_in * addr, uint32_t addr_val, uint16_t port)
{
/* Init of the IP addresses */
memset(addr, 0, sizeof(struct sockaddr_in));
addr->sin_family = AF_INET;
#ifdef _WINDOWS
addr->sin_addr.S_un.S_addr = addr_val;
#else
addr->sin_addr.s_addr = addr_val;
#endif
addr->sin_port = port;
}