diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5df113 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.dub +__test__*__ diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..aebdcf7 --- /dev/null +++ b/dub.json @@ -0,0 +1,17 @@ +{ + "name": "tanya", + "description": "D library with event loop", + "license": "MPL-2.0", + "copyright": "(c) Eugene Wissner ", + "authors": [ + "Eugene Wissner" + ], + + "targetType": "library", + + "configurations": [ + { + "name": "library" + } + ] +} diff --git a/source/tanya/container/buffer.d b/source/tanya/container/buffer.d new file mode 100644 index 0000000..3d7205b --- /dev/null +++ b/source/tanya/container/buffer.d @@ -0,0 +1,641 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.container.buffer; + +import tanya.memory; + +@nogc: + +version (unittest) +{ + private int fillBuffer(void* buffer, + in size_t size, + int start = 0, + int end = 10) + in + { + assert(start < end); + } + body + { + ubyte[] buf = cast(ubyte[]) buffer[0..size]; + auto numberRead = end - start; + + for (ubyte i; i < numberRead; ++i) + { + buf[i] = cast(ubyte) (start + i); + } + return numberRead; + } +} + +/** + * Interface for implemeting input/output buffers. + */ +interface Buffer +{ +@nogc: + /** + * Returns: The size of the internal buffer. + */ + @property size_t capacity() const @safe pure nothrow; + + /** + * Returns: Data size. + */ + @property size_t length() const @safe pure nothrow; + + /** + * Returns: Available space. + */ + @property size_t free() const @safe pure nothrow; + + /** + * Appends some data to the buffer. + * + * Params: + * buffer = Buffer chunk got with $(D_PSYMBOL buffer). + */ + Buffer opOpAssign(string op)(void[] buffer) + if (op == "~"); +} + +/** + * Buffer that can be used with C functions accepting void pointer and + * returning the number of the read bytes. + */ +class ReadBuffer : Buffer +{ +@nogc: + /// Internal buffer. + protected ubyte[] _buffer; + + /// Filled buffer length. + protected size_t _length; + + /// Available space. + protected immutable size_t minAvailable; + + /// Size by which the buffer will grow. + protected immutable size_t blockSize; + + private Allocator allocator; + + invariant + { + assert(_length <= _buffer.length); + assert(blockSize > 0); + assert(minAvailable > 0); + } + + /** + * Params: + * size = Initial buffer size and the size by which the buffer + * will grow. + * minAvailable = minimal size should be always available to fill. + * So it will reallocate if $(D_INLINECODE + * $(D_PSYMBOL free) < $(D_PARAM minAvailable) + * ). + */ + this(size_t size = 8192, + size_t minAvailable = 1024, + Allocator allocator = defaultAllocator) + { + this.allocator = allocator; + this.minAvailable = minAvailable; + this.blockSize = size; + resizeArray!ubyte(this.allocator, _buffer, size); + } + + /** + * Deallocates the internal buffer. + */ + ~this() + { + finalize(allocator, _buffer); + } + + /// + unittest + { + auto b = make!ReadBuffer(defaultAllocator); + assert(b.capacity == 8192); + assert(b.length == 0); + + finalize(defaultAllocator, b); + } + + /** + * Returns: The size of the internal buffer. + */ + @property size_t capacity() const @safe pure nothrow + { + return _buffer.length; + } + + /** + * Returns: Data size. + */ + @property size_t length() const @safe pure nothrow + { + return _length; + } + + /** + * Returns: Available space. + */ + @property size_t free() const @safe pure nothrow + { + return capacity - length; + } + + /// + unittest + { + auto b = make!ReadBuffer(defaultAllocator); + size_t numberRead; + void* buf; + + // Fills the buffer with values 0..10 + assert(b.free == b.blockSize); + buf = b.buffer; + numberRead = fillBuffer(buf, b.free, 0, 10); + b ~= buf[0..numberRead]; + assert(b.free == b.blockSize - numberRead); + b[]; + assert(b.free == b.blockSize); + + finalize(defaultAllocator, b); + } + + /** + * Returns a pointer to a chunk of the internal buffer. You can pass it to + * a function that requires such a buffer. + * + * Set the buffer again after reading something into it. Append + * $(D_KEYWORD ~=) a slice from the beginning of the buffer you got and + * till the number of the read bytes. The data will be appended to the + * existing buffer. + * + * Returns: A chunk of available buffer. + */ + @property void* buffer() + { + if (capacity - length < minAvailable) + { + resizeArray!ubyte(this.allocator, _buffer, capacity + blockSize); + } + return _buffer[_length..$].ptr; + } + + /** + * Appends some data to the buffer. Use only the buffer you got + * with $(D_PSYMBOL buffer)! + * + * Params: + * buffer = Buffer chunk got with $(D_PSYMBOL buffer). + */ + ReadBuffer opOpAssign(string op)(void[] buffer) + if (op == "~") + { + _length += buffer.length; + return this; + } + + /// + unittest + { + auto b = make!ReadBuffer(defaultAllocator); + size_t numberRead; + void* buf; + ubyte[] result; + + // Fills the buffer with values 0..10 + buf = b.buffer; + numberRead = fillBuffer(buf, b.free, 0, 10); + b ~= buf[0..numberRead]; + + result = b[]; + assert(result[0] == 0); + assert(result[1] == 1); + assert(result[9] == 9); + + // It shouldn't overwrite, but append another 5 bytes to the buffer + buf = b.buffer; + numberRead = fillBuffer(buf, b.free, 0, 10); + b ~= buf[0..numberRead]; + + buf = b.buffer; + numberRead = fillBuffer(buf, b.free, 20, 25); + b ~= buf[0..numberRead]; + + result = b[]; + assert(result[0] == 0); + assert(result[1] == 1); + assert(result[9] == 9); + assert(result[10] == 20); + assert(result[14] == 24); + + finalize(defaultAllocator, b); + } + + /** + * Returns the buffer. The buffer is cleared after that. So you can get it + * only one time. + * + * Returns: The buffer as array. + */ + @property ubyte[] opIndex() + { + auto ret = _buffer[0.._length]; + _length = 0; + return ret; + } + + /// + unittest + { + auto b = make!ReadBuffer(defaultAllocator); + size_t numberRead; + void* buf; + ubyte[] result; + + // Fills the buffer with values 0..10 + buf = b.buffer; + numberRead = fillBuffer(buf, b.free, 0, 10); + b ~= buf[0..numberRead]; + + assert(b.length == 10); + result = b[]; + assert(result[0] == 0); + assert(result[9] == 9); + assert(b.length == 0); + + finalize(defaultAllocator, b); + } +} + +/** + * Circular, self-expanding buffer that can be used with C functions accepting + * void pointer and returning the number of the read bytes. + * + * The buffer is optimized for situations where you read all the data from it + * at once (without writing to it occasionally). It can become ineffective if + * you permanently keep some data in the buffer and alternate writing and + * reading, because it may allocate and move elements. + */ +class WriteBuffer : Buffer +{ +@nogc: + /// Internal buffer. + protected ubyte[] _buffer; + + /// Buffer start position. + protected size_t start; + + /// Buffer ring area size. After this position begins buffer overflow area. + protected size_t ring; + + /// Size by which the buffer will grow. + protected immutable size_t blockSize; + + /// The position of the free area in the buffer. + protected size_t position; + + private Allocator allocator; + + invariant + { + assert(blockSize > 0); + // position can refer to an element outside the buffer if the buffer is full. + assert(position <= _buffer.length); + } + + /** + * Params: + * size = Initial buffer size and the size by which the buffer + * will grow. + */ + this(size_t size = 8192, + Allocator allocator = defaultAllocator) + { + this.allocator = allocator; + blockSize = size; + ring = size - 1; + resizeArray!ubyte(this.allocator, _buffer, size); + } + + /** + * Deallocates the internal buffer. + */ + ~this() + { + finalize(allocator, _buffer); + } + + /** + * Returns: The size of the internal buffer. + */ + @property size_t capacity() const @safe pure nothrow + { + return _buffer.length; + } + + /** + * Note that $(D_PSYMBOL length) doesn't return the real length of the data, + * but only the array length that will be returned with $(D_PSYMBOL buffer) + * next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_PSYMBOL written) + * until $(D_PSYMBOL length) returns 0. + * + * Returns: Data size. + */ + @property size_t length() const @safe pure nothrow + { + if (position > ring || position < start) // Buffer overflowed + { + return ring - start + 1; + } + else + { + return position - start; + } + } + + /// + unittest + { + auto b = make!WriteBuffer(defaultAllocator, 4); + ubyte[3] buf = [48, 23, 255]; + + b ~= buf; + assert(b.length == 3); + b.written = 2; + assert(b.length == 1); + + b ~= buf; + assert(b.length == 2); + b.written = 2; + assert(b.length == 2); + + b ~= buf; + assert(b.length == 5); + b.written = b.length; + assert(b.length == 0); + + finalize(defaultAllocator, b); + } + + /** + * Returns: Available space. + */ + @property size_t free() const @safe pure nothrow + { + return capacity - length; + } + + /** + * Appends data to the buffer. + * + * Params: + * buffer = Buffer chunk got with $(D_PSYMBOL buffer). + */ + WriteBuffer opOpAssign(string op)(ubyte[] buffer) + if (op == "~") + { + size_t end, start; + + if (position >= this.start && position <= ring) + { + auto afterRing = ring + 1; + + end = position + buffer.length; + if (end > afterRing) + { + end = afterRing; + } + start = end - position; + _buffer[position..end] = buffer[0..start]; + if (end == afterRing) + { + position = this.start == 0 ? afterRing : 0; + } + else + { + position = end; + } + } + + // Check if we have some free space at the beginning + if (start < buffer.length && position < this.start) + { + end = position + buffer.length - start; + if (end > this.start) + { + end = this.start; + } + auto areaEnd = end - position + start; + _buffer[position..end] = buffer[start..areaEnd]; + position = end == this.start ? ring + 1 : end - position; + start = areaEnd; + } + + // And if we still haven't found any place, save the rest in the overflow area + if (start < buffer.length) + { + end = position + buffer.length - start; + if (end > capacity) + { + auto newSize = end / blockSize * blockSize + blockSize; + + resizeArray!ubyte(this.allocator, _buffer, newSize); + } + _buffer[position..end] = buffer[start..$]; + position = end; + if (this.start == 0) + { + ring = capacity - 1; + } + } + + return this; + } + + /// + unittest + { + auto b = make!WriteBuffer(defaultAllocator, 4); + ubyte[3] buf = [48, 23, 255]; + + b ~= buf; + assert(b.capacity == 4); + assert(b._buffer[0] == 48 && b._buffer[1] == 23 && b._buffer[2] == 255); + + b.written = 2; + b ~= buf; + assert(b.capacity == 4); + assert(b._buffer[0] == 23 && b._buffer[1] == 255 + && b._buffer[2] == 255 && b._buffer[3] == 48); + + b.written = 2; + b ~= buf; + assert(b.capacity == 8); + assert(b._buffer[0] == 23 && b._buffer[1] == 255 + && b._buffer[2] == 48 && b._buffer[3] == 23 && b._buffer[4] == 255); + + finalize(defaultAllocator, b); + + b = make!WriteBuffer(defaultAllocator, 2); + + b ~= buf; + assert(b.start == 0); + assert(b.capacity == 4); + assert(b.ring == 3); + assert(b.position == 3); + + finalize(defaultAllocator, b); + } + + /** + * Sets how many bytes were written. It will shrink the buffer + * appropriately. Always set this property after calling + * $(D_PSYMBOL buffer). + * + * Params: + * length = Length of the written data. + */ + @property void written(size_t length) @safe pure nothrow + in + { + assert(length <= this.length); + } + body + { + auto afterRing = ring + 1; + auto oldStart = start; + + if (length <= 0) + { + return; + } + else if (position <= afterRing) + { + start += length; + if (start > 0 && position == afterRing) + { + position = oldStart; + } + } + else + { + auto overflow = position - afterRing; + + if (overflow > length) { + _buffer[start.. start + length] = _buffer[afterRing.. afterRing + length]; + _buffer[afterRing.. afterRing + length] = _buffer[afterRing + length ..position]; + position -= length; + } + else if (overflow == length) + { + _buffer[start.. start + overflow] = _buffer[afterRing..position]; + position -= overflow; + } + else + { + _buffer[start.. start + overflow] = _buffer[afterRing..position]; + position = overflow; + } + start += length; + + if (start == position) + { + if (position != afterRing) + { + position = 0; + } + start = 0; + ring = capacity - 1; + } + } + if (start > ring) + { + start = 0; + } + } + + /// + unittest + { + auto b = make!WriteBuffer(defaultAllocator); + ubyte[6] buf = [23, 23, 255, 128, 127, 9]; + + b ~= buf; + assert(b.length == 6); + b.written = 2; + assert(b.length == 4); + b.written = 4; + assert(b.length == 0); + + finalize(defaultAllocator, b); + } + + /** + * Returns a pointer to a buffer chunk with data. You can pass it to + * a function that requires such a buffer. + * + * After calling it, set $(D_PSYMBOL written) to the length could be + * written. + * + * $(D_PSYMBOL buffer) may return only part of the data. You may need + * to call it (and set $(D_PSYMBOL written) several times until + * $(D_PSYMBOL length) is 0. If all the data can be written, + * maximally 3 calls are required. + * + * Returns: A chunk of data buffer. + */ + @property void* buffer() @safe pure nothrow + { + if (position > ring || position < start) // Buffer overflowed + { + return _buffer[start.. ring + 1].ptr; + } + else + { + return _buffer[start..position].ptr; + } + } + + /// + unittest + { + auto b = make!WriteBuffer(defaultAllocator, 6); + ubyte[6] buf = [23, 23, 255, 128, 127, 9]; + void* returnedBuf; + + b ~= buf; + returnedBuf = b.buffer; + assert(returnedBuf[0..b.length] == buf[0..6]); + b.written = 2; + + returnedBuf = b.buffer; + assert(returnedBuf[0..b.length] == buf[2..6]); + + b ~= buf; + returnedBuf = b.buffer; + assert(returnedBuf[0..b.length] == buf[2..6]); + b.written = b.length; + + returnedBuf = b.buffer; + assert(returnedBuf[0..b.length] == buf[0..6]); + b.written = b.length; + + finalize(defaultAllocator, b); + } +} diff --git a/source/tanya/container/list.d b/source/tanya/container/list.d new file mode 100644 index 0000000..e6e4ffc --- /dev/null +++ b/source/tanya/container/list.d @@ -0,0 +1,405 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.container.list; + +import tanya.memory; + +/** + * Singly linked list. + * + * Params: + * T = Content type. + */ +class SList(T) +{ +@nogc: + /** + * Creates a new $(D_PSYMBOL SList). + * + * Params: + * allocator = The allocator should be used for the element + * allocations. + */ + this(Allocator allocator = defaultAllocator) + { + this.allocator = allocator; + reset(); + } + + /** + * Removes all elements from the list. + */ + ~this() + { + while (!empty) + { + static if (isFinalizable!T) + { + finalize(allocator, front); + } + popFront(); + } + } + + /** + * Returns: First element. + */ + @property ref T front() + in + { + assert(!empty); + } + body + { + return first.next.content; + } + + /** + * Inserts a new element at the beginning. + * + * Params: + * x = New element. + */ + @property void front(T x) + { + Entry* temp = make!Entry(allocator); + + temp.content = x; + temp.next = first.next; + first.next = temp; + } + + /// + unittest + { + auto l = make!(SList!int)(defaultAllocator); + int[2] values = [8, 9]; + + l.front = values[0]; + assert(l.front == values[0]); + l.front = values[1]; + assert(l.front == values[1]); + + finalize(defaultAllocator, l); + } + + /** + * Inserts a new element at the beginning. + * + * Params: + * x = New element. + * + * Returns: $(D_KEYWORD this). + */ + typeof(this) opOpAssign(string Op)(ref T x) + if (Op == "~") + { + front = x; + return this; + } + + /// + unittest + { + auto l = make!(SList!int)(defaultAllocator); + int value = 5; + + assert(l.empty); + + l ~= value; + + assert(l.front == value); + assert(!l.empty); + + finalize(defaultAllocator, l); + } + + /** + * Returns: $(D_KEYWORD true) if the list is empty. + */ + @property bool empty() const @safe pure nothrow + { + return first.next is null; + } + + /** + * Returns the first element and moves to the next one. + * + * Returns: The first element. + */ + T popFront() + in + { + assert(!empty); + } + body + { + auto n = first.next.next; + auto content = first.next.content; + + finalize(allocator, first.next); + first.next = n; + + return content; + } + + /// + unittest + { + auto l = make!(SList!int)(defaultAllocator); + int[2] values = [8, 9]; + + l.front = values[0]; + l.front = values[1]; + assert(l.front == values[1]); + l.popFront(); + assert(l.front == values[0]); + + finalize(defaultAllocator, l); + } + + /** + * Returns the current item from the list and removes from the list. + * + * Params: + * x = The item should be removed. + * + * Returns: Removed item. + */ + T remove() + in + { + assert(!empty); + } + body + { + auto temp = position.next.next; + auto content = position.next.content; + + finalize(allocator, position.next); + position.next = temp; + + return content; + } + + /// + unittest + { + auto l = make!(SList!int)(defaultAllocator); + int[3] values = [8, 5, 4]; + + l.front = values[0]; + l.front = values[1]; + assert(l.remove() == 5); + l.front = values[2]; + assert(l.remove() == 4); + assert(l.remove() == 8); + assert(l.empty); + + finalize(defaultAllocator, l); + } + + /** + * Resets the current position. + * + * Returns: $(D_KEYWORD this). + */ + typeof(this) reset() + { + position = &first; + return this; + } + + /// + unittest + { + auto l = make!(SList!int)(defaultAllocator); + int[2] values = [8, 5]; + + l.current = values[0]; + l.current = values[1]; + assert(l.current == 5); + l.advance(); + assert(l.current == 8); + l.reset(); + assert(l.current == 5); + + finalize(defaultAllocator, l); + } + + /** + * $(D_KEYWORD foreach) iteration. + * + * Params: + * dg = $(D_KEYWORD foreach) body. + */ + int opApply(int delegate(ref size_t i, ref T) @nogc dg) + { + int result; + size_t i; + + for (position = first.next; position; position = position.next, ++i) + { + result = dg(i, position.content); + + if (result != 0) + { + return result; + } + } + reset(); + + return result; + } + + /// + unittest + { + auto l = make!(SList!int)(defaultAllocator); + int[3] values = [5, 4, 9]; + + l.front = values[0]; + l.front = values[1]; + l.front = values[2]; + foreach (i, e; l) + { + assert(i != 0 || e == values[2]); + assert(i != 1 || e == values[1]); + assert(i != 2 || e == values[0]); + } + + finalize(defaultAllocator, l); + } + + /// Ditto. + int opApply(int delegate(ref T) @nogc dg) + { + int result; + + for (position = first.next; position; position = position.next) + { + result = dg(position.content); + + if (result != 0) + { + return result; + } + } + reset(); + + return result; + } + + /// + unittest + { + auto l = make!(SList!int)(defaultAllocator); + int[3] values = [5, 4, 9]; + size_t i; + + l.front = values[0]; + l.front = values[1]; + l.front = values[2]; + foreach (e; l) + { + assert(i != 0 || e == values[2]); + assert(i != 1 || e == values[1]); + assert(i != 2 || e == values[0]); + ++i; + } + + finalize(defaultAllocator, l); + } + + /** + * Returns: $(D_KEYWORD true) if the current position is the end position. + */ + @property bool end() const + { + return empty || position.next.next is null; + } + + /** + * Moves to the next element and returns it. + * + * Returns: The element on the next position. + */ + T advance() + in + { + assert(!end); + } + body + { + position = position.next; + return position.content; + } + + /** + * Returns: Element on the current position. + */ + @property ref T current() + in + { + assert(!empty); + } + body + { + return position.next.content; + } + + /** + * Inserts a new element at the current position. + * + * Params: + * x = New element. + */ + @property void current(T x) + { + Entry* temp = make!Entry(allocator); + + temp.content = x; + temp.next = position.next; + position.next = temp; + } + + /** + * List entry. + */ + protected struct Entry + { + /// List item content. + T content; + + /// Next list item. + Entry* next; + } + + /// 0th element of the list. + protected Entry first; + + /// Current position in the list. + protected Entry* position; + + private Allocator allocator; +} + +interface Stuff +{ +} + +/// +unittest +{ + auto l = make!(SList!Stuff)(defaultAllocator); + + finalize(defaultAllocator, l); +} diff --git a/source/tanya/container/package.d b/source/tanya/container/package.d new file mode 100644 index 0000000..eba95a7 --- /dev/null +++ b/source/tanya/container/package.d @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.container; + +public import tanya.container.buffer; +public import tanya.container.list; +public import tanya.container.vector; +public import tanya.container.queue; diff --git a/source/tanya/container/queue.d b/source/tanya/container/queue.d new file mode 100644 index 0000000..0890bcf --- /dev/null +++ b/source/tanya/container/queue.d @@ -0,0 +1,219 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.container.queue; + +import tanya.memory; + +/** + * Queue. + * + * Params: + * T = Content type. + */ +class Queue(T) +{ +@nogc: + /** + * Creates a new $(D_PSYMBOL Queue). + * + * Params: + * allocator = The allocator should be used for the element + * allocations. + */ + this(Allocator allocator = defaultAllocator) + { + this.allocator = allocator; + } + + /** + * Removes all elements from the queue. + */ + ~this() + { + foreach (e; this) + { + static if (isFinalizable!T) + { + finalize(allocator, e); + } + } + } + + /** + * Returns: First element. + */ + @property ref T front() + in + { + assert(!empty); + } + body + { + return first.next.content; + } + + /** + * Inserts a new element. + * + * Params: + * x = New element. + * + * Returns: $(D_KEYWORD this). + */ + typeof(this) insertBack(T x) + { + Entry* temp = make!Entry(allocator); + + temp.content = x; + + if (empty) + { + first.next = rear = temp; + } + else + { + rear.next = temp; + rear = rear.next; + } + + return this; + } + + alias insert = insertBack; + + /// + unittest + { + auto q = make!(Queue!int)(defaultAllocator); + int[2] values = [8, 9]; + + q.insertBack(values[0]); + assert(q.front is values[0]); + q.insertBack(values[1]); + assert(q.front is values[0]); + + finalize(defaultAllocator, q); + } + + /** + * Inserts a new element. + * + * Params: + * x = New element. + * + * Returns: $(D_KEYWORD this). + */ + typeof(this) opOpAssign(string Op)(ref T x) + if (Op == "~") + { + return insertBack(x); + } + + /// + unittest + { + auto q = make!(Queue!int)(defaultAllocator); + int value = 5; + + assert(q.empty); + + q ~= value; + + assert(q.front == value); + assert(!q.empty); + + finalize(defaultAllocator, q); + } + + /** + * Returns: $(D_KEYWORD true) if the queue is empty. + */ + @property bool empty() const @safe pure nothrow + { + return first.next is null; + } + + /// + unittest + { + auto q = make!(Queue!int)(defaultAllocator); + int value = 7; + + assert(q.empty); + q.insertBack(value); + assert(!q.empty); + + finalize(defaultAllocator, q); + } + + /** + * Move position to the next element. + * + * Returns: $(D_KEYWORD this). + */ + typeof(this) popFront() + in + { + assert(!empty); + } + body + { + auto n = first.next.next; + + finalize(allocator, first.next); + first.next = n; + + return this; + } + + /// + unittest + { + auto q = make!(Queue!int)(defaultAllocator); + int[2] values = [8, 9]; + + q.insertBack(values[0]); + q.insertBack(values[1]); + assert(q.front is values[0]); + q.popFront(); + assert(q.front is values[1]); + + finalize(defaultAllocator, q); + } + + /** + * Queue entry. + */ + protected struct Entry + { + /// Queue item content. + T content; + + /// Next list item. + Entry* next; + } + + /// The first element of the list. + protected Entry first; + + /// The last element of the list. + protected Entry* rear; + + private Allocator allocator; +} + +/// +unittest +{ + auto q = make!(Queue!int)(defaultAllocator); + + finalize(defaultAllocator, q); +} diff --git a/source/tanya/container/vector.d b/source/tanya/container/vector.d new file mode 100644 index 0000000..4f66dda --- /dev/null +++ b/source/tanya/container/vector.d @@ -0,0 +1,420 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.container.vector; + +import tanya.memory; + +@nogc: + +/** + * One dimensional array. It allocates automatically if needed. + * + * If you assign a value: + * --- + * auto v = make!(Vector!int)(defaultAllocator); + * int value = 5; + * + * v[1000] = value; + * + * finalize(defaultAllocator, v); + * --- + * it will allocate not only for one, but for 1000 elements. So this + * implementation is more suitable for sequential data with random access. + * + * Params: + * T = Content type. + */ +class Vector(T) +{ +@nogc: + /** + * Creates a new $(D_PSYMBOL Vector). + * + * Params: + * length = Initial length. + * allocator = The allocator should be used for the element + * allocations. + */ + this(size_t length, Allocator allocator = defaultAllocator) + { + this.allocator = allocator; + vector = makeArray!T(allocator, length); + } + + /// Ditto. + this(Allocator allocator = defaultAllocator) + { + this(0, allocator); + } + + /** + * Removes all elements from the vector. + */ + ~this() + { + finalize(allocator, vector); + } + + /** + * Returns: Vector length. + */ + @property size_t length() const + { + return vector.length; + } + + /** + * Expans/shrinks the vector. + * + * Params: + * length = New length. + */ + @property void length(size_t length) + { + resizeArray!T(allocator, vector, length); + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator); + + v.length = 5; + assert(v.length == 5); + + // TODO + v.length = 7; + assert(v.length == 7); + + v.length = 0; + assert(v.length == 0); + + finalize(defaultAllocator, v); + } + + /** + * Returns: $(D_KEYWORD true) if the vector is empty. + */ + @property bool empty() const + { + return length == 0; + } + + static if (isFinalizable!T) + { + /** + * Removes an elements from the vector. + * + * Params: + * pos = Element index. + */ + void remove(size_t pos) + { + auto el = vector[pos]; + finalize(allocator, el); + } + } + + /** + * Assigns a value. Allocates if needed. + * + * Params: + * value = Value. + * + * Returns: Assigned value. + */ + T opIndexAssign(T value, size_t pos) + { + if (pos >= length) + { + resizeArray!T(allocator, vector, pos + 1); + } + return vector[pos] = value; + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator); + int[2] values = [5, 15]; + + assert(v.length == 0); + v[1] = values[0]; + assert(v.length == 2); + v[3] = values[0]; + assert(v.length == 4); + v[4] = values[1]; + assert(v.length == 5); + + finalize(defaultAllocator, v); + } + + /** + * Returns: The value on index $(D_PARAM pos). + */ + ref T opIndex(in size_t pos) + in + { + assert(length > pos); + } + body + { + return vector[pos]; + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator); + int[2] values = [5, 15]; + + v[1] = values[0]; + assert(v[1] is values[0]); + v[3] = values[0]; + assert(v[3] is values[0]); + v[4] = values[1]; + assert(v[4] is values[1]); + v[0] = values[1]; + assert(v[0] is values[1]); + + finalize(defaultAllocator, v); + } + + /** + * $(D_KEYWORD foreach) iteration. + * + * Params: + * dg = $(D_KEYWORD foreach) body. + */ + int opApply(int delegate(ref T) @nogc dg) + { + int result; + + foreach (e; vector) + { + result = dg(e); + + if (result != 0) + { + return result; + } + } + return result; + } + + /// Ditto. + int opApply(int delegate(ref size_t i, ref T) @nogc dg) + { + int result; + + foreach (i, e; vector) + { + result = dg(i, e); + + if (result != 0) + { + return result; + } + } + return result; + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator, 1); + int[3] values = [5, 15, 8]; + + v[0] = values[0]; + v[1] = values[1]; + v[2] = values[2]; + + int i; + foreach (e; v) + { + assert(i != 0 || e is values[0]); + assert(i != 1 || e is values[1]); + assert(i != 2 || e is values[2]); + ++i; + } + + foreach (j, e; v) + { + assert(j != 0 || e is values[0]); + assert(j != 1 || e is values[1]); + assert(j != 2 || e is values[2]); + } + + finalize(defaultAllocator, v); + } + + /** + * Sets the first element. Allocates if the vector is empty. + * + * Params: + * x = New element. + */ + @property void front(ref T x) + { + this[0] = x; + } + + /** + * Returns: The first element. + */ + @property ref inout(T) front() inout + in + { + assert(!empty); + } + body + { + return vector[0]; + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator, 1); + int[2] values = [5, 15]; + + v.front = values[0]; + assert(v.front == 5); + + v.front = values[1]; + assert(v.front == 15); + + finalize(defaultAllocator, v); + } + + /** + * Move position to the next element. + * + * Returns: $(D_KEYWORD this). + */ + typeof(this) popFront() + in + { + assert(!empty); + } + body + { + vector[0 .. $ - 1] = vector[1..$]; + resizeArray(allocator, vector, length - 1); + return this; + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator, 1); + int[2] values = [5, 15]; + + v[0] = values[0]; + v[1] = values[1]; + assert(v.front is values[0]); + assert(v.length == 2); + v.popFront(); + assert(v.front is values[1]); + assert(v.length == 1); + v.popFront(); + assert(v.empty); + + finalize(defaultAllocator, v); + } + + /** + * Sets the last element. Allocates if the vector is empty. + * + * Params: + * x = New element. + */ + @property void back(ref T x) + { + vector[empty ? 0 : $ - 1] = x; + } + + /** + * Returns: The last element. + */ + @property ref inout(T) back() inout + in + { + assert(!empty); + } + body + { + return vector[$ - 1]; + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator, 1); + int[2] values = [5, 15]; + + v.back = values[0]; + assert(v.back == 5); + + v.back = values[1]; + assert(v.back == 15); + + finalize(defaultAllocator, v); + } + + /** + * Move position to the previous element. + * + * Returns: $(D_KEYWORD this). + */ + typeof(this) popBack() + in + { + assert(!empty); + } + body + { + resizeArray(allocator, vector, length - 1); + return this; + } + + /// + unittest + { + auto v = make!(Vector!int)(defaultAllocator, 1); + int[2] values = [5, 15]; + + v[0] = values[0]; + v[1] = values[1]; + assert(v.back is values[1]); + assert(v.length == 2); + v.popBack(); + assert(v.back is values[0]); + assert(v.length == 1); + v.popBack(); + assert(v.empty); + + finalize(defaultAllocator, v); + } + + /// Container. + protected T[] vector; + + private Allocator allocator; +} + +/// +unittest +{ + auto v = make!(Vector!int)(defaultAllocator); + + finalize(defaultAllocator, v); +} diff --git a/source/tanya/crypto/padding.d b/source/tanya/crypto/padding.d new file mode 100644 index 0000000..fbf7ec5 --- /dev/null +++ b/source/tanya/crypto/padding.d @@ -0,0 +1,191 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.crypto.padding; + +import tanya.memory; +import std.algorithm.iteration; +import std.typecons; + +@nogc: + +/** + * Supported padding mode. + * + * See_Also: + * $(D_PSYMBOL applyPadding) + */ +enum Mode +{ + zero, + pkcs7, + ansiX923, +} + +/** + * Params: + * allocator = Allocator that should be used if the block should be extended + * or a new block should be added. + * input = Sequence that should be padded. + * mode = Padding mode. + * blockSize = Block size. + * + * Returns: The functions modifies the initial array and returns it. + * + * See_Also: + * $(D_PSYMBOL Mode) + */ +ubyte[] applyPadding(ref ubyte[] input, + in Mode mode, + in ushort blockSize, + Allocator allocator = defaultAllocator) +in +{ + assert(blockSize > 0 && blockSize <= 256); + assert(blockSize % 64 == 0); + assert(input.length > 0); +} +body +{ + immutable rest = cast(ubyte) input.length % blockSize; + immutable size_t lastBlock = input.length - (rest > 0 ? rest : blockSize); + immutable needed = cast(ubyte) (rest > 0 ? blockSize - rest : 0); + + final switch (mode) with (Mode) + { + case zero: + allocator.expandArray(input, needed); + break; + case pkcs7: + if (needed) + { + allocator.expandArray(input, needed); + input[input.length - needed ..$].each!((ref e) => e = needed); + } + else + { + allocator.expandArray(input, blockSize); + } + break; + case ansiX923: + allocator.expandArray(input, needed ? needed : blockSize); + input[$ - 1] = needed; + break; + } + + return input; +} + +/// +unittest +{ + { // Zeros + auto input = defaultAllocator.makeArray!ubyte(50); + + applyPadding(input, Mode.zero, 64); + assert(input.length == 64); + + applyPadding(input, Mode.zero, 64); + assert(input.length == 64); + assert(input[63] == 0); + + defaultAllocator.finalize(input); + } + { // PKCS#7 + auto input = defaultAllocator.makeArray!ubyte(50); + for (ubyte i; i < 40; ++i) + { + input[i] = i; + } + + applyPadding(input, Mode.pkcs7, 64); + assert(input.length == 64); + for (ubyte i; i < 64; ++i) + { + if (i >= 40 && i < 50) + { + assert(input[i] == 0); + } + else if (i >= 50) + { + assert(input[i] == 14); + } + else + { + assert(input[i] == i); + } + } + + applyPadding(input, Mode.pkcs7, 64); + assert(input.length == 128); + for (ubyte i; i < 128; ++i) + { + if (i >= 64 || (i >= 40 && i < 50)) + { + assert(input[i] == 0); + } + else if (i >= 50 && i < 64) + { + assert(input[i] == 14); + } + else + { + assert(input[i] == i); + } + } + + defaultAllocator.finalize(input); + } + { // ANSI X.923 + auto input = defaultAllocator.makeArray!ubyte(50); + for (ubyte i; i < 40; ++i) + { + input[i] = i; + } + + applyPadding(input, Mode.ansiX923, 64); + assert(input.length == 64); + for (ubyte i; i < 64; ++i) + { + if (i < 40) + { + assert(input[i] == i); + } + else if (i == 63) + { + assert(input[i] == 14); + } + else + { + assert(input[i] == 0); + } + } + + applyPadding(input, Mode.pkcs7, 64); + assert(input.length == 128); + for (ubyte i = 0; i < 128; ++i) + { + if (i < 40) + { + assert(input[i] == i); + } + else if (i == 63) + { + assert(input[i] == 14); + } + else + { + assert(input[i] == 0); + } + } + + defaultAllocator.finalize(input); + } +} diff --git a/source/tanya/event/config.d b/source/tanya/event/config.d new file mode 100644 index 0000000..11a432a --- /dev/null +++ b/source/tanya/event/config.d @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event.config; + +package version (DisableBackends) +{ +} +else +{ + version (linux) + { + enum UseEpoll = true; + } + else + { + enum UseEpoll = false; + } +} diff --git a/source/tanya/event/internal/epoll.d b/source/tanya/event/internal/epoll.d new file mode 100644 index 0000000..f4e99d9 --- /dev/null +++ b/source/tanya/event/internal/epoll.d @@ -0,0 +1,226 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event.internal.epoll; + +import tanya.event.config; + +static if (UseEpoll): + +public import core.sys.linux.epoll; +import tanya.event.internal.selector; +import tanya.event.protocol; +import tanya.event.transport; +import tanya.event.watcher; +import tanya.event.loop; +import tanya.container.list; +import tanya.memory; +import core.stdc.errno; +import core.sys.posix.fcntl; +import core.sys.posix.netinet.in_; +import core.time; +import std.algorithm.comparison; + +@nogc: + +extern (C) nothrow +{ // TODO: Make a pull request for Phobos to mark this extern functions as @nogc. + int epoll_create1(int __flags); + int epoll_ctl(int __epfd, int __op, int __fd, epoll_event *__event); + int epoll_wait(int __epfd, epoll_event *__events, int __maxevents, int __timeout); + int accept4(int, sockaddr*, socklen_t*, int flags); +} + +private enum maxEvents = 128; + +class EpollLoop : Loop +{ +@nogc: + /** + * Initializes the loop. + */ + this() + { + super(); + + if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + { + return; + } + epollEvents = makeArray!epoll_event(defaultAllocator, maxEvents).ptr; + } + + /** + * Frees loop internals. + */ + ~this() + { + finalize(defaultAllocator, epollEvents); + } + + /** + * Should be called if the backend configuration changes. + * + * Params: + * socket = Socket. + * oldEvents = The events were already set. + * events = The events should be set. + * + * Returns: $(D_KEYWORD true) if the operation was successful. + */ + protected override bool modify(int socket, EventMask oldEvents, EventMask events) + { + int op = EPOLL_CTL_DEL; + epoll_event ev; + + if (events == oldEvents) + { + return true; + } + if (events && oldEvents) + { + op = EPOLL_CTL_MOD; + } + else if (events && !oldEvents) + { + op = EPOLL_CTL_ADD; + } + + ev.data.fd = socket; + ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0) + | (events & Event.write ? EPOLLOUT : 0) + | EPOLLET; + + return epoll_ctl(fd, op, socket, &ev) == 0; + } + + /** + * Accept incoming connections. + * + * Params: + * protocolFactory = Protocol factory. + * socket = Socket. + */ + protected override void acceptConnection(Protocol delegate() @nogc protocolFactory, + int socket) + { + sockaddr_in client_addr; + socklen_t client_len = client_addr.sizeof; + int client = accept4(socket, + cast(sockaddr *)&client_addr, + &client_len, + O_NONBLOCK); + while (client >= 0) + { + auto transport = make!SocketTransport(defaultAllocator, this, client); + IOWatcher connection; + + if (connections.length > client) + { + connection = cast(IOWatcher) connections[client]; + // If it is a ConnectionWatcher + if (connection is null && connections[client] !is null) + { + finalize(defaultAllocator, connections[client]); + connections[client] = null; + } + } + if (connection !is null) + { + connection(protocolFactory, transport); + } + else + { + connections[client] = make!IOWatcher(defaultAllocator, + protocolFactory, + transport); + } + + modify(client, EventMask(Event.none), EventMask(Event.read, Event.write)); + + swapPendings.insertBack(connections[client]); + + client = accept4(socket, + cast(sockaddr *)&client_addr, + &client_len, + O_NONBLOCK); + } + } + + /** + * Does the actual polling. + */ + protected override void poll() + { + // Don't block + immutable timeout = cast(immutable int) blockTime.total!"msecs"; + auto eventCount = epoll_wait(fd, epollEvents, maxEvents, timeout); + + if (eventCount < 0) + { + if (errno != EINTR) + { + throw make!BadLoopException(defaultAllocator); + } + + return; + } + + for (auto i = 0; i < eventCount; ++i) + { + epoll_event *ev = epollEvents + i; + auto connection = cast(IOWatcher) connections[ev.data.fd]; + + if (connection is null) + { + swapPendings.insertBack(connections[ev.data.fd]); +// acceptConnection(connections[ev.data.fd].protocol, +// connections[ev.data.fd].socket); + } + else + { + auto transport = cast(SocketTransport) connection.transport; + assert(transport !is null); + + if (ev.events & (EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP)) + { + try + { + while (!transport.receive()) + { + } + swapPendings.insertBack(connection); + } + catch (TransportException e) + { + swapPendings.insertBack(connection); + finalize(defaultAllocator, e); + } + } + else if (ev.events & (EPOLLOUT | EPOLLERR | EPOLLHUP)) + { + transport.writeReady = true; + } + } + } + } + + /** + * Returns: The blocking time. + */ + override protected @property inout(Duration) blockTime() + inout @safe pure nothrow + { + return min(super.blockTime, 1.dur!"seconds"); + } + + private int fd; + private epoll_event* epollEvents; +} diff --git a/source/tanya/event/internal/selector.d b/source/tanya/event/internal/selector.d new file mode 100644 index 0000000..9682a5d --- /dev/null +++ b/source/tanya/event/internal/selector.d @@ -0,0 +1,217 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event.internal.selector; + +import tanya.memory; +import tanya.container.buffer; +import tanya.event.loop; +import tanya.event.protocol; +import tanya.event.transport; +import core.stdc.errno; +import core.sys.posix.netinet.in_; +import core.sys.posix.unistd; + +/** + * Transport for stream sockets. + */ +class SocketTransport : DuplexTransport +{ +@nogc: + private int socket_ = -1; + + private Protocol protocol_; + + /// Input buffer. + private WriteBuffer input_; + + /// Output buffer. + private ReadBuffer output_; + + private Loop loop; + + private bool disconnected_; + + package bool writeReady; + + /** + * Params: + * loop = Event loop. + * socket = Socket. + * protocol = Protocol. + */ + this(Loop loop, int socket, Protocol protocol = null) + { + socket_ = socket; + protocol_ = protocol; + this.loop = loop; + input_ = make!WriteBuffer(defaultAllocator); + output_ = make!ReadBuffer(defaultAllocator); + } + + /** + * Close the transport and deallocate the data buffers. + */ + ~this() + { + close(socket); + finalize(defaultAllocator, input_); + finalize(defaultAllocator, output_); + finalize(defaultAllocator, protocol_); + } + + /** + * Returns: Transport socket. + */ + int socket() const @safe pure nothrow + { + return socket_; + } + + /** + * Returns: Protocol. + */ + @property Protocol protocol() @safe pure nothrow + { + return protocol_; + } + + /** + * Returns: $(D_KEYWORD true) if the remote peer closed the connection, + * $(D_KEYWORD false) otherwise. + */ + @property immutable(bool) disconnected() const @safe pure nothrow + { + return disconnected_; + } + + /** + * Params: + * protocol = Application protocol. + */ + @property void protocol(Protocol protocol) @safe pure nothrow + { + protocol_ = protocol; + } + + /** + * Returns: Application protocol. + */ + @property inout(Protocol) protocol() inout @safe pure nothrow + { + return protocol_; + } + + /** + * Write some data to the transport. + * + * Params: + * data = Data to send. + */ + void write(ubyte[] data) + { + // If the buffer wasn't empty the transport should be already there. + if (!input.length && data.length) + { + loop.feed(this); + } + input ~= data; + } + + /** + * Returns: Input buffer. + */ + @property WriteBuffer input() @safe pure nothrow + { + return input_; + } + + /** + * Returns: Output buffer. + */ + @property ReadBuffer output() @safe pure nothrow + { + return output_; + } + + /** + * Read data from the socket. Returns $(D_KEYWORD true) if the reading + * is completed. In the case that the peer closed the connection, returns + * $(D_KEYWORD true) aswell. + * + * Returns: Whether the reading is completed. + * + * Throws: $(D_PSYMBOL TransportException) if a read error is occured. + */ + bool receive() + { + auto readCount = recv(socket, output.buffer, output.free, 0); + + if (readCount > 0) + { + output_ ~= output.buffer[0..readCount]; + return false; + } + else if (readCount == 0) + { + disconnected_ = true; + return true; + } + else if (errno == EAGAIN || errno == EWOULDBLOCK) + { + return true; + } + else + { + disconnected_ = true; + throw make!TransportException(defaultAllocator, + "Read from the socket failed."); + } + } + + /** + * Returns: Whether the writing is completed. + * + * Throws: $(D_PSYMBOL TransportException) if a read error is occured. + */ + bool send() + { + auto sentCount = core.sys.posix.netinet.in_.send(socket, + input.buffer, + input.length, + 0); + + input.written = sentCount; + if (input.length == 0) + { + return true; + } + else if (sentCount >= 0) + { + loop.feed(this); + + return false; + } + else if (errno == EAGAIN || errno == EWOULDBLOCK) + { + writeReady = false; + loop.feed(this); + + return false; + } + else + { + disconnected_ = true; + loop.feed(this); + throw make!TransportException(defaultAllocator, + "Write to the socket failed."); + } + } +} diff --git a/source/tanya/event/loop.d b/source/tanya/event/loop.d new file mode 100644 index 0000000..4383845 --- /dev/null +++ b/source/tanya/event/loop.d @@ -0,0 +1,279 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event.loop; + +import tanya.memory; +import tanya.container.queue; +import tanya.container.vector; +import tanya.event.config; +import tanya.event.protocol; +import tanya.event.transport; +import tanya.event.watcher; +import tanya.container.buffer; +import core.thread; +import core.time; +import std.algorithm.iteration; +import std.algorithm.mutation; +import std.typecons; + +static if (UseEpoll) +{ + import tanya.event.internal.epoll; +} + +@nogc: + +/** + * Events. + */ +enum Event : uint +{ + none = 0x00, /// No events. + read = 0x01, /// Non-blocking read call. + write = 0x02, /// Non-blocking write call. + accept = 0x04, /// Connection made. +} + +alias EventMask = BitFlags!Event; + +/** + * Event loop. + */ +abstract class Loop +{ +@nogc: + /// Pending watchers. + protected Queue!Watcher pendings; + + protected Queue!Watcher swapPendings; + + /// Pending connections. + protected Vector!ConnectionWatcher connections; + + /** + * Initializes the loop. + */ + this() + { + connections = make!(Vector!ConnectionWatcher)(defaultAllocator); + pendings = make!(Queue!Watcher)(defaultAllocator); + swapPendings = make!(Queue!Watcher)(defaultAllocator); + } + + /** + * Frees loop internals. + */ + ~this() + { + finalize(defaultAllocator, connections); + finalize(defaultAllocator, pendings); + finalize(defaultAllocator, swapPendings); + } + + /** + * Starts the loop. + */ + void run() + { + done_ = false; + do + { + poll(); + + // Invoke pendings + swapPendings.each!((ref p) => p.invoke()); + + swap(pendings, swapPendings); + } + while (!done_); + } + + /** + * Break out of the loop. + */ + void unloop() @safe pure nothrow + { + done_ = true; + } + + /** + * Start watching. + * + * Params: + * watcher = Watcher. + */ + void start(ConnectionWatcher watcher) + { + if (watcher.active) + { + return; + } + watcher.active = true; + watcher.accept = &acceptConnection; + connections[watcher.socket] = watcher; + + modify(watcher.socket, EventMask(Event.none), EventMask(Event.accept)); + } + + /** + * Stop watching. + * + * Params: + * watcher = Watcher. + */ + void stop(ConnectionWatcher watcher) + { + if (!watcher.active) + { + return; + } + watcher.active = false; + + modify(watcher.socket, EventMask(Event.accept), EventMask(Event.none)); + } + + /** + * Feeds the given event set into the event loop, as if the specified event + * had happened for the specified watcher. + * + * Params: + * transport = Affected transport. + */ + void feed(DuplexTransport transport) + { + pendings.insertBack(connections[transport.socket]); + } + + /** + * Should be called if the backend configuration changes. + * + * Params: + * socket = Socket. + * oldEvents = The events were already set. + * events = The events should be set. + * + * Returns: $(D_KEYWORD true) if the operation was successful. + */ + protected bool modify(int socket, EventMask oldEvents, EventMask events); + + /** + * Returns: The blocking time. + */ + protected @property inout(Duration) blockTime() + inout @safe pure nothrow + { + // Don't block if we have to do. + return swapPendings.empty ? blockTime_ : Duration.zero; + } + + /** + * Sets the blocking time for IO watchers. + * + * Params: + * blockTime = The blocking time. Cannot be larger than + * $(D_PSYMBOL maxBlockTime). + */ + protected @property void blockTime(in Duration blockTime) @safe pure nothrow + in + { + assert(blockTime <= 1.dur!"hours", "Too long to wait."); + assert(!blockTime.isNegative); + } + body + { + blockTime_ = blockTime; + } + + /** + * Does the actual polling. + */ + protected void poll(); + + /** + * Accept incoming connections. + * + * Params: + * protocolFactory = Protocol factory. + * socket = Socket. + */ + protected void acceptConnection(Protocol delegate() @nogc protocolFactory, + int socket); + + /// Whether the event loop should be stopped. + private bool done_; + + /// Maximal block time. + protected Duration blockTime_ = 1.dur!"minutes"; +} + +/** + * Exception thrown on errors in the event loop. + */ +class BadLoopException : Exception +{ +@nogc: + /** + * Params: + * file = The file where the exception occurred. + * line = The line number where the exception occurred. + * next = The previous exception in the chain of exceptions, if any. + */ + this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) + pure @safe nothrow const + { + super("Event loop cannot be initialized.", file, line, next); + } +} + +/** + * Returns the event loop used by default. If an event loop wasn't set with + * $(D_PSYMBOL defaultLoop) before, $(D_PSYMBOL getDefaultLoop()) will try to + * choose an event loop supported on the system. + * + * Returns: The default event loop. + */ +Loop getDefaultLoop() +{ + if (_defaultLoop !is null) + { + return _defaultLoop; + } + + static if (UseEpoll) + { + _defaultLoop = make!EpollLoop(defaultAllocator); + } + + return _defaultLoop; +} + +/** + * Sets the default event loop. + * + * This property makes it possible to implement your own backends or event + * loops, for example, if the system is not supported or if you want to + * extend the supported implementation. Just extend $(D_PSYMBOL Loop) and pass + * your implementation to this property. + * + * Params: + * loop = The event loop. + */ +@property void defaultLoop(Loop loop) +in +{ + assert(loop !is null); +} +body +{ + _defaultLoop = loop; +} + +private Loop _defaultLoop; diff --git a/source/tanya/event/package.d b/source/tanya/event/package.d new file mode 100644 index 0000000..9dcaeeb --- /dev/null +++ b/source/tanya/event/package.d @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event; + +public import tanya.event.loop; +public import tanya.event.protocol; +public import tanya.event.transport; +public import tanya.event.watcher; diff --git a/source/tanya/event/protocol.d b/source/tanya/event/protocol.d new file mode 100644 index 0000000..02c2f35 --- /dev/null +++ b/source/tanya/event/protocol.d @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event.protocol; + +import tanya.event.transport; + +/** + * Common protocol interface. + */ +interface Protocol +{ +@nogc: + /** + * Params: + * data = Read data. + */ + void received(ubyte[] data); + + /** + * Called when a connection is made. + * + * Params: + * transport = Protocol transport. + */ + void connected(DuplexTransport transport); + + /** + * Called when a connection is lost. + */ + void disconnected(); +} + +/** + * Interface for TCP. + */ +interface TransmissionControlProtocol : Protocol +{ +} diff --git a/source/tanya/event/transport.d b/source/tanya/event/transport.d new file mode 100644 index 0000000..7a3a60a --- /dev/null +++ b/source/tanya/event/transport.d @@ -0,0 +1,138 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event.transport; + +import tanya.container.buffer; +import tanya.event.protocol; + +/** + * Exception thrown on read/write errors. + */ +class TransportException : Exception +{ +@nogc: + /** + * Params: + * msg = Message to output. + * file = The file where the exception occurred. + * line = The line number where the exception occurred. + * next = The previous exception in the chain of exceptions, if any. + */ + this(string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) pure @safe nothrow const + { + super(msg, file, line, next); + } +} + +/** + * Base transport interface. + */ +interface Transport +{ +@nogc: + /** + * Returns: Protocol. + */ + @property Protocol protocol() @safe pure nothrow; + + /** + * Returns: $(D_KEYWORD true) if the peer closed the connection, + * $(D_KEYWORD false) otherwise. + */ + @property immutable(bool) disconnected() const @safe pure nothrow; + + /** + * Params: + * protocol = Application protocol. + */ + @property void protocol(Protocol protocol) @safe pure nothrow + in + { + assert(protocol !is null, "protocolConnected cannot be unset."); + } + + /** + * Returns: Application protocol. + */ + @property inout(Protocol) protocol() inout @safe pure nothrow; + + /** + * Returns: Transport socket. + */ + int socket() const @safe pure nothrow; +} + +/** + * Interface for read-only transports. + */ +interface ReadTransport : Transport +{ +@nogc: + /** + * Returns: Underlying output buffer. + */ + @property ReadBuffer output(); + + /** + * Reads data into the buffer. + * + * Returns: Whether the reading is completed. + * + * Throws: $(D_PSYMBOL TransportException) if a read error is occured. + */ + bool receive() + in + { + assert(!disconnected); + } +} + +/** + * Interface for write-only transports. + */ +interface WriteTransport : Transport +{ +@nogc: + /** + * Returns: Underlying input buffer. + */ + @property WriteBuffer input(); + + /** + * Write some data to the transport. + * + * Params: + * data = Data to send. + */ + void write(ubyte[] data); + + /** + * Returns: Whether the writing is completed. + * + * Throws: $(D_PSYMBOL TransportException) if a read error is occured. + */ + bool send() + in + { + assert(input.length); + assert(!disconnected); + } +} + +/** + * Represents a bidirectional transport. + */ +abstract class DuplexTransport : ReadTransport, WriteTransport +{ +} diff --git a/source/tanya/event/watcher.d b/source/tanya/event/watcher.d new file mode 100644 index 0000000..f0ed9e1 --- /dev/null +++ b/source/tanya/event/watcher.d @@ -0,0 +1,210 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.event.watcher; + +import tanya.event.protocol; +import tanya.event.transport; +import tanya.memory; +import std.functional; + +/** + * A watcher is an opaque structure that you allocate and register to record + * your interest in some event. + */ +abstract class Watcher +{ +@nogc: + /// Whether the watcher is active. + bool active; + + /** + * Invoke some action on event. + */ + void invoke(); +} + +class ConnectionWatcher : Watcher +{ +@nogc: + /// Watched file descriptor. + private int socket_; + + /// Protocol factory. + protected Protocol delegate() protocolFactory; + + /// Callback. + package void delegate(Protocol delegate() @nogc protocolFactory, + int socket) accept; + + invariant + { + assert(socket_ >= 0, "Called with negative file descriptor."); + } + + /** + * Params: + * protocolFactory = Function returning a new $(D_PSYMBOL Protocol) instance. + * socket = Socket. + */ + this(Protocol function() @nogc protocolFactory, int socket) + { + this.protocolFactory = toDelegate(protocolFactory); + socket_ = socket; + } + + /// Ditto. + this(Protocol delegate() @nogc protocolFactory, int socket) + { + this.protocolFactory = protocolFactory; + socket_ = socket; + } + + /// Ditto. + protected this(Protocol function() @nogc protocolFactory) + { + this.protocolFactory = toDelegate(protocolFactory); + } + + /// Ditto. + protected this(Protocol delegate() @nogc protocolFactory) + { + this.protocolFactory = protocolFactory; + } + + /** + * Returns: Socket. + */ + @property inout(int) socket() inout @safe pure nothrow + { + return socket_; + } + + /** + * Returns: Application protocol factory. + */ + @property inout(Protocol delegate() @nogc) protocol() inout + { + return protocolFactory; + } + + override void invoke() + { + accept(protocol, socket); + } +} + +/** + * Contains a pending watcher with the invoked events or a transport can be + * read from. + */ +class IOWatcher : ConnectionWatcher +{ +@nogc: + /// References a watcher or a transport. + DuplexTransport transport_; + + /** + * Params: + * protocolFactory = Function returning application specific protocol. + * transport = Transport. + */ + this(Protocol delegate() @nogc protocolFactory, + DuplexTransport transport) + in + { + assert(transport !is null); + assert(protocolFactory !is null); + } + body + { + super(protocolFactory); + this.transport_ = transport; + } + + ~this() + { + finalize(defaultAllocator, transport_); + } + + /** + * Assigns a transport. + * + * Params: + * protocolFactory = Function returning application specific protocol. + * transport = Transport. + * + * Returns: $(D_KEYWORD this). + */ + IOWatcher opCall(Protocol delegate() @nogc protocolFactory, + DuplexTransport transport) @safe pure nothrow + in + { + assert(transport !is null); + assert(protocolFactory !is null); + } + body + { + this.protocolFactory = protocolFactory; + this.transport_ = transport; + return this; + } + + /** + * Returns: Transport used by this watcher. + */ + @property inout(DuplexTransport) transport() inout @safe pure nothrow + { + return transport_; + } + + /** + * Returns: Socket. + */ + override @property inout(int) socket() inout @safe pure nothrow + { + return transport.socket; + } + + /** + * Invokes the watcher callback. + * + * Finalizes the transport on disconnect. + */ + override void invoke() + { + if (transport.protocol is null) + { + transport.protocol = protocolFactory(); + transport.protocol.connected(transport); + } + else if (transport.disconnected) + { + transport.protocol.disconnected(); + finalize(defaultAllocator, transport_); + protocolFactory = null; + } + else if (transport.output.length) + { + transport.protocol.received(transport.output[]); + } + else if (transport.input.length) + { + try + { + transport.send(); + } + catch (TransportException e) + { + finalize(defaultAllocator, e); + } + } + } +} diff --git a/source/tanya/math.d b/source/tanya/math.d new file mode 100644 index 0000000..46932b2 --- /dev/null +++ b/source/tanya/math.d @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.container.math; + +version (unittest) +{ + import std.algorithm.iteration; +} + +@nogc: + +/** + * Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z). + * + * Params: + * x = Base. + * y = Exponent. + * z = Divisor. + * + * Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y) + * by $(D_PARAM z). + */ +ulong pow(ulong x, ulong y, ulong z) @safe nothrow pure +in +{ + assert(z > 0); +} +out (result) +{ + assert(result >= 0); +} +body +{ + ulong mask = ulong.max / 2 + 1, result; + + if (y == 0) + { + return 1 % z; + } + else if (y == 1) + { + return x % z; + } + do + { + auto bit = y & mask; + if (!result && bit) + { + result = x; + continue; + } + + result *= result; + if (bit) + { + result *= x; + } + result %= z; + + } + while (mask >>= 1); + + return result; +} + +/// +unittest +{ + assert(pow(3, 5, 7) == 5); + assert(pow(2, 2, 1) == 0); + assert(pow(3, 3, 3) == 0); + assert(pow(7, 4, 2) == 1); + assert(pow(53, 0, 2) == 1); + assert(pow(53, 1, 3) == 2); + assert(pow(53, 2, 5) == 4); + assert(pow(0, 0, 5) == 1); + assert(pow(0, 5, 5) == 0); +} + +/** + * Checks if $(D_PARAM x) is a prime. + * + * Params: + * x = The number should be checked. + * + * Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number, + * $(D_KEYWORD false) otherwise. + */ +bool isPseudoprime(ulong x) @safe nothrow pure +{ + return pow(2, x - 1, x) == 1; +} + +/// +unittest +{ + uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, + 74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821, + 74827, 9973, 104729, 15485867, 49979693, 104395303, + 593441861, 104729, 15485867, 49979693, 104395303, + 593441861, 899809363, 982451653]; + + known.each!((ref x) => assert(isPseudoprime(x))); +} diff --git a/source/tanya/memory/allocator.d b/source/tanya/memory/allocator.d new file mode 100644 index 0000000..c206826 --- /dev/null +++ b/source/tanya/memory/allocator.d @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.memory.allocator; + +/** + * This interface should be similar to $(D_PSYMBOL + * std.experimental.allocator.IAllocator), but usable in + * $(D_KEYWORD @nogc)-code. + */ +interface Allocator +{ +@nogc: + /** + * Allocates $(D_PARAM s) bytes of memory. + * + * Params: + * s = Amount of memory to allocate. + * + * Returns: The pointer to the new allocated memory. + */ + void[] allocate(size_t s) @safe; + + /** + * Deallocates a memory block. + * + * Params: + * p = A pointer to the memory block to be freed. + * + * Returns: Whether the deallocation was successful. + */ + bool deallocate(void[] p) @safe; + + /** + * Increases or decreases the size of a memory block. + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: Whether the reallocation was successful. + */ + bool reallocate(ref void[] p, size_t s) @safe; + + /** + * Static allocator instance and initializer. + * + * Returns: An $(D_PSYMBOL Allocator) instance. + */ + static @property Allocator instance() @safe; +} diff --git a/source/tanya/memory/package.d b/source/tanya/memory/package.d new file mode 100644 index 0000000..67114df --- /dev/null +++ b/source/tanya/memory/package.d @@ -0,0 +1,207 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.memory; + +public import tanya.memory.allocator; +public import std.experimental.allocator : make, makeArray, expandArray, shrinkArray, IAllocator; +import core.atomic; +import core.stdc.stdlib; +import std.traits; + +version (Windows) +{ + import core.sys.windows.windows; +} +else version (Posix) +{ + public import tanya.memory.ullocator; + import core.sys.posix.pthread; +} + +@nogc: + +version (Windows) +{ + package alias Mutex = CRITICAL_SECTION; + package alias destroyMutex = DeleteCriticalSection; +} +else version (Posix) +{ + package alias Mutex = pthread_mutex_t; + package void destroyMutex(pthread_mutex_t* mtx) + { + pthread_mutex_destroy(mtx) && assert(0); + } +} + +@property void defaultAllocator(Allocator allocator) @safe nothrow +{ + _defaultAllocator = allocator; +} + +@property Allocator defaultAllocator() @safe nothrow +{ + return _defaultAllocator; +} + +static this() @safe nothrow +{ + defaultAllocator = Ullocator.instance; +} + +package struct Monitor +{ + Object.Monitor impl; // for user-level monitors + void delegate(Object) @nogc[] devt; // for internal monitors + size_t refs; // reference count + version (Posix) + { + Mutex mtx; + } +} + +package @property ref shared(Monitor*) monitor(Object h) pure nothrow +{ + return *cast(shared Monitor**)&h.__monitor; +} + +/** + * Destroys and then deallocates (using $(D_PARAM allocator)) the class + * object referred to by a $(D_KEYWORD class) or $(D_KEYWORD interface) + * reference. It is assumed the respective entities had been allocated with + * the same allocator. + * + * Params: + * A = The type of the allocator used for the ojbect allocation. + * T = The type of the object that should be destroyed. + * allocator = The allocator used for the object allocation. + * p = The object should be destroyed. + */ +void finalize(A, T)(auto ref A allocator, ref T p) + if (is(T == class) || is(T == interface)) +{ + static if (is(T == interface)) + { + auto ob = cast(Object) p; + } + else + { + alias ob = p; + } + auto pp = cast(void*) ob; + auto ppv = cast(void**) pp; + if (!pp || !*ppv) + { + return; + } + auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length]; + auto pc = cast(ClassInfo*) *ppv; + auto c = *pc; + do + { + if (c.destructor) + { + (cast(void function(Object)) c.destructor)(ob); + } + } while ((c = c.base) !is null); + + // Take care of monitors for synchronized blocks + if (ppv[1]) + { + shared(Monitor)* m = atomicLoad!(MemoryOrder.acq)(ob.monitor); + if (m !is null) + { + auto mc = cast(Monitor*) m; + if (!atomicOp!("-=")(m.refs, cast(size_t) 1)) + { + foreach (v; mc.devt) + { + if (v) + { + v(ob); + } + } + if (mc.devt.ptr) + { + free(mc.devt.ptr); + } + destroyMutex(&mc.mtx); + free(mc); + atomicStore!(MemoryOrder.rel)(ob.monitor, null); + } + } + } + *ppv = null; + + allocator.deallocate(support); + p = null; +} + +/// Ditto. +void finalize(A, T)(auto ref A allocator, ref T *p) + if (is(T == struct)) +{ + if (p is null) + { + return; + } + static if (hasElaborateDestructor!T) + { + *p.__xdtor(); + } + allocator.deallocate((cast(void*)p)[0 .. T.sizeof]); + p = null; +} + +/// Ditto. +void finalize(A, T)(auto ref A allocator, ref T[] p) +{ + static if (hasElaborateDestructor!T) + { + foreach (ref e; p) + { + finalize(allocator, e); + } + } + allocator.deallocate(p); + p = null; +} + +bool resizeArray(T, A)(auto ref A allocator, ref T[] array, in size_t length) +@trusted +{ + if (length == array.length) + { + return true; + } + if (array is null && length > 0) + { + array = makeArray!T(allocator, length); + return array !is null; + } + if (length == 0) + { + finalize(allocator, array); + return true; + } + void[] buf = array; + if (!allocator.reallocate(buf, length * T.sizeof)) + { + return false; + } + array = cast(T[]) buf; + return true; +} + +enum bool isFinalizable(T) = is(T == class) || is(T == interface) + || hasElaborateDestructor!T || isDynamicArray!T; + +private Allocator _defaultAllocator; diff --git a/source/tanya/memory/ullocator.d b/source/tanya/memory/ullocator.d new file mode 100644 index 0000000..54af6b4 --- /dev/null +++ b/source/tanya/memory/ullocator.d @@ -0,0 +1,423 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + */ +module tanya.memory.ullocator; + +import tanya.memory.allocator; + +@nogc: + +version (Posix): + +import core.sys.posix.sys.mman; +import core.sys.posix.unistd; + +/** + * Allocator for Posix systems with mmap/munmap support. + * + * This allocator allocates memory in regions (multiple of 4 KB for example). + * Each region is then splitted in blocks. So it doesn't request the memory + * from the operating system on each call, but only if there are no large + * enought free blocks in the available regions. + * Deallocation works in the same way. Deallocation doesn't immediately + * gives the memory back to the operating system, but marks the appropriate + * block as free and only if all blocks in the region are free, the complet + * region is deallocated. + * + * ---------------------------------------------------------------------------- + * | | | | | || | | | + * | |prev <----------- | || | | | + * | R | B | | B | || R | B | | + * | E | L | | L | next E | L | | + * | G | O | DATA | O | FREE ---> G | O | DATA | + * | I | C | | C | <--- I | C | | + * | O | K | | K | prev O | K | | + * | N | -----------> next| || N | | | + * | | | | | || | | | + * --------------------------------------------------- ------------------------ + */ +class Ullocator : Allocator +{ +@nogc: + @disable this(); + + shared static this() @safe nothrow + { + pageSize = sysconf(_SC_PAGE_SIZE); + } + + /** + * Allocates $(D_PARAM size) bytes of memory. + * + * Params: + * size = Amount of memory to allocate. + * + * Returns: The pointer to the new allocated memory. + */ + void[] allocate(size_t size) @trusted nothrow + { + immutable dataSize = addAlignment(size); + + void* data = findBlock(dataSize); + if (data is null) + { + data = initializeRegion(dataSize); + } + + return data is null ? null : data[0..size]; + } + + /// + unittest + { + auto p = Ullocator.instance.allocate(20); + + assert(p); + + Ullocator.instance.deallocate(p); + } + + /** + * Search for a block large enough to keep $(D_PARAM size) and split it + * into two blocks if the block is too large. + * + * Params: + * size = Minimum size the block should have. + * + * Returns: Data the block points to or $(D_KEYWORD null). + */ + private void* findBlock(size_t size) nothrow + { + Block block1; + RegionLoop: for (auto r = head; r !is null; r = r.next) + { + block1 = cast(Block) (cast(void*) r + regionEntrySize); + do + { + if (block1.free && block1.size >= size) + { + break RegionLoop; + } + } + while ((block1 = block1.next) !is null); + } + if (block1 is null) + { + return null; + } + else if (block1.size >= size + alignment + blockEntrySize) + { // Split the block if needed + Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size); + block2.prev = block1; + if (block1.next is null) + { + block2.next = null; + } + else + { + block2.next = block1.next.next; + } + block1.next = block2; + + block1.free = false; + block2.free = true; + + block2.size = block1.size - blockEntrySize - size; + block1.size = size; + + block2.region = block1.region; + ++block1.region.blocks; + } + else + { + block1.free = false; + ++block1.region.blocks; + } + return cast(void*) block1 + blockEntrySize; + } + + /** + * Deallocates a memory block. + * + * Params: + * p = A pointer to the memory block to be freed. + * + * Returns: Whether the deallocation was successful. + */ + bool deallocate(void[] p) @trusted nothrow + { + if (p is null) + { + return true; + } + + Block block = cast(Block) (p.ptr - blockEntrySize); + if (block.region.blocks <= 1) + { + if (block.region.prev !is null) + { + block.region.prev.next = block.region.next; + } + else // Replace the list head. It is being deallocated + { + head = block.region.next; + } + if (block.region.next !is null) + { + block.region.next.prev = block.region.prev; + } + return munmap(block.region, block.region.size) == 0; + } + else + { + block.free = true; + --block.region.blocks; + return true; + } + } + + /// + unittest + { + auto p = Ullocator.instance.allocate(20); + + assert(Ullocator.instance.deallocate(p)); + } + + /** + * Increases or decreases the size of a memory block. + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: Whether the reallocation was successful. + */ + bool reallocate(ref void[] p, size_t size) @trusted nothrow + { + if (size == p.length) + { + return true; + } + + auto reallocP = allocate(size); + if (reallocP is null) + { + return false; + } + + if (p !is null) + { + if (size > p.length) + { + reallocP[0..p.length] = p[0..$]; + } + else + { + reallocP[0..size] = p[0..size]; + } + deallocate(p); + } + p = reallocP; + + return true; + } + + /// + unittest + { + void[] p; + Ullocator.instance.reallocate(p, 10 * int.sizeof); + (cast(int[]) p)[7] = 123; + + assert(p.length == 40); + + Ullocator.instance.reallocate(p, 8 * int.sizeof); + + assert(p.length == 32); + assert((cast(int[]) p)[7] == 123); + + Ullocator.instance.reallocate(p, 20 * int.sizeof); + (cast(int[]) p)[15] = 8; + + assert(p.length == 80); + assert((cast(int[]) p)[15] == 8); + assert((cast(int[]) p)[7] == 123); + + Ullocator.instance.reallocate(p, 8 * int.sizeof); + + assert(p.length == 32); + assert((cast(int[]) p)[7] == 123); + + Ullocator.instance.deallocate(p); + } + + /** + * Static allocator instance and initializer. + * + * Returns: The global $(D_PSYMBOL Allocator) instance. + */ + static @property Ullocator instance() @trusted nothrow + { + if (instance_ is null) + { + immutable instanceSize = addAlignment(__traits(classInstanceSize, Ullocator)); + + Region head; // Will become soon our region list head + void* data = initializeRegion(instanceSize, head); + + if (data is null) + { + return null; + } + data[0..instanceSize] = typeid(Ullocator).initializer[]; + instance_ = cast(Ullocator) data; + instance_.head = head; + } + return instance_; + } + + /// + unittest + { + assert(instance is instance); + } + + /** + * Initializes a region for one element. + * + * Params: + * size = Aligned size of the first data block in the region. + * head = Region list head. + * + * Returns: A pointer to the data. + */ + pragma(inline) + private static void* initializeRegion(size_t size, + ref Region head) nothrow + { + immutable regionSize = calculateRegionSize(size); + void* p = mmap(null, + regionSize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0); + if (p is MAP_FAILED) + { + return null; + } + + Region region = cast(Region) p; + region.blocks = 1; + region.size = regionSize; + + // Set the pointer to the head of the region list + if (head !is null) + { + head.prev = region; + } + region.next = head; + region.prev = null; + head = region; + + // Initialize the data block + void* memoryPointer = p + regionEntrySize; + Block block1 = cast(Block) memoryPointer; + block1.size = size; + block1.free = false; + + // It is what we want to return + void* data = memoryPointer + blockEntrySize; + + // Free block after data + memoryPointer = data + size; + Block block2 = cast(Block) memoryPointer; + block1.prev = block2.next = null; + block1.next = block2; + block2.prev = block1; + block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2; + block2.free = true; + block1.region = block2.region = region; + + return data; + } + + /// Ditto. + private void* initializeRegion(size_t size) nothrow + { + return initializeRegion(size, head); + } + + /** + * Params: + * x = Space to be aligned. + * + * Returns: Aligned size of $(D_PARAM x). + */ + pragma(inline) + private static immutable(size_t) addAlignment(size_t x) @safe pure nothrow + out (result) + { + assert(result > 0); + } + body + { + return (x - 1) / alignment * alignment + alignment; + } + + /** + * Params: + * x = Required space. + * + * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). + */ + pragma(inline) + private static immutable(size_t) calculateRegionSize(size_t x) + @safe pure nothrow + out (result) + { + assert(result > 0); + } + body + { + x += regionEntrySize + blockEntrySize * 2; + return x / pageSize * pageSize + pageSize; + } + + enum alignment = 8; + + private static Ullocator instance_; + + private shared static immutable long pageSize; + + private struct RegionEntry + { + Region prev; + Region next; + uint blocks; + ulong size; + } + private alias Region = RegionEntry*; + private enum regionEntrySize = 32; + + private Region head; + + private struct BlockEntry + { + Block prev; + Block next; + bool free; + ulong size; + Region region; + } + private alias Block = BlockEntry*; + private enum blockEntrySize = 40; +} diff --git a/source/tanya/random.d b/source/tanya/random.d new file mode 100644 index 0000000..bf749c7 --- /dev/null +++ b/source/tanya/random.d @@ -0,0 +1,324 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Copyright: Eugene Wissner 2016. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) + */ +module tanya.random; + +import tanya.memory; +import std.digest.sha; +import std.typecons; + +@nogc: + +/// Block size of entropy accumulator (SHA-512). +enum blockSize = 64; + +/// Maximum amount gathered from the entropy sources. +enum maxGather = 128; + +/** + * Exception thrown if random number generating fails. + */ +class EntropyException : Exception +{ +@nogc: + /** + * Params: + * msg = Message to output. + * file = The file where the exception occurred. + * line = The line number where the exception occurred. + * next = The previous exception in the chain of exceptions, if any. + */ + this(string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) pure @safe nothrow const + { + super(msg, file, line, next); + } +} + +/** + * Interface for implementing entropy sources. + */ +abstract class EntropySource +{ +@nogc: + /// Amount of already generated entropy. + protected ushort size_; + + /** + * Returns: Minimum bytes required from the entropy source. + */ + @property immutable(ubyte) threshold() const @safe pure nothrow; + + /** + * Returns: Whether this entropy source is strong. + */ + @property immutable(bool) strong() const @safe pure nothrow; + + /** + * Returns: Amount of already generated entropy. + */ + @property ushort size() const @safe pure nothrow + { + return size_; + } + + /** + * Params: + * size = Amount of already generated entropy. Cannot be smaller than the + * already set value. + */ + @property void size(ushort size) @safe pure nothrow + { + size_ = size; + } + + /** + * Poll the entropy source. + * + * Params: + * output = Buffer to save the generate random sequence (the method will + * to fill the buffer). + * + * Returns: Number of bytes that were copied to the $(D_PARAM output) + * or $(D_PSYMBOL Nullable!ubyte.init) on error. + */ + Nullable!ubyte poll(out ubyte[maxGather] output); +} + +version (linux) +{ + extern (C) long syscall(long number, ...) nothrow; + + /** + * Uses getrandom system call. + */ + class PlatformEntropySource : EntropySource + { + @nogc: + /** + * Returns: Minimum bytes required from the entropy source. + */ + override @property immutable(ubyte) threshold() const @safe pure nothrow + { + return 32; + } + + /** + * Returns: Whether this entropy source is strong. + */ + override @property immutable(bool) strong() const @safe pure nothrow + { + return true; + } + + /** + * Poll the entropy source. + * + * Params: + * output = Buffer to save the generate random sequence (the method will + * to fill the buffer). + * + * Returns: Number of bytes that were copied to the $(D_PARAM output) + * or $(D_PSYMBOL Nullable!ubyte.init) on error. + */ + override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow + out (length) + { + assert(length <= maxGather); + } + body + { + // int getrandom(void *buf, size_t buflen, unsigned int flags); + auto length = syscall(318, output.ptr, output.length, 0); + Nullable!ubyte ret; + + if (length >= 0) + { + ret = cast(ubyte) length; + } + return ret; + } + } +} + +/** + * Pseudorandom number generator. + * --- + * auto entropy = defaultAllocator.make!Entropy; + * + * ubyte[blockSize] output; + * + * output = entropy.random; + * + * defaultAllocator.finalize(entropy); + * --- + */ +class Entropy +{ +@nogc: + /// Entropy sources. + protected EntropySource[] sources; + + private ubyte sourceCount_; + + private Allocator allocator; + + /// Entropy accumulator. + protected SHA!(maxGather * 8, 512) accumulator; + + /** + * Params: + * maxSources = Maximum amount of entropy sources can be set. + * allocator = Allocator to allocate entropy sources available on the + * system. + */ + this(size_t maxSources = 20, Allocator allocator = defaultAllocator) + in + { + assert(maxSources > 0 && maxSources <= ubyte.max); + assert(allocator !is null); + } + body + { + allocator.resizeArray(sources, maxSources); + + version (linux) + { + this ~= allocator.make!PlatformEntropySource; + } + } + + /** + * Returns: Amount of the registered entropy sources. + */ + @property ubyte sourceCount() const @safe pure nothrow + { + return sourceCount_; + } + + /** + * Add an entropy source. + * + * Params: + * source = Entropy source. + * + * Returns: $(D_PSYMBOL this). + * + * See_Also: + * $(D_PSYMBOL EntropySource) + */ + Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow + if (Op == "~") + in + { + assert(sourceCount_ <= sources.length); + } + body + { + sources[sourceCount_++] = source; + return this; + } + + /** + * Returns: Generated random sequence. + * + * Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was + * registered or it failed. + */ + @property ubyte[blockSize] random() + in + { + assert(sourceCount_ > 0, "No entropy sources defined."); + } + body + { + bool haveStrong; + ushort done; + ubyte[blockSize] output; + + do + { + ubyte[maxGather] buffer; + + // Run through our entropy sources + for (ubyte i; i < sourceCount; ++i) + { + auto outputLength = sources[i].poll(buffer); + + if (!outputLength.isNull) + { + if (outputLength > 0) + { + update(i, buffer, outputLength); + sources[i].size = cast(ushort) (sources[i].size + outputLength); + } + if (sources[i].size < sources[i].threshold) + { + continue; + } + else if (sources[i].strong) + { + haveStrong = true; + } + } + done = 257; + } + } + while (++done < 256); + + if (!haveStrong) + { + throw allocator.make!EntropyException("No strong entropy source defined."); + } + + output = accumulator.finish(); + + // Reset accumulator and counters and recycle existing entropy + accumulator.start(); + + // Perform second SHA-512 on entropy + output = sha512Of(output); + + for (ubyte i = 0; i < sourceCount; ++i) + { + sources[i].size = 0; + } + return output; + } + + /** + * Update entropy accumulator. + * + * Params: + * sourceId = Entropy source index in $(D_PSYMBOL sources). + * data = Data got from the entropy source. + * length = Length of the received data. + */ + protected void update(in ubyte sourceId, + ref ubyte[maxGather] data, + ubyte length) @safe pure nothrow + { + ubyte[2] header; + + if (length > blockSize) + { + data[0..64] = sha512Of(data); + length = blockSize; + } + + header[0] = sourceId; + header[1] = length; + + accumulator.put(header); + accumulator.put(data[0..length]); + } +}