diff --git a/include/freerdp/utils/ringbuffer.h b/include/freerdp/utils/ringbuffer.h new file mode 100644 index 000000000..099ba8ba1 --- /dev/null +++ b/include/freerdp/utils/ringbuffer.h @@ -0,0 +1,122 @@ +/** + * Copyright © 2014 Thincast Technologies GmbH + * Copyright © 2014 Hardening + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __RINGBUFFER_H___ +#define __RINGBUFFER_H___ + +#include + +/** @brief ring buffer meta data */ +struct _RingBuffer { + size_t initialSize; + size_t freeSize; + size_t size; + size_t readPtr; + size_t writePtr; + BYTE *buffer; +}; +typedef struct _RingBuffer RingBuffer; + + +/** @brief a piece of data in the ring buffer, exactly like a glibc iovec */ +struct _DataChunk { + size_t size; + const BYTE *data; +}; +typedef struct _DataChunk DataChunk; + +/** initialise a ringbuffer + * @param initialSize the initial capacity of the ringBuffer + * @return if the initialisation was successful + */ +BOOL ringbuffer_init(RingBuffer *rb, size_t initialSize); + +/** destroys internal data used by this ringbuffer + * @param ringbuffer + */ +void ringbuffer_destroy(RingBuffer *ringbuffer); + +/** computes the space used in this ringbuffer + * @param ringbuffer + * @return the number of bytes stored in that ringbuffer + */ +size_t ringbuffer_used(const RingBuffer *ringbuffer); + +/** returns the capacity of the ring buffer + * @param ringbuffer + * @return the capacity of this ring buffer + */ +size_t ringbuffer_capacity(const RingBuffer *ringbuffer); + +/** writes some bytes in the ringbuffer, if the data doesn't fit, the ringbuffer + * is resized automatically + * + * @param rb the ringbuffer + * @param ptr a pointer on the data to add + * @param sz the size of the data to add + * @return if the operation was successful, it could fail in case of OOM during realloc() + */ +BOOL ringbuffer_write(RingBuffer *rb, const void *ptr, size_t sz); + + +/** ensures that we have sz bytes available at the write head, and return a pointer + * on the write head + * + * @param rb the ring buffer + * @param sz the size to ensure + * @return a pointer on the write head, or NULL in case of OOM + */ +BYTE *ringbuffer_ensure_linear_write(RingBuffer *rb, size_t sz); + +/** move ahead the write head in case some byte were written directly by using + * a pointer retrieved via ringbuffer_ensure_linear_write(). This function is + * used to commit the written bytes. The provided size should not exceed the + * size ensured by ringbuffer_ensure_linear_write() + * + * @param rb the ring buffer + * @param sz the number of bytes that have been written + * @return if the operation was successful, FALSE is sz is too big + */ +BOOL ringbuffer_commit_written_bytes(RingBuffer *rb, size_t sz); + + +/** peeks the buffer chunks for sz bytes and returns how many chunks are filled. + * Note that the sum of the resulting chunks may be smaller than sz. + * + * @param rb the ringbuffer + * @param chunks an array of data chunks that will contain data / size of chunks + * @param sz the requested size + * @return the number of chunks used for reading sz bytes + */ +int ringbuffer_peek(const RingBuffer *rb, DataChunk chunks[2], size_t sz); + +/** move ahead the read head in case some byte were read using ringbuffer_peek() + * This function is used to commit the bytes that were effectively consumed. + * + * @param rb the ring buffer + * @param sz the + */ +void ringbuffer_commit_read_bytes(RingBuffer *rb, size_t sz); + + +#endif /* __RINGBUFFER_H___ */ diff --git a/libfreerdp/utils/CMakeLists.txt b/libfreerdp/utils/CMakeLists.txt index 716e96384..6e5858672 100644 --- a/libfreerdp/utils/CMakeLists.txt +++ b/libfreerdp/utils/CMakeLists.txt @@ -25,6 +25,7 @@ set(${MODULE_PREFIX}_SRCS pcap.c profiler.c rail.c + ringbuffer.c signal.c stopwatch.c svc_plugin.c @@ -68,3 +69,9 @@ else() endif() set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/libfreerdp") + + +if(BUILD_TESTING) + add_subdirectory(test) +endif() + diff --git a/libfreerdp/utils/ringbuffer.c b/libfreerdp/utils/ringbuffer.c new file mode 100644 index 000000000..04dcffef1 --- /dev/null +++ b/libfreerdp/utils/ringbuffer.c @@ -0,0 +1,250 @@ +/** + * Copyright © 2014 Thincast Technologies GmbH + * Copyright © 2014 Hardening + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + + +BOOL ringbuffer_init(RingBuffer *rb, size_t initialSize) +{ + rb->buffer = malloc(initialSize); + if (!rb->buffer) + return FALSE; + + rb->readPtr = rb->writePtr = 0; + rb->initialSize = rb->size = rb->freeSize = initialSize; + return TRUE; +} + + +size_t ringbuffer_used(const RingBuffer *ringbuffer) +{ + return ringbuffer->size - ringbuffer->freeSize; +} + +size_t ringbuffer_capacity(const RingBuffer *ringbuffer) +{ + return ringbuffer->size; +} + +void ringbuffer_destroy(RingBuffer *ringbuffer) +{ + free(ringbuffer->buffer); + ringbuffer->buffer = 0; +} + +static BOOL ringbuffer_realloc(RingBuffer *rb, size_t targetSize) +{ + BYTE *newData; + + if (rb->writePtr == rb->readPtr) + { + /* when no size is used we can realloc() and set the heads at the + * beginning of the buffer + */ + newData = (BYTE *)realloc(rb->buffer, targetSize); + if (!newData) + return FALSE; + rb->readPtr = rb->writePtr = 0; + } + else if ((rb->writePtr >= rb->readPtr) && (rb->writePtr < targetSize)) + { + /* we reallocate only if we're in that case, realloc don't touch read + * and write heads + * + * readPtr writePtr + * | | + * v v + * [............|XXXXXXXXXXXXXX|..........] + */ + newData = (BYTE *)realloc(rb->buffer, targetSize); + if (!newData) + return FALSE; + + rb->buffer = newData; + } + else + { + /* in case of malloc the read head is moved at the beginning of the new buffer + * and the write head is set accordingly + */ + newData = (BYTE *)malloc(targetSize); + if (!newData) + return FALSE; + if (rb->readPtr < rb->writePtr) + { + /* readPtr writePtr + * | | + * v v + * [............|XXXXXXXXXXXXXX|..........] + */ + memcpy(newData, rb->buffer + rb->readPtr, ringbuffer_used(rb)); + } + else + { + /* writePtr readPtr + * | | + * v v + * [XXXXXXXXXXXX|..............|XXXXXXXXXX] + */ + BYTE *dst = newData; + memcpy(dst, rb->buffer + rb->readPtr, rb->size - rb->readPtr); + dst += (rb->size - rb->readPtr); + if (rb->writePtr) + memcpy(dst, rb->buffer, rb->writePtr); + } + rb->writePtr = rb->size - rb->freeSize; + rb->readPtr = 0; + rb->buffer = newData; + } + + rb->freeSize += (targetSize - rb->size); + rb->size = targetSize; + return TRUE; +} + +/** + * + * @param rb + * @param ptr + * @param sz + * @return + */ +BOOL ringbuffer_write(RingBuffer *rb, const void *ptr, size_t sz) +{ + if ((rb->freeSize <= sz) && !ringbuffer_realloc(rb, rb->size + sz)) + return FALSE; + + /* the write could be split in two + * readHead writeHead + * | | + * v v + * [ ################ ] + */ + size_t toWrite = sz; + size_t remaining = sz; + if (rb->size - rb->writePtr < sz) + toWrite = rb->size - rb->writePtr; + + if (toWrite) + { + memcpy(rb->buffer + rb->writePtr, ptr, toWrite); + remaining -= toWrite; + ptr += toWrite; + } + + if (remaining) + memcpy(rb->buffer, ptr, remaining); + + rb->writePtr = (rb->writePtr + sz) % rb->size; + + rb->freeSize -= sz; + return TRUE; +} + + +BYTE *ringbuffer_ensure_linear_write(RingBuffer *rb, size_t sz) +{ + if (rb->freeSize < sz) + { + if (!ringbuffer_realloc(rb, rb->size + sz - rb->freeSize + 32)) + return NULL; + } + + if (rb->writePtr == rb->readPtr) + { + rb->writePtr = rb->readPtr = 0; + } + + if (rb->writePtr + sz < rb->size) + return rb->buffer + rb->writePtr; + + /* + * to add: ....... + * [ XXXXXXXXX ] + * + * result: + * [XXXXXXXXX....... ] + */ + memmove(rb->buffer, rb->buffer + rb->readPtr, rb->writePtr - rb->readPtr); + rb->readPtr = 0; + rb->writePtr = rb->size - rb->freeSize; + return rb->buffer + rb->writePtr; +} + +BOOL ringbuffer_commit_written_bytes(RingBuffer *rb, size_t sz) +{ + if (rb->writePtr + sz > rb->size) + return FALSE; + rb->writePtr = (rb->writePtr + sz) % rb->size; + rb->freeSize -= sz; + return TRUE; +} + +int ringbuffer_peek(const RingBuffer *rb, DataChunk chunks[2], size_t sz) +{ + size_t remaining = sz; + size_t toRead; + int chunkIndex = 0; + int ret = 0; + + if (rb->size - rb->freeSize < sz) + remaining = rb->size - rb->freeSize; + + toRead = remaining; + + if (rb->readPtr + remaining > rb->size) + toRead = rb->size - rb->readPtr; + + if (toRead) + { + chunks[0].data = rb->buffer + rb->readPtr; + chunks[0].size = toRead; + remaining -= toRead; + chunkIndex++; + ret++; + } + + if (remaining) + { + chunks[chunkIndex].data = rb->buffer; + chunks[chunkIndex].size = remaining; + ret++; + } + return ret; +} + +void ringbuffer_commit_read_bytes(RingBuffer *rb, size_t sz) +{ + assert(rb->size - rb->freeSize >= sz); + + rb->readPtr = (rb->readPtr + sz) % rb->size; + rb->freeSize += sz; + + /* when we reach a reasonable free size, we can go back to the original size */ + if ((rb->size != rb->initialSize) && (ringbuffer_used(rb) < rb->initialSize / 2)) + ringbuffer_realloc(rb, rb->initialSize); +} diff --git a/libfreerdp/utils/test/CMakeLists.txt b/libfreerdp/utils/test/CMakeLists.txt new file mode 100644 index 000000000..2e8dbf153 --- /dev/null +++ b/libfreerdp/utils/test/CMakeLists.txt @@ -0,0 +1,34 @@ + +set(MODULE_NAME "TestFreeRDPutils") +set(MODULE_PREFIX "TEST_FREERDP_UTILS") + +set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c) + +set(${MODULE_PREFIX}_TESTS + TestRingBuffer.c +) + +create_test_sourcelist(${MODULE_PREFIX}_SRCS + ${${MODULE_PREFIX}_DRIVER} + ${${MODULE_PREFIX}_TESTS} +) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS + MONOLITHIC ${MONOLITHIC_BUILD} + MODULE winpr + MODULES winpr-thread winpr-synch winpr-file winpr-utils winpr-crt freerdp-utils +) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test") + diff --git a/libfreerdp/utils/test/TestRingBuffer.c b/libfreerdp/utils/test/TestRingBuffer.c new file mode 100644 index 000000000..36cbaa559 --- /dev/null +++ b/libfreerdp/utils/test/TestRingBuffer.c @@ -0,0 +1,228 @@ +/** + * Copyright © 2014 Thincast Technologies GmbH + * Copyright © 2014 Hardening + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include + +BOOL test_overlaps(void) +{ + RingBuffer rb; + DataChunk chunks[2]; + BYTE bytes[200]; + int nchunks, i, j, k, counter = 0; + + for (i = 0; i < sizeof(bytes); i++) + bytes[i] = (BYTE)i; + + ringbuffer_init(&rb, 5); + if (!ringbuffer_write(&rb, bytes, 4)) /* [0123.] */ + goto error; + counter += 4; + ringbuffer_commit_read_bytes(&rb, 2); /* [..23.] */ + + if (!ringbuffer_write(&rb, &bytes[counter], 2)) /* [5.234] */ + goto error; + counter += 2; + + nchunks = ringbuffer_peek(&rb, chunks, 4); + if (nchunks != 2 || chunks[0].size != 3 || chunks[1].size != 1) + goto error; + + for (i = 0, j = 2; i < nchunks; i++) + { + for (k = 0; k < chunks[i].size; k++, j++) + { + if (chunks[i].data[k] != (BYTE)j) + goto error; + } + } + + ringbuffer_commit_read_bytes(&rb, 3); /* [5....] */ + if (ringbuffer_used(&rb) != 1) + goto error; + + if (!ringbuffer_write(&rb, &bytes[counter], 6)) /* [56789ab....] */ + goto error; + counter += 6; + + ringbuffer_commit_read_bytes(&rb, 6); /* [......b....] */ + nchunks = ringbuffer_peek(&rb, chunks, 10); + if (nchunks != 1 || chunks[0].size != 1 || (*chunks[0].data != 0xb)) + goto error; + + if (ringbuffer_capacity(&rb) != 5) + goto error; + + return TRUE; +error: + ringbuffer_destroy(&rb); + return FALSE; +} + + +int TestRingBuffer(int argc, char* argv[]) +{ + RingBuffer ringBuffer; + int testNo = 0; + BYTE *tmpBuf; + BYTE *rb_ptr; + int i/*, chunkNb, counter*/; + DataChunk chunks[2]; + + if (!ringbuffer_init(&ringBuffer, 10)) + { + fprintf(stderr, "unable to initialize ringbuffer\n"); + return -1; + } + + tmpBuf = (BYTE *)malloc(50); + if (!tmpBuf) + return -1; + + for (i = 0; i < 50; i++) + tmpBuf[i] = (char)i; + + fprintf(stderr, "%d: basic tests...", ++testNo); + if (!ringbuffer_write(&ringBuffer, tmpBuf, 5) || !ringbuffer_write(&ringBuffer, tmpBuf, 5) || + !ringbuffer_write(&ringBuffer, tmpBuf, 5)) + { + fprintf(stderr, "error when writing bytes\n"); + return -1; + } + + if (ringbuffer_used(&ringBuffer) != 15) + { + fprintf(stderr, "invalid used size got %d when i would expect 15\n", ringbuffer_used(&ringBuffer)); + return -1; + } + + if (ringbuffer_peek(&ringBuffer, chunks, 10) != 1 || chunks[0].size != 10) + { + fprintf(stderr, "error when reading bytes\n"); + return -1; + } + ringbuffer_commit_read_bytes(&ringBuffer, chunks[0].size); + + /* check retrieved bytes */ + for (i = 0; i < chunks[0].size; i++) + { + if (chunks[0].data[i] != i % 5) + { + fprintf(stderr, "invalid byte at %d, got %d instead of %d\n", i, chunks[0].data[i], i % 5); + return -1; + } + } + + if (ringbuffer_used(&ringBuffer) != 5) + { + fprintf(stderr, "invalid used size after read got %d when i would expect 5\n", ringbuffer_used(&ringBuffer)); + return -1; + } + + /* write some more bytes to have writePtr < readPtr and data splitted in 2 chunks */ + if (!ringbuffer_write(&ringBuffer, tmpBuf, 6) || + ringbuffer_peek(&ringBuffer, chunks, 11) != 2 || + chunks[0].size != 10 || + chunks[1].size != 1) + { + fprintf(stderr, "invalid read of splitted data\n"); + return -1; + } + + ringbuffer_commit_read_bytes(&ringBuffer, 11); + fprintf(stderr, "ok\n"); + + fprintf(stderr, "%d: peek with nothing to read...", ++testNo); + if (ringbuffer_peek(&ringBuffer, chunks, 10)) + { + fprintf(stderr, "peek returns some chunks\n"); + return -1; + } + fprintf(stderr, "ok\n"); + + fprintf(stderr, "%d: ensure_linear_write / read() shouldn't grow...", ++testNo); + for (i = 0; i < 1000; i++) + { + rb_ptr = ringbuffer_ensure_linear_write(&ringBuffer, 50); + if (!rb_ptr) + { + fprintf(stderr, "ringbuffer_ensure_linear_write() error\n"); + return -1; + } + + memcpy(rb_ptr, tmpBuf, 50); + + if (!ringbuffer_commit_written_bytes(&ringBuffer, 50)) + { + fprintf(stderr, "ringbuffer_commit_written_bytes() error, i=%d\n", i); + return -1; + } + + //ringbuffer_commit_read_bytes(&ringBuffer, 25); + } + + for (i = 0; i < 1000; i++) + ringbuffer_commit_read_bytes(&ringBuffer, 25); + + for (i = 0; i < 1000; i++) + ringbuffer_commit_read_bytes(&ringBuffer, 25); + + + if (ringbuffer_capacity(&ringBuffer) != 10) + { + fprintf(stderr, "not the expected capacity, have %d and expects 10\n", ringbuffer_capacity(&ringBuffer)); + return -1; + } + fprintf(stderr, "ok\n"); + + + fprintf(stderr, "%d: free size is correctly computed...", ++testNo); + for (i = 0; i < 1000; i++) + { + ringbuffer_ensure_linear_write(&ringBuffer, 50); + if (!ringbuffer_commit_written_bytes(&ringBuffer, 50)) + { + fprintf(stderr, "ringbuffer_commit_written_bytes() error, i=%d\n", i); + return -1; + } + } + ringbuffer_commit_read_bytes(&ringBuffer, 50 * 1000); + fprintf(stderr, "ok\n"); + + ringbuffer_destroy(&ringBuffer); + + fprintf(stderr, "%d: specific overlaps test...", ++testNo); + if (!test_overlaps()) + { + fprintf(stderr, "ko\n", i); + return -1; + } + fprintf(stderr, "ok\n"); + return 0; +} + + + +