470 lines
15 KiB
C
470 lines
15 KiB
C
|
/**
|
||
|
* @file FragmentProtoAssembler.c
|
||
|
* @author Ambroz Bizjak <ambrop7@gmail.com>
|
||
|
*
|
||
|
* @section LICENSE
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
* 1. Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
* 3. Neither the name of the author nor the
|
||
|
* names of its contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* 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 THE AUTHOR 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.
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <misc/offset.h>
|
||
|
#include <misc/byteorder.h>
|
||
|
#include <misc/balloc.h>
|
||
|
|
||
|
#include "FragmentProtoAssembler.h"
|
||
|
|
||
|
#include <generated/blog_channel_FragmentProtoAssembler.h>
|
||
|
|
||
|
#define PeerLog(_o, ...) BLog_LogViaFunc((_o)->logfunc, (_o)->user, BLOG_CURRENT_CHANNEL, __VA_ARGS__)
|
||
|
|
||
|
#include "FragmentProtoAssembler_tree.h"
|
||
|
#include <structure/SAvl_impl.h>
|
||
|
|
||
|
static void free_frame (FragmentProtoAssembler *o, struct FragmentProtoAssembler_frame *frame)
|
||
|
{
|
||
|
// remove from used list
|
||
|
LinkedList1_Remove(&o->frames_used, &frame->list_node);
|
||
|
// remove from used tree
|
||
|
FPAFramesTree_Remove(&o->frames_used_tree, 0, frame);
|
||
|
|
||
|
// append to free list
|
||
|
LinkedList1_Append(&o->frames_free, &frame->list_node);
|
||
|
}
|
||
|
|
||
|
static void free_oldest_frame (FragmentProtoAssembler *o)
|
||
|
{
|
||
|
ASSERT(!LinkedList1_IsEmpty(&o->frames_used))
|
||
|
|
||
|
// obtain oldest frame (first on the list)
|
||
|
LinkedList1Node *list_node = LinkedList1_GetFirst(&o->frames_used);
|
||
|
ASSERT(list_node)
|
||
|
struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
|
||
|
|
||
|
// free frame
|
||
|
free_frame(o, frame);
|
||
|
}
|
||
|
|
||
|
static struct FragmentProtoAssembler_frame * allocate_new_frame (FragmentProtoAssembler *o, fragmentproto_frameid id)
|
||
|
{
|
||
|
ASSERT(!FPAFramesTree_LookupExact(&o->frames_used_tree, 0, id))
|
||
|
|
||
|
// if there are no free entries, free the oldest used one
|
||
|
if (LinkedList1_IsEmpty(&o->frames_free)) {
|
||
|
PeerLog(o, BLOG_INFO, "freeing used frame");
|
||
|
free_oldest_frame(o);
|
||
|
}
|
||
|
|
||
|
// obtain frame entry
|
||
|
LinkedList1Node *list_node = LinkedList1_GetFirst(&o->frames_free);
|
||
|
ASSERT(list_node)
|
||
|
struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
|
||
|
|
||
|
// remove from free list
|
||
|
LinkedList1_Remove(&o->frames_free, &frame->list_node);
|
||
|
|
||
|
// initialize values
|
||
|
frame->id = id;
|
||
|
frame->time = o->time;
|
||
|
frame->num_chunks = 0;
|
||
|
frame->sum = 0;
|
||
|
frame->length = -1;
|
||
|
frame->length_so_far = 0;
|
||
|
|
||
|
// append to used list
|
||
|
LinkedList1_Append(&o->frames_used, &frame->list_node);
|
||
|
// insert to used tree
|
||
|
int res = FPAFramesTree_Insert(&o->frames_used_tree, 0, frame, NULL);
|
||
|
ASSERT_EXECUTE(res)
|
||
|
|
||
|
return frame;
|
||
|
}
|
||
|
|
||
|
static int chunks_overlap (int c1_start, int c1_len, int c2_start, int c2_len)
|
||
|
{
|
||
|
return (c1_start + c1_len > c2_start && c2_start + c2_len > c1_start);
|
||
|
}
|
||
|
|
||
|
static int frame_is_timed_out (FragmentProtoAssembler *o, struct FragmentProtoAssembler_frame *frame)
|
||
|
{
|
||
|
ASSERT(frame->time <= o->time)
|
||
|
|
||
|
return (o->time - frame->time > o->time_tolerance);
|
||
|
}
|
||
|
|
||
|
static void reduce_times (FragmentProtoAssembler *o)
|
||
|
{
|
||
|
// find the frame with minimal time, removing timed out frames
|
||
|
struct FragmentProtoAssembler_frame *minframe = NULL;
|
||
|
LinkedList1Node *list_node = LinkedList1_GetFirst(&o->frames_used);
|
||
|
while (list_node) {
|
||
|
LinkedList1Node *next = LinkedList1Node_Next(list_node);
|
||
|
struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
|
||
|
if (frame_is_timed_out(o, frame)) {
|
||
|
PeerLog(o, BLOG_INFO, "freeing timed out frame (while reducing times)");
|
||
|
free_frame(o, frame);
|
||
|
} else {
|
||
|
if (!minframe || frame->time < minframe->time) {
|
||
|
minframe = frame;
|
||
|
}
|
||
|
}
|
||
|
list_node = next;
|
||
|
}
|
||
|
|
||
|
if (!minframe) {
|
||
|
// have no frames, set packet time to zero
|
||
|
o->time = 0;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
uint32_t min_time = minframe->time;
|
||
|
|
||
|
// subtract minimal time from all frames
|
||
|
for (list_node = LinkedList1_GetFirst(&o->frames_used); list_node; list_node = LinkedList1Node_Next(list_node)) {
|
||
|
struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
|
||
|
frame->time -= min_time;
|
||
|
}
|
||
|
|
||
|
// subtract minimal time from packet time
|
||
|
o->time -= min_time;
|
||
|
}
|
||
|
|
||
|
static int process_chunk (FragmentProtoAssembler *o, fragmentproto_frameid frame_id, int chunk_start, int chunk_len, int is_last, uint8_t *payload)
|
||
|
{
|
||
|
ASSERT(chunk_start >= 0)
|
||
|
ASSERT(chunk_len >= 0)
|
||
|
ASSERT(is_last == 0 || is_last == 1)
|
||
|
|
||
|
// verify chunk
|
||
|
|
||
|
// check start
|
||
|
if (chunk_start > o->output_mtu) {
|
||
|
PeerLog(o, BLOG_INFO, "chunk starts outside");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// check frame size bound
|
||
|
if (chunk_len > o->output_mtu - chunk_start) {
|
||
|
PeerLog(o, BLOG_INFO, "chunk ends outside");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// calculate end
|
||
|
int chunk_end = chunk_start + chunk_len;
|
||
|
ASSERT(chunk_end >= 0)
|
||
|
ASSERT(chunk_end <= o->output_mtu)
|
||
|
|
||
|
// lookup frame
|
||
|
struct FragmentProtoAssembler_frame *frame = FPAFramesTree_LookupExact(&o->frames_used_tree, 0, frame_id);
|
||
|
if (!frame) {
|
||
|
// frame not found, add a new one
|
||
|
frame = allocate_new_frame(o, frame_id);
|
||
|
} else {
|
||
|
// have existing frame with that ID
|
||
|
// check frame time
|
||
|
if (frame_is_timed_out(o, frame)) {
|
||
|
// frame is timed out, remove it and use a new one
|
||
|
PeerLog(o, BLOG_INFO, "freeing timed out frame (while processing chunk)");
|
||
|
free_frame(o, frame);
|
||
|
frame = allocate_new_frame(o, frame_id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ASSERT(frame->num_chunks < o->num_chunks)
|
||
|
|
||
|
// check if the chunk overlaps with any existing chunks
|
||
|
for (int i = 0; i < frame->num_chunks; i++) {
|
||
|
struct FragmentProtoAssembler_chunk *chunk = &frame->chunks[i];
|
||
|
if (chunks_overlap(chunk->start, chunk->len, chunk_start, chunk_len)) {
|
||
|
PeerLog(o, BLOG_INFO, "chunk overlaps with existing chunk");
|
||
|
goto fail_frame;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (is_last) {
|
||
|
// this chunk is marked as last
|
||
|
if (frame->length >= 0) {
|
||
|
PeerLog(o, BLOG_INFO, "got last chunk, but already have one");
|
||
|
goto fail_frame;
|
||
|
}
|
||
|
// check if frame size according to this packet is consistent
|
||
|
// with existing chunks
|
||
|
if (frame->length_so_far > chunk_end) {
|
||
|
PeerLog(o, BLOG_INFO, "got last chunk, but already have data over its bound");
|
||
|
goto fail_frame;
|
||
|
}
|
||
|
} else {
|
||
|
// if we have length, chunk must be in its bound
|
||
|
if (frame->length >= 0) {
|
||
|
if (chunk_end > frame->length) {
|
||
|
PeerLog(o, BLOG_INFO, "chunk out of length bound");
|
||
|
goto fail_frame;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// chunk is good, add it
|
||
|
|
||
|
// update frame time
|
||
|
frame->time = o->time;
|
||
|
|
||
|
// add chunk entry
|
||
|
struct FragmentProtoAssembler_chunk *chunk = &frame->chunks[frame->num_chunks];
|
||
|
chunk->start = chunk_start;
|
||
|
chunk->len = chunk_len;
|
||
|
frame->num_chunks++;
|
||
|
|
||
|
// update sum
|
||
|
frame->sum += chunk_len;
|
||
|
|
||
|
// update length
|
||
|
if (is_last) {
|
||
|
frame->length = chunk_end;
|
||
|
} else {
|
||
|
if (frame->length < 0) {
|
||
|
if (frame->length_so_far < chunk_end) {
|
||
|
frame->length_so_far = chunk_end;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// copy chunk payload to buffer
|
||
|
memcpy(frame->buffer + chunk_start, payload, chunk_len);
|
||
|
|
||
|
// is frame incomplete?
|
||
|
if (frame->length < 0 || frame->sum < frame->length) {
|
||
|
// if all chunks are used, fail it
|
||
|
if (frame->num_chunks == o->num_chunks) {
|
||
|
PeerLog(o, BLOG_INFO, "all chunks used, but frame not complete");
|
||
|
goto fail_frame;
|
||
|
}
|
||
|
|
||
|
// wait for more chunks
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ASSERT(frame->sum == frame->length)
|
||
|
|
||
|
PeerLog(o, BLOG_DEBUG, "frame complete");
|
||
|
|
||
|
// free frame entry
|
||
|
free_frame(o, frame);
|
||
|
|
||
|
// send frame
|
||
|
PacketPassInterface_Sender_Send(o->output, frame->buffer, frame->length);
|
||
|
|
||
|
return 1;
|
||
|
|
||
|
fail_frame:
|
||
|
free_frame(o, frame);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void process_input (FragmentProtoAssembler *o)
|
||
|
{
|
||
|
ASSERT(o->in_len >= 0)
|
||
|
|
||
|
// read chunks
|
||
|
while (o->in_pos < o->in_len) {
|
||
|
// obtain header
|
||
|
if (o->in_len - o->in_pos < sizeof(struct fragmentproto_chunk_header)) {
|
||
|
PeerLog(o, BLOG_INFO, "too little data for chunk header");
|
||
|
break;
|
||
|
}
|
||
|
struct fragmentproto_chunk_header header;
|
||
|
memcpy(&header, o->in + o->in_pos, sizeof(header));
|
||
|
o->in_pos += sizeof(struct fragmentproto_chunk_header);
|
||
|
fragmentproto_frameid frame_id = ltoh16(header.frame_id);
|
||
|
int chunk_start = ltoh16(header.chunk_start);
|
||
|
int chunk_len = ltoh16(header.chunk_len);
|
||
|
int is_last = ltoh8(header.is_last);
|
||
|
|
||
|
// check is_last field
|
||
|
if (!(is_last == 0 || is_last == 1)) {
|
||
|
PeerLog(o, BLOG_INFO, "chunk is_last wrong");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// obtain data
|
||
|
if (o->in_len - o->in_pos < chunk_len) {
|
||
|
PeerLog(o, BLOG_INFO, "too little data for chunk data");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// process chunk
|
||
|
int res = process_chunk(o, frame_id, chunk_start, chunk_len, is_last, o->in + o->in_pos);
|
||
|
o->in_pos += chunk_len;
|
||
|
|
||
|
if (res) {
|
||
|
// sending complete frame, stop processing input
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// increment packet time
|
||
|
if (o->time == FPA_MAX_TIME) {
|
||
|
reduce_times(o);
|
||
|
if (!LinkedList1_IsEmpty(&o->frames_used)) {
|
||
|
ASSERT(o->time < FPA_MAX_TIME) // If there was a frame with zero time, it was removed because
|
||
|
// time_tolerance < FPA_MAX_TIME. So something >0 was subtracted.
|
||
|
o->time++;
|
||
|
} else {
|
||
|
// it was set to zero by reduce_times
|
||
|
ASSERT(o->time == 0)
|
||
|
}
|
||
|
} else {
|
||
|
o->time++;
|
||
|
}
|
||
|
|
||
|
// set no input packet
|
||
|
o->in_len = -1;
|
||
|
|
||
|
// finish input
|
||
|
PacketPassInterface_Done(&o->input);
|
||
|
}
|
||
|
|
||
|
static void input_handler_send (FragmentProtoAssembler *o, uint8_t *data, int data_len)
|
||
|
{
|
||
|
ASSERT(data_len >= 0)
|
||
|
ASSERT(o->in_len == -1)
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
// save input packet
|
||
|
o->in_len = data_len;
|
||
|
o->in = data;
|
||
|
o->in_pos = 0;
|
||
|
|
||
|
process_input(o);
|
||
|
}
|
||
|
|
||
|
static void output_handler_done (FragmentProtoAssembler *o)
|
||
|
{
|
||
|
ASSERT(o->in_len >= 0)
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
process_input(o);
|
||
|
}
|
||
|
|
||
|
int FragmentProtoAssembler_Init (FragmentProtoAssembler *o, int input_mtu, PacketPassInterface *output, int num_frames, int num_chunks, BPendingGroup *pg, void *user, BLog_logfunc logfunc)
|
||
|
{
|
||
|
ASSERT(input_mtu >= 0)
|
||
|
ASSERT(num_frames > 0)
|
||
|
ASSERT(num_frames < FPA_MAX_TIME) // needed so we can always subtract times when packet time is maximum
|
||
|
ASSERT(num_chunks > 0)
|
||
|
|
||
|
// init arguments
|
||
|
o->output = output;
|
||
|
o->num_chunks = num_chunks;
|
||
|
o->user = user;
|
||
|
o->logfunc = logfunc;
|
||
|
|
||
|
// init input
|
||
|
PacketPassInterface_Init(&o->input, input_mtu, (PacketPassInterface_handler_send)input_handler_send, o, pg);
|
||
|
|
||
|
// init output
|
||
|
PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
|
||
|
|
||
|
// remebmer output MTU
|
||
|
o->output_mtu = PacketPassInterface_GetMTU(o->output);
|
||
|
|
||
|
// set packet time to zero
|
||
|
o->time = 0;
|
||
|
|
||
|
// set time tolerance to num_frames
|
||
|
o->time_tolerance = num_frames;
|
||
|
|
||
|
// allocate frames
|
||
|
if (!(o->frames_entries = (struct FragmentProtoAssembler_frame *)BAllocArray(num_frames, sizeof(o->frames_entries[0])))) {
|
||
|
goto fail1;
|
||
|
}
|
||
|
|
||
|
// allocate chunks
|
||
|
if (!(o->frames_chunks = (struct FragmentProtoAssembler_chunk *)BAllocArray2(num_frames, o->num_chunks, sizeof(o->frames_chunks[0])))) {
|
||
|
goto fail2;
|
||
|
}
|
||
|
|
||
|
// allocate buffers
|
||
|
if (!(o->frames_buffer = (uint8_t *)BAllocArray(num_frames, o->output_mtu))) {
|
||
|
goto fail3;
|
||
|
}
|
||
|
|
||
|
// init frame lists
|
||
|
LinkedList1_Init(&o->frames_free);
|
||
|
LinkedList1_Init(&o->frames_used);
|
||
|
|
||
|
// initialize frame entries
|
||
|
for (int i = 0; i < num_frames; i++) {
|
||
|
struct FragmentProtoAssembler_frame *frame = &o->frames_entries[i];
|
||
|
// set chunks array pointer
|
||
|
frame->chunks = o->frames_chunks + (size_t)i * o->num_chunks;
|
||
|
// set buffer pointer
|
||
|
frame->buffer = o->frames_buffer + (size_t)i * o->output_mtu;
|
||
|
// add to free list
|
||
|
LinkedList1_Append(&o->frames_free, &frame->list_node);
|
||
|
}
|
||
|
|
||
|
// init tree
|
||
|
FPAFramesTree_Init(&o->frames_used_tree);
|
||
|
|
||
|
// have no input packet
|
||
|
o->in_len = -1;
|
||
|
|
||
|
DebugObject_Init(&o->d_obj);
|
||
|
|
||
|
return 1;
|
||
|
|
||
|
fail3:
|
||
|
BFree(o->frames_chunks);
|
||
|
fail2:
|
||
|
BFree(o->frames_entries);
|
||
|
fail1:
|
||
|
PacketPassInterface_Free(&o->input);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void FragmentProtoAssembler_Free (FragmentProtoAssembler *o)
|
||
|
{
|
||
|
DebugObject_Free(&o->d_obj);
|
||
|
|
||
|
// free buffers
|
||
|
BFree(o->frames_buffer);
|
||
|
|
||
|
// free chunks
|
||
|
BFree(o->frames_chunks);
|
||
|
|
||
|
// free frames
|
||
|
BFree(o->frames_entries);
|
||
|
|
||
|
// free input
|
||
|
PacketPassInterface_Free(&o->input);
|
||
|
}
|
||
|
|
||
|
PacketPassInterface * FragmentProtoAssembler_GetInput (FragmentProtoAssembler *o)
|
||
|
{
|
||
|
DebugObject_Access(&o->d_obj);
|
||
|
|
||
|
return &o->input;
|
||
|
}
|