summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2016-08-24 18:15:21 +0200
committerEugen Wissner <belka@caraus.de>2016-08-24 18:15:21 +0200
commita3efee6d7f5b3e4a2720154cf162f2f07f199135 (patch)
tree0816b77d8ad481f0c9e05bd1be36eee8a8a2d18e
parentc0df3c933040e314ee93fc566ac203053df6e253 (diff)
downloadtanya-a3efee6d7f5b3e4a2720154cf162f2f07f199135.tar.gz
Add codev0.0.1
-rw-r--r--.gitignore2
-rw-r--r--dub.json17
-rw-r--r--source/tanya/container/buffer.d641
-rw-r--r--source/tanya/container/list.d405
-rw-r--r--source/tanya/container/package.d16
-rw-r--r--source/tanya/container/queue.d219
-rw-r--r--source/tanya/container/vector.d420
-rw-r--r--source/tanya/crypto/padding.d191
-rw-r--r--source/tanya/event/config.d26
-rw-r--r--source/tanya/event/internal/epoll.d226
-rw-r--r--source/tanya/event/internal/selector.d217
-rw-r--r--source/tanya/event/loop.d279
-rw-r--r--source/tanya/event/package.d16
-rw-r--r--source/tanya/event/protocol.d46
-rw-r--r--source/tanya/event/transport.d138
-rw-r--r--source/tanya/event/watcher.d210
-rw-r--r--source/tanya/math.d112
-rw-r--r--source/tanya/memory/allocator.d58
-rw-r--r--source/tanya/memory/package.d207
-rw-r--r--source/tanya/memory/ullocator.d423
-rw-r--r--source/tanya/random.d324
21 files changed, 4193 insertions, 0 deletions
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 <info@caraus.de>",
+ "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]);
+ }
+}