Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
79a7b840f7 | |||
6b093cd5fa | |||
154e2f2ff7 | |||
e9a0a93d3c | |||
da5dc276d5 | |||
721bb110e5 | |||
9241ec503c | |||
c2afb07ff6 | |||
698660c4c8 | |||
be9181698a | |||
25c292662a |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,6 @@
|
||||
# Binary
|
||||
*.[oa]
|
||||
|
||||
# D
|
||||
.dub
|
||||
__test__*__
|
||||
|
178
source/tanya/async/event/epoll.d
Normal file
178
source/tanya/async/event/epoll.d
Normal file
@ -0,0 +1,178 @@
|
||||
/* 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.async.event.epoll;
|
||||
|
||||
version (linux):
|
||||
|
||||
public import core.sys.linux.epoll;
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.event.selector;
|
||||
import tanya.async.loop;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
import core.stdc.errno;
|
||||
import core.sys.posix.unistd;
|
||||
import core.time;
|
||||
import std.algorithm.comparison;
|
||||
|
||||
class EpollLoop : SelectorLoop
|
||||
{
|
||||
protected int fd;
|
||||
private epoll_event[] events;
|
||||
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
{
|
||||
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
||||
{
|
||||
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
|
||||
}
|
||||
super();
|
||||
events = MmapPool.instance.makeArray!epoll_event(maxEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free loop internals.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(events);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if the backend configuration changes.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
* oldEvents = The events were already set.
|
||||
* events = The events should be set.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||
*/
|
||||
protected override bool reify(ConnectionWatcher watcher, EventMask oldEvents, EventMask events)
|
||||
in
|
||||
{
|
||||
assert(watcher !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
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 = watcher.socket.handle;
|
||||
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
|
||||
| (events & Event.write ? EPOLLOUT : 0)
|
||||
| EPOLLET;
|
||||
|
||||
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
protected override void poll()
|
||||
{
|
||||
// Don't block
|
||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||
auto eventCount = epoll_wait(fd, events.ptr, maxEvents, timeout);
|
||||
|
||||
if (eventCount < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
{
|
||||
throw defaultAllocator.make!BadLoopException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < eventCount; ++i)
|
||||
{
|
||||
auto io = cast(IOWatcher) connections[events[i].data.fd];
|
||||
|
||||
if (io is null)
|
||||
{
|
||||
acceptConnections(connections[events[i].data.fd]);
|
||||
}
|
||||
else if (events[i].events & EPOLLERR)
|
||||
{
|
||||
kill(io, null);
|
||||
}
|
||||
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
|
||||
{
|
||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
SocketException exception;
|
||||
try
|
||||
{
|
||||
ptrdiff_t received;
|
||||
do
|
||||
{
|
||||
received = transport.socket.receive(io.output[]);
|
||||
io.output += received;
|
||||
}
|
||||
while (received);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
exception = e;
|
||||
}
|
||||
if (transport.socket.disconnected)
|
||||
{
|
||||
kill(io, exception);
|
||||
}
|
||||
else if (io.output.length)
|
||||
{
|
||||
swapPendings.insertBack(io);
|
||||
}
|
||||
}
|
||||
else if (events[i].events & EPOLLOUT)
|
||||
{
|
||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
transport.writeReady = true;
|
||||
if (transport.input.length)
|
||||
{
|
||||
feed(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The blocking time.
|
||||
*/
|
||||
override protected @property inout(Duration) blockTime()
|
||||
inout @safe pure nothrow
|
||||
{
|
||||
return min(super.blockTime, 1.dur!"seconds");
|
||||
}
|
||||
}
|
294
source/tanya/async/event/iocp.d
Normal file
294
source/tanya/async/event/iocp.d
Normal file
@ -0,0 +1,294 @@
|
||||
/* 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.async.event.iocp;
|
||||
|
||||
version (Windows):
|
||||
|
||||
import tanya.container.buffer;
|
||||
import tanya.async.loop;
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
import core.sys.windows.basetyps;
|
||||
import core.sys.windows.mswsock;
|
||||
import core.sys.windows.winbase;
|
||||
import core.sys.windows.windef;
|
||||
import core.sys.windows.winsock2;
|
||||
|
||||
class IOCPStreamTransport : StreamTransport
|
||||
{
|
||||
private OverlappedConnectedSocket socket_;
|
||||
|
||||
private WriteBuffer input;
|
||||
|
||||
/**
|
||||
* Creates new completion port transport.
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(OverlappedConnectedSocket socket)
|
||||
in
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
socket_ = socket;
|
||||
input = MmapPool.instance.make!WriteBuffer();
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(input);
|
||||
}
|
||||
|
||||
@property inout(OverlappedConnectedSocket) socket() inout pure nothrow @safe @nogc
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write some data to the transport.
|
||||
*
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data)
|
||||
{
|
||||
immutable empty = input.length == 0;
|
||||
input ~= data;
|
||||
if (empty)
|
||||
{
|
||||
SocketState overlapped;
|
||||
try
|
||||
{
|
||||
overlapped = MmapPool.instance.make!SocketState;
|
||||
socket.beginSend(input[], overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
MmapPool.instance.dispose(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IOCPLoop : Loop
|
||||
{
|
||||
protected HANDLE completionPort;
|
||||
|
||||
protected OVERLAPPED overlap;
|
||||
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
{
|
||||
super();
|
||||
|
||||
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
||||
if (!completionPort)
|
||||
{
|
||||
throw defaultAllocator.make!BadLoopException("Creating completion port failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if the backend configuration changes.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
* oldEvents = The events were already set.
|
||||
* events = The events should be set.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||
*/
|
||||
override protected bool reify(ConnectionWatcher watcher,
|
||||
EventMask oldEvents,
|
||||
EventMask events)
|
||||
{
|
||||
SocketState overlapped;
|
||||
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
||||
{
|
||||
auto socket = cast(OverlappedStreamSocket) watcher.socket;
|
||||
assert(socket !is null);
|
||||
|
||||
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
|
||||
completionPort,
|
||||
cast(ULONG_PTR) (cast(void*) watcher),
|
||||
0) !is completionPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
overlapped = MmapPool.instance.make!SocketState;
|
||||
socket.beginAccept(overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
defaultAllocator.dispose(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!(oldEvents & Event.read) && (events & Event.read)
|
||||
|| !(oldEvents & Event.write) && (events & Event.write))
|
||||
{
|
||||
auto io = cast(IOWatcher) watcher;
|
||||
assert(io !is null);
|
||||
|
||||
auto transport = cast(IOCPStreamTransport) io.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
||||
completionPort,
|
||||
cast(ULONG_PTR) (cast(void*) watcher),
|
||||
0) !is completionPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin to read
|
||||
if (!(oldEvents & Event.read) && (events & Event.read))
|
||||
{
|
||||
try
|
||||
{
|
||||
overlapped = MmapPool.instance.make!SocketState;
|
||||
transport.socket.beginReceive(io.output[], overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
defaultAllocator.dispose(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
override protected void poll()
|
||||
{
|
||||
DWORD lpNumberOfBytes;
|
||||
ULONG_PTR key;
|
||||
LPOVERLAPPED overlap;
|
||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||
|
||||
auto result = GetQueuedCompletionStatus(completionPort,
|
||||
&lpNumberOfBytes,
|
||||
&key,
|
||||
&overlap,
|
||||
timeout);
|
||||
if (result == FALSE && overlap == NULL)
|
||||
{
|
||||
return; // Timeout
|
||||
}
|
||||
|
||||
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
|
||||
assert(overlapped !is null);
|
||||
scope (failure)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
}
|
||||
|
||||
switch (overlapped.event)
|
||||
{
|
||||
case OverlappedSocketEvent.accept:
|
||||
auto connection = cast(ConnectionWatcher) (cast(void*) key);
|
||||
assert(connection !is null);
|
||||
|
||||
auto listener = cast(OverlappedStreamSocket) connection.socket;
|
||||
assert(listener !is null);
|
||||
|
||||
auto socket = listener.endAccept(overlapped);
|
||||
auto transport = MmapPool.instance.make!IOCPStreamTransport(socket);
|
||||
auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol);
|
||||
|
||||
connection.incoming.insertBack(io);
|
||||
|
||||
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
|
||||
swapPendings.insertBack(connection);
|
||||
listener.beginAccept(overlapped);
|
||||
break;
|
||||
case OverlappedSocketEvent.read:
|
||||
auto io = cast(IOWatcher) (cast(void*) key);
|
||||
assert(io !is null);
|
||||
if (!io.active)
|
||||
{
|
||||
MmapPool.instance.dispose(io);
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
return;
|
||||
}
|
||||
|
||||
auto transport = cast(IOCPStreamTransport) io.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
int received;
|
||||
SocketException exception;
|
||||
try
|
||||
{
|
||||
received = transport.socket.endReceive(overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
exception = e;
|
||||
}
|
||||
if (transport.socket.disconnected)
|
||||
{
|
||||
// We want to get one last notification to destroy the watcher
|
||||
transport.socket.beginReceive(io.output[], overlapped);
|
||||
kill(io, exception);
|
||||
}
|
||||
else if (received > 0)
|
||||
{
|
||||
immutable full = io.output.free == received;
|
||||
|
||||
io.output += received;
|
||||
// Receive was interrupted because the buffer is full. We have to continue
|
||||
if (full)
|
||||
{
|
||||
transport.socket.beginReceive(io.output[], overlapped);
|
||||
}
|
||||
swapPendings.insertBack(io);
|
||||
}
|
||||
break;
|
||||
case OverlappedSocketEvent.write:
|
||||
auto io = cast(IOWatcher) (cast(void*) key);
|
||||
assert(io !is null);
|
||||
|
||||
auto transport = cast(IOCPStreamTransport) io.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
transport.input += transport.socket.endSend(overlapped);
|
||||
if (transport.input.length)
|
||||
{
|
||||
transport.socket.beginSend(transport.input[], overlapped);
|
||||
}
|
||||
else
|
||||
{
|
||||
transport.socket.beginReceive(io.output[], overlapped);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false, "Unknown event");
|
||||
}
|
||||
}
|
||||
}
|
349
source/tanya/async/event/kqueue.d
Normal file
349
source/tanya/async/event/kqueue.d
Normal file
@ -0,0 +1,349 @@
|
||||
/* 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.async.event.kqueue;
|
||||
|
||||
version (OSX)
|
||||
{
|
||||
version = MissingKevent;
|
||||
}
|
||||
else version (iOS)
|
||||
{
|
||||
version = MissingKevent;
|
||||
}
|
||||
else version (TVOS)
|
||||
{
|
||||
version = MissingKevent;
|
||||
}
|
||||
else version (WatchOS)
|
||||
{
|
||||
version = MissingKevent;
|
||||
}
|
||||
else version (OpenBSD)
|
||||
{
|
||||
version = MissingKevent;
|
||||
}
|
||||
else version (DragonFlyBSD)
|
||||
{
|
||||
version = MissingKevent;
|
||||
}
|
||||
|
||||
version (MissingKevent)
|
||||
{
|
||||
extern (C):
|
||||
nothrow:
|
||||
@nogc:
|
||||
|
||||
import core.stdc.stdint; // intptr_t, uintptr_t
|
||||
import core.sys.posix.time; // timespec
|
||||
|
||||
enum : short
|
||||
{
|
||||
EVFILT_READ = -1,
|
||||
EVFILT_WRITE = -2,
|
||||
EVFILT_AIO = -3, /* attached to aio requests */
|
||||
EVFILT_VNODE = -4, /* attached to vnodes */
|
||||
EVFILT_PROC = -5, /* attached to struct proc */
|
||||
EVFILT_SIGNAL = -6, /* attached to struct proc */
|
||||
EVFILT_TIMER = -7, /* timers */
|
||||
EVFILT_MACHPORT = -8, /* Mach portsets */
|
||||
EVFILT_FS = -9, /* filesystem events */
|
||||
EVFILT_USER = -10, /* User events */
|
||||
EVFILT_VM = -12, /* virtual memory events */
|
||||
EVFILT_SYSCOUNT = 11
|
||||
}
|
||||
|
||||
extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args)
|
||||
{
|
||||
*kevp = kevent_t(args);
|
||||
}
|
||||
|
||||
struct kevent_t
|
||||
{
|
||||
uintptr_t ident; /* identifier for this event */
|
||||
short filter; /* filter for event */
|
||||
ushort flags;
|
||||
uint fflags;
|
||||
intptr_t data;
|
||||
void *udata; /* opaque user data identifier */
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
/* actions */
|
||||
EV_ADD = 0x0001, /* add event to kq (implies enable) */
|
||||
EV_DELETE = 0x0002, /* delete event from kq */
|
||||
EV_ENABLE = 0x0004, /* enable event */
|
||||
EV_DISABLE = 0x0008, /* disable event (not reported) */
|
||||
|
||||
/* flags */
|
||||
EV_ONESHOT = 0x0010, /* only report one occurrence */
|
||||
EV_CLEAR = 0x0020, /* clear event state after reporting */
|
||||
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
|
||||
EV_DISPATCH = 0x0080, /* disable event after reporting */
|
||||
|
||||
EV_SYSFLAGS = 0xF000, /* reserved by system */
|
||||
EV_FLAG1 = 0x2000, /* filter-specific flag */
|
||||
|
||||
/* returned values */
|
||||
EV_EOF = 0x8000, /* EOF detected */
|
||||
EV_ERROR = 0x4000, /* error, data contains errno */
|
||||
}
|
||||
|
||||
int kqueue();
|
||||
int kevent(int kq, const kevent_t *changelist, int nchanges,
|
||||
kevent_t *eventlist, int nevents,
|
||||
const timespec *timeout);
|
||||
}
|
||||
|
||||
version (OSX)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (iOS)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (FreeBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
public import core.sys.freebsd.sys.event;
|
||||
}
|
||||
else version (OpenBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (DragonFlyBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
|
||||
version (MacBSD):
|
||||
|
||||
import dlib.async.event.selector;
|
||||
import dlib.async.loop;
|
||||
import dlib.async.transport;
|
||||
import dlib.async.watcher;
|
||||
import dlib.memory;
|
||||
import dlib.memory.mmappool;
|
||||
import dlib.network.socket;
|
||||
import core.stdc.errno;
|
||||
import core.sys.posix.unistd;
|
||||
import core.sys.posix.sys.time;
|
||||
import core.time;
|
||||
import std.algorithm.comparison;
|
||||
|
||||
class KqueueLoop : SelectorLoop
|
||||
{
|
||||
protected int fd;
|
||||
private kevent_t[] events;
|
||||
private kevent_t[] changes;
|
||||
private size_t changeCount;
|
||||
|
||||
/**
|
||||
* Returns: Maximal event count can be got at a time
|
||||
* (should be supported by the backend).
|
||||
*/
|
||||
override protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return cast(uint) events.length;
|
||||
}
|
||||
|
||||
this()
|
||||
{
|
||||
super();
|
||||
|
||||
if ((fd = kqueue()) == -1)
|
||||
{
|
||||
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
|
||||
}
|
||||
events = MmapPool.instance.makeArray!kevent_t(64);
|
||||
changes = MmapPool.instance.makeArray!kevent_t(64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free loop internals.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(events);
|
||||
MmapPool.instance.dispose(changes);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
private void set(socket_t socket, short filter, ushort flags)
|
||||
{
|
||||
if (changes.length <= changeCount)
|
||||
{
|
||||
MmapPool.instance.resizeArray(changes, changeCount + maxEvents);
|
||||
}
|
||||
EV_SET(&changes[changeCount],
|
||||
cast(ulong) socket,
|
||||
filter,
|
||||
flags,
|
||||
0U,
|
||||
0L,
|
||||
null);
|
||||
++changeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if the backend configuration changes.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
* oldEvents = The events were already set.
|
||||
* events = The events should be set.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||
*/
|
||||
override protected bool reify(ConnectionWatcher watcher,
|
||||
EventMask oldEvents,
|
||||
EventMask events)
|
||||
{
|
||||
if (events != oldEvents)
|
||||
{
|
||||
if (oldEvents & Event.read || oldEvents & Event.accept)
|
||||
{
|
||||
set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
|
||||
}
|
||||
if (oldEvents & Event.write)
|
||||
{
|
||||
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
|
||||
}
|
||||
}
|
||||
if (events & (Event.read | events & Event.accept))
|
||||
{
|
||||
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
|
||||
}
|
||||
if (events & Event.write)
|
||||
{
|
||||
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
protected override void poll()
|
||||
{
|
||||
timespec ts;
|
||||
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
||||
|
||||
if (changeCount > maxEvents)
|
||||
{
|
||||
MmapPool.instance.resizeArray(events, changes.length);
|
||||
}
|
||||
|
||||
auto eventCount = kevent(fd, changes.ptr, cast(int) changeCount, events.ptr, maxEvents, &ts);
|
||||
changeCount = 0;
|
||||
|
||||
if (eventCount < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
{
|
||||
throw defaultAllocator.make!BadLoopException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i; i < eventCount; ++i)
|
||||
{
|
||||
assert(connections.length > events[i].ident);
|
||||
|
||||
IOWatcher io = cast(IOWatcher) connections[events[i].ident];
|
||||
// If it is a ConnectionWatcher. Accept connections.
|
||||
if (io is null)
|
||||
{
|
||||
acceptConnections(connections[events[i].ident]);
|
||||
}
|
||||
else if (events[i].flags & EV_ERROR)
|
||||
{
|
||||
kill(io, null);
|
||||
}
|
||||
else if (events[i].filter == EVFILT_READ)
|
||||
{
|
||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
SocketException exception;
|
||||
try
|
||||
{
|
||||
ptrdiff_t received;
|
||||
do
|
||||
{
|
||||
received = transport.socket.receive(io.output[]);
|
||||
io.output += received;
|
||||
}
|
||||
while (received);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
exception = e;
|
||||
}
|
||||
if (transport.socket.disconnected)
|
||||
{
|
||||
kill(io, exception);
|
||||
}
|
||||
else if (io.output.length)
|
||||
{
|
||||
swapPendings.insertBack(io);
|
||||
}
|
||||
}
|
||||
else if (events[i].filter == EVFILT_WRITE)
|
||||
{
|
||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
transport.writeReady = true;
|
||||
if (transport.input.length)
|
||||
{
|
||||
feed(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The blocking time.
|
||||
*/
|
||||
override protected @property inout(Duration) blockTime()
|
||||
inout @safe pure nothrow
|
||||
{
|
||||
return min(super.blockTime, 1.dur!"seconds");
|
||||
}
|
||||
|
||||
/**
|
||||
* If the transport couldn't send the data, the further sending should
|
||||
* be handled by the event loop.
|
||||
*
|
||||
* Params:
|
||||
* transport = Transport.
|
||||
* exception = Exception thrown on sending.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||
* transport is be destroyed then).
|
||||
*/
|
||||
protected override bool feed(SelectorStreamTransport transport, SocketException exception = null)
|
||||
{
|
||||
if (!super.feed(transport, exception))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!transport.writeReady)
|
||||
{
|
||||
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
266
source/tanya/async/event/selector.d
Normal file
266
source/tanya/async/event/selector.d
Normal file
@ -0,0 +1,266 @@
|
||||
/* 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.async.event.selector;
|
||||
|
||||
version (Posix):
|
||||
|
||||
import tanya.async.loop;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.buffer;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
import core.sys.posix.netinet.in_;
|
||||
import core.stdc.errno;
|
||||
|
||||
/**
|
||||
* Transport for stream sockets.
|
||||
*/
|
||||
class SelectorStreamTransport : StreamTransport
|
||||
{
|
||||
private ConnectedSocket socket_;
|
||||
|
||||
/// Input buffer.
|
||||
package WriteBuffer input;
|
||||
|
||||
private SelectorLoop loop;
|
||||
|
||||
/// Received notification that the underlying socket is write-ready.
|
||||
package bool writeReady;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* loop = Event loop.
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(SelectorLoop loop, ConnectedSocket socket)
|
||||
{
|
||||
socket_ = socket;
|
||||
this.loop = loop;
|
||||
input = MmapPool.instance.make!WriteBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the transport and deallocate the data buffers.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Transport socket.
|
||||
*/
|
||||
inout(ConnectedSocket) socket() inout pure nothrow @safe @nogc
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write some data to the transport.
|
||||
*
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data)
|
||||
{
|
||||
if (!data.length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Try to write if the socket is write ready.
|
||||
if (writeReady)
|
||||
{
|
||||
ptrdiff_t sent;
|
||||
SocketException exception;
|
||||
try
|
||||
{
|
||||
sent = socket.send(data);
|
||||
if (sent == 0)
|
||||
{
|
||||
writeReady = false;
|
||||
}
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
writeReady = false;
|
||||
exception = e;
|
||||
}
|
||||
if (sent < data.length)
|
||||
{
|
||||
input ~= data[sent..$];
|
||||
loop.feed(this, exception);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
input ~= data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SelectorLoop : Loop
|
||||
{
|
||||
/// Pending connections.
|
||||
protected ConnectionWatcher[] connections;
|
||||
|
||||
this()
|
||||
{
|
||||
super();
|
||||
connections = MmapPool.instance.makeArray!ConnectionWatcher(maxEvents);
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
foreach (ref connection; connections)
|
||||
{
|
||||
// We want to free only IOWatchers. ConnectionWatcher are created by the
|
||||
// user and should be freed by himself.
|
||||
auto io = cast(IOWatcher) connection;
|
||||
if (io !is null)
|
||||
{
|
||||
MmapPool.instance.dispose(io);
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
MmapPool.instance.dispose(connections);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the transport couldn't send the data, the further sending should
|
||||
* be handled by the event loop.
|
||||
*
|
||||
* Params:
|
||||
* transport = Transport.
|
||||
* exception = Exception thrown on sending.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||
* transport will be destroyed then).
|
||||
*/
|
||||
protected bool feed(SelectorStreamTransport transport, SocketException exception = null)
|
||||
{
|
||||
while (transport.input.length && transport.writeReady)
|
||||
{
|
||||
try
|
||||
{
|
||||
ptrdiff_t sent = transport.socket.send(transport.input[]);
|
||||
if (sent == 0)
|
||||
{
|
||||
transport.writeReady = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
transport.input += sent;
|
||||
}
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
exception = e;
|
||||
transport.writeReady = false;
|
||||
}
|
||||
}
|
||||
if (exception !is null)
|
||||
{
|
||||
auto watcher = cast(IOWatcher) connections[transport.socket.handle];
|
||||
assert(watcher !is null);
|
||||
|
||||
kill(watcher, exception);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start watching.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
override void start(ConnectionWatcher watcher)
|
||||
{
|
||||
if (watcher.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (connections.length <= watcher.socket)
|
||||
{
|
||||
MmapPool.instance.resizeArray(connections, watcher.socket.handle + maxEvents / 2);
|
||||
}
|
||||
connections[watcher.socket.handle] = watcher;
|
||||
|
||||
super.start(watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept incoming connections.
|
||||
*
|
||||
* Params:
|
||||
* connection = Connection watcher ready to accept.
|
||||
*/
|
||||
package void acceptConnections(ConnectionWatcher connection)
|
||||
in
|
||||
{
|
||||
assert(connection !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ConnectedSocket client;
|
||||
try
|
||||
{
|
||||
client = (cast(StreamSocket) connection.socket).accept();
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
defaultAllocator.dispose(e);
|
||||
break;
|
||||
}
|
||||
if (client is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
IOWatcher io;
|
||||
auto transport = MmapPool.instance.make!SelectorStreamTransport(this, client);
|
||||
|
||||
if (connections.length >= client.handle)
|
||||
{
|
||||
io = cast(IOWatcher) connections[client.handle];
|
||||
}
|
||||
else
|
||||
{
|
||||
MmapPool.instance.resizeArray(connections, client.handle + maxEvents / 2);
|
||||
}
|
||||
if (io is null)
|
||||
{
|
||||
io = MmapPool.instance.make!IOWatcher(transport,
|
||||
connection.protocol);
|
||||
connections[client.handle] = io;
|
||||
}
|
||||
else
|
||||
{
|
||||
io(transport, connection.protocol);
|
||||
}
|
||||
|
||||
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
connection.incoming.insertBack(io);
|
||||
}
|
||||
|
||||
if (!connection.incoming.empty)
|
||||
{
|
||||
swapPendings.insertBack(connection);
|
||||
}
|
||||
}
|
||||
}
|
32
source/tanya/async/iocp.d
Normal file
32
source/tanya/async/iocp.d
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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.async.iocp;
|
||||
|
||||
version (Windows):
|
||||
|
||||
import core.sys.windows.winbase;
|
||||
import core.sys.windows.windef;
|
||||
|
||||
/**
|
||||
* Provides an extendable representation of a Win32 $(D_PSYMBOL OVERLAPPED)
|
||||
* structure.
|
||||
*/
|
||||
class State
|
||||
{
|
||||
/// For internal use by Windows API.
|
||||
align(1) OVERLAPPED overlapped;
|
||||
|
||||
/// File/socket handle.
|
||||
HANDLE handle;
|
||||
|
||||
/// For keeping events or event masks.
|
||||
int event;
|
||||
}
|
493
source/tanya/async/loop.d
Normal file
493
source/tanya/async/loop.d
Normal file
@ -0,0 +1,493 @@
|
||||
/* 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)
|
||||
*
|
||||
* ---
|
||||
* import tanya.async;
|
||||
* import tanya.network.socket;
|
||||
*
|
||||
* class EchoProtocol : TransmissionControlProtocol
|
||||
* {
|
||||
* private DuplexTransport transport;
|
||||
*
|
||||
* void received(ubyte[] data)
|
||||
* {
|
||||
* transport.write(data);
|
||||
* }
|
||||
*
|
||||
* void connected(DuplexTransport transport)
|
||||
* {
|
||||
* this.transport = transport;
|
||||
* }
|
||||
*
|
||||
* void disconnected(SocketException exception = null)
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* void main()
|
||||
* {
|
||||
* auto address = new InternetAddress("127.0.0.1", cast(ushort) 8192);
|
||||
* version (Windows)
|
||||
* {
|
||||
* auto sock = new OverlappedStreamSocket(AddressFamily.INET);
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* auto sock = new StreamSocket(AddressFamily.INET);
|
||||
* sock.blocking = false;
|
||||
* }
|
||||
*
|
||||
* sock.bind(address);
|
||||
* sock.listen(5);
|
||||
*
|
||||
* auto io = new ConnectionWatcher(sock);
|
||||
* io.setProtocol!EchoProtocol;
|
||||
*
|
||||
* defaultLoop.start(io);
|
||||
* defaultLoop.run();
|
||||
*
|
||||
* sock.shutdown();
|
||||
* }
|
||||
* ---
|
||||
*/
|
||||
module tanya.async.loop;
|
||||
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.buffer;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
import core.time;
|
||||
import std.algorithm.iteration;
|
||||
import std.algorithm.mutation;
|
||||
import std.typecons;
|
||||
|
||||
version (DisableBackends)
|
||||
{
|
||||
}
|
||||
else version (linux)
|
||||
{
|
||||
import tanya.async.event.epoll;
|
||||
version = Epoll;
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
import tanya.async.event.iocp;
|
||||
version = IOCP;
|
||||
}
|
||||
else version (OSX)
|
||||
{
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (iOS)
|
||||
{
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (FreeBSD)
|
||||
{
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (OpenBSD)
|
||||
{
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (DragonFlyBSD)
|
||||
{
|
||||
version = Kqueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Events.
|
||||
*/
|
||||
enum Event : uint
|
||||
{
|
||||
none = 0x00, /// No events.
|
||||
read = 0x01, /// Non-blocking read call.
|
||||
write = 0x02, /// Non-blocking write call.
|
||||
accept = 0x04, /// Connection made.
|
||||
error = 0x80000000, /// Sent when an error occurs.
|
||||
}
|
||||
|
||||
alias EventMask = BitFlags!Event;
|
||||
|
||||
/**
|
||||
* Tries to set $(D_PSYMBOL MmapPool) to the default allocator.
|
||||
*/
|
||||
shared static this()
|
||||
{
|
||||
if (allocator is null)
|
||||
{
|
||||
allocator = MmapPool.instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event loop.
|
||||
*/
|
||||
abstract class Loop
|
||||
{
|
||||
/// Pending watchers.
|
||||
protected PendingQueue!Watcher pendings;
|
||||
|
||||
protected PendingQueue!Watcher swapPendings;
|
||||
|
||||
/**
|
||||
* Returns: Maximal event count can be got at a time
|
||||
* (should be supported by the backend).
|
||||
*/
|
||||
protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return 128U;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
{
|
||||
pendings = MmapPool.instance.make!(PendingQueue!Watcher);
|
||||
swapPendings = MmapPool.instance.make!(PendingQueue!Watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(pendings);
|
||||
MmapPool.instance.dispose(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;
|
||||
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
void stop(ConnectionWatcher watcher)
|
||||
{
|
||||
if (!watcher.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
watcher.active = false;
|
||||
|
||||
reify(watcher, EventMask(Event.accept), EventMask(Event.none));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if the backend configuration changes.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
* oldEvents = The events were already set.
|
||||
* events = The events should be set.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||
*/
|
||||
abstract protected bool reify(ConnectionWatcher watcher,
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the watcher and closes the connection.
|
||||
*/
|
||||
protected void kill(IOWatcher watcher, SocketException exception)
|
||||
{
|
||||
watcher.socket.shutdown();
|
||||
defaultAllocator.dispose(watcher.socket);
|
||||
MmapPool.instance.dispose(watcher.transport);
|
||||
watcher.exception = exception;
|
||||
swapPendings.insertBack(watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
abstract protected void poll();
|
||||
|
||||
/// 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 defaultLoop) will try to
|
||||
* choose an event loop supported on the system.
|
||||
*
|
||||
* Returns: The default event loop.
|
||||
*/
|
||||
@property Loop defaultLoop()
|
||||
{
|
||||
if (defaultLoop_ !is null)
|
||||
{
|
||||
return defaultLoop_;
|
||||
}
|
||||
version (Epoll)
|
||||
{
|
||||
defaultLoop_ = MmapPool.instance.make!EpollLoop;
|
||||
}
|
||||
else version (IOCP)
|
||||
{
|
||||
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
|
||||
}
|
||||
else version (Kqueue)
|
||||
{
|
||||
import tanya.async.event.kqueue;
|
||||
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
|
||||
}
|
||||
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_;
|
||||
|
||||
/**
|
||||
* Queue.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
*/
|
||||
class PendingQueue(T)
|
||||
{
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL Queue).
|
||||
*/
|
||||
this()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
foreach (e; this)
|
||||
{
|
||||
MmapPool.instance.dispose(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 = MmapPool.instance.make!Entry;
|
||||
|
||||
temp.content = x;
|
||||
|
||||
if (empty)
|
||||
{
|
||||
first.next = rear = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
rear.next = temp;
|
||||
rear = rear.next;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
alias insert = insertBack;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||
*/
|
||||
@property bool empty() const @safe pure nothrow
|
||||
{
|
||||
return first.next is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move position to the next element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) popFront()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto n = first.next.next;
|
||||
|
||||
MmapPool.instance.dispose(first.next);
|
||||
first.next = n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
@ -8,9 +8,12 @@
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event;
|
||||
module tanya.async;
|
||||
|
||||
public import tanya.event.loop;
|
||||
public import tanya.event.protocol;
|
||||
public import tanya.event.transport;
|
||||
public import tanya.event.watcher;
|
||||
public
|
||||
{
|
||||
import tanya.async.loop;
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
}
|
50
source/tanya/async/protocol.d
Normal file
50
source/tanya/async/protocol.d
Normal file
@ -0,0 +1,50 @@
|
||||
/* 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.async.protocol;
|
||||
|
||||
import tanya.network.socket;
|
||||
import tanya.async.transport;
|
||||
|
||||
/**
|
||||
* Common protocol interface.
|
||||
*/
|
||||
interface Protocol
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Params:
|
||||
* exception = $(D_PSYMBOL Exception) if an error caused
|
||||
* the disconnect, $(D_KEYWORD null) otherwise.
|
||||
*/
|
||||
void disconnected(SocketException exception = null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for TCP.
|
||||
*/
|
||||
interface TransmissionControlProtocol : Protocol
|
||||
{
|
||||
}
|
63
source/tanya/async/transport.d
Normal file
63
source/tanya/async/transport.d
Normal file
@ -0,0 +1,63 @@
|
||||
/* 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.async.transport;
|
||||
|
||||
import tanya.network.socket;
|
||||
|
||||
/**
|
||||
* Base transport interface.
|
||||
*/
|
||||
interface Transport
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for read-only transports.
|
||||
*/
|
||||
interface ReadTransport : Transport
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for write-only transports.
|
||||
*/
|
||||
interface WriteTransport : Transport
|
||||
{
|
||||
/**
|
||||
* Write some data to the transport.
|
||||
*
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a bidirectional transport.
|
||||
*/
|
||||
interface DuplexTransport : ReadTransport, WriteTransport
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a socket transport.
|
||||
*/
|
||||
interface SocketTransport : Transport
|
||||
{
|
||||
@property inout(Socket) socket() inout pure nothrow @safe @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a connection-oriented socket transport.
|
||||
*/
|
||||
package interface StreamTransport : DuplexTransport, SocketTransport
|
||||
{
|
||||
}
|
242
source/tanya/async/watcher.d
Normal file
242
source/tanya/async/watcher.d
Normal file
@ -0,0 +1,242 @@
|
||||
/* 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.async.watcher;
|
||||
|
||||
import tanya.async.loop;
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.container.buffer;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
import std.functional;
|
||||
import std.exception;
|
||||
|
||||
version (Windows)
|
||||
{
|
||||
import core.sys.windows.basetyps;
|
||||
import core.sys.windows.mswsock;
|
||||
import core.sys.windows.winbase;
|
||||
import core.sys.windows.windef;
|
||||
import core.sys.windows.winsock2;
|
||||
}
|
||||
|
||||
/**
|
||||
* A watcher is an opaque structure that you allocate and register to record
|
||||
* your interest in some event.
|
||||
*/
|
||||
abstract class Watcher
|
||||
{
|
||||
/// Whether the watcher is active.
|
||||
bool active;
|
||||
|
||||
/**
|
||||
* Invoke some action on event.
|
||||
*/
|
||||
void invoke();
|
||||
}
|
||||
|
||||
class ConnectionWatcher : Watcher
|
||||
{
|
||||
/// Watched socket.
|
||||
private Socket socket_;
|
||||
|
||||
/// Protocol factory.
|
||||
protected Protocol delegate() protocolFactory;
|
||||
|
||||
package PendingQueue!IOWatcher incoming;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(Socket socket)
|
||||
{
|
||||
socket_ = socket;
|
||||
incoming = MmapPool.instance.make!(PendingQueue!IOWatcher);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
protected this()
|
||||
{
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(incoming);
|
||||
}
|
||||
|
||||
/*
|
||||
* Params:
|
||||
* P = Protocol should be used.
|
||||
*/
|
||||
void setProtocol(P : Protocol)()
|
||||
{
|
||||
this.protocolFactory = () => cast(Protocol) MmapPool.instance.make!P;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
@property inout(Socket) socket() inout pure nothrow @nogc
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: New protocol instance.
|
||||
*/
|
||||
@property Protocol protocol()
|
||||
in
|
||||
{
|
||||
assert(protocolFactory !is null, "Protocol isn't set.");
|
||||
}
|
||||
body
|
||||
{
|
||||
return protocolFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes new connection callback.
|
||||
*/
|
||||
override void invoke()
|
||||
{
|
||||
foreach (io; incoming)
|
||||
{
|
||||
io.protocol.connected(cast(DuplexTransport) io.transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains a pending watcher with the invoked events or a transport can be
|
||||
* read from.
|
||||
*/
|
||||
class IOWatcher : ConnectionWatcher
|
||||
{
|
||||
/// If an exception was thrown the transport should be already invalid.
|
||||
private union
|
||||
{
|
||||
StreamTransport transport_;
|
||||
SocketException exception_;
|
||||
}
|
||||
|
||||
private Protocol protocol_;
|
||||
|
||||
/**
|
||||
* Returns: Underlying output buffer.
|
||||
*/
|
||||
package ReadBuffer output;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* transport = Transport.
|
||||
* protocol = New instance of the application protocol.
|
||||
*/
|
||||
this(StreamTransport transport, Protocol protocol)
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
assert(protocol !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
super();
|
||||
transport_ = transport;
|
||||
protocol_ = protocol;
|
||||
output = MmapPool.instance.make!ReadBuffer();
|
||||
active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the watcher.
|
||||
*/
|
||||
protected ~this()
|
||||
{
|
||||
MmapPool.instance.dispose(output);
|
||||
MmapPool.instance.dispose(protocol_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a transport.
|
||||
*
|
||||
* Params:
|
||||
* transport = Transport.
|
||||
* protocol = Application protocol.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
IOWatcher opCall(StreamTransport transport, Protocol protocol) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
assert(protocol !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
transport_ = transport;
|
||||
protocol_ = protocol;
|
||||
active = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Transport used by this watcher.
|
||||
*/
|
||||
@property inout(StreamTransport) transport() inout pure nothrow @nogc
|
||||
{
|
||||
return transport_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an exception occurred during a read/write operation.
|
||||
*
|
||||
* Params:
|
||||
* exception = Thrown exception.
|
||||
*/
|
||||
@property void exception(SocketException exception) pure nothrow @nogc
|
||||
{
|
||||
exception_ = exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Application protocol.
|
||||
*/
|
||||
override @property Protocol protocol() pure nothrow @safe @nogc
|
||||
{
|
||||
return protocol_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
override @property inout(Socket) socket() inout pure nothrow @nogc
|
||||
{
|
||||
return transport.socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the watcher callback.
|
||||
*/
|
||||
override void invoke()
|
||||
{
|
||||
if (output.length)
|
||||
{
|
||||
protocol.received(output[0..$]);
|
||||
output.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
protocol.disconnected(exception_);
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
}
|
510
source/tanya/container/bit.d
Normal file
510
source/tanya/container/bit.d
Normal file
@ -0,0 +1,510 @@
|
||||
/* 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.bit;
|
||||
|
||||
/**
|
||||
* Wrapper that allows bit manipulation on $(D_KEYWORD ubyte[]) array.
|
||||
*/
|
||||
struct BitVector
|
||||
{
|
||||
protected ubyte[] vector;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* array = Array should be manipulated on.
|
||||
*/
|
||||
this(inout(ubyte[]) array) inout pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(array.length <= size_t.max / 8);
|
||||
assert(array !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
vector = array;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
ubyte[5] array1 = [234, 3, 252, 10, 18];
|
||||
ubyte[3] array2 = [65, 13, 173];
|
||||
auto bits = BitVector(array1);
|
||||
|
||||
assert(bits[] is array1);
|
||||
assert(bits[] !is array2);
|
||||
|
||||
bits = BitVector(array2);
|
||||
assert(bits[] is array2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Number of bits in the vector.
|
||||
*/
|
||||
@property inout(size_t) length() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return vector.length * 8;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
inout(size_t) opDollar() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return vector.length * 8;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
// [01000001, 00001101, 10101101]
|
||||
ubyte[3] arr = [65, 13, 173];
|
||||
auto bits = BitVector(arr);
|
||||
|
||||
assert(bits.length == 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* bit = Bit position.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the bit on position $(D_PARAM bit) is set,
|
||||
* $(D_KEYWORD false) if not set.
|
||||
*/
|
||||
inout(bool) opIndex(size_t bit) inout const pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(bit / 8 <= vector.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
return (vector[bit / 8] & (0x80 >> (bit % 8))) != 0;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
// [01000001, 00001101, 10101101]
|
||||
ubyte[3] arr = [65, 13, 173];
|
||||
auto bits = BitVector(arr);
|
||||
|
||||
assert(!bits[0]);
|
||||
assert(bits[1]);
|
||||
assert(bits[7]);
|
||||
assert(!bits[8]);
|
||||
assert(!bits[11]);
|
||||
assert(bits[12]);
|
||||
assert(bits[20]);
|
||||
assert(bits[23]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Underlying array.
|
||||
*/
|
||||
inout(ubyte[]) opIndex() inout pure nothrow @safe @nogc
|
||||
{
|
||||
return vector;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
// [01000001, 00001101, 10101101]
|
||||
ubyte[3] arr = [65, 13, 173];
|
||||
auto bits = BitVector(arr);
|
||||
|
||||
assert(bits[] is arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* value = $(D_KEYWORD true) if the bit should be set,
|
||||
* $(D_KEYWORD false) if cleared.
|
||||
* bit = Bit position.
|
||||
*
|
||||
* Returns: $(D_PSYMBOL this).
|
||||
*/
|
||||
bool opIndexAssign(bool value, size_t bit) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(bit / 8 <= vector.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
vector[bit / 8] |= (0x80 >> (bit % 8));
|
||||
}
|
||||
else
|
||||
{
|
||||
vector[bit / 8] &= ~(0x80 >> (bit % 8));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
// [01000001, 00001101, 10101101]
|
||||
ubyte[3] arr = [65, 13, 173];
|
||||
auto bits = BitVector(arr);
|
||||
|
||||
bits[5] = bits[6] = true;
|
||||
assert(bits[][0] == 71);
|
||||
|
||||
bits[14] = true;
|
||||
bits[15] = false;
|
||||
assert(bits[][1] == 14);
|
||||
|
||||
bits[16] = bits[23] = false;
|
||||
assert(bits[][2] == 44);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies bits from $(D_PARAM vector) into this $(D_PSYMBOL BitVector).
|
||||
*
|
||||
* The array that should be assigned, can be smaller (but not larger) than
|
||||
* the underlying array of this $(D_PSYMBOL BitVector), leading zeros will
|
||||
* be added in this case to the left.
|
||||
*
|
||||
* Params:
|
||||
* vector = $(D_KEYWORD ubyte[]) array not larger than
|
||||
* `$(D_PSYMBOL length) / 8`.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
BitVector opAssign(ubyte[] vector) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(vector.length <= this.vector.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
immutable delta = this.vector.length - vector.length;
|
||||
if (delta > 0)
|
||||
{
|
||||
this.vector[0..delta] = 0;
|
||||
}
|
||||
this.vector[delta..$] = vector[0..$];
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
ubyte[5] array1 = [234, 3, 252, 10, 18];
|
||||
ubyte[3] array2 = [65, 13, 173];
|
||||
auto bits = BitVector(array1);
|
||||
|
||||
bits = array2;
|
||||
assert(bits[][0] == 0);
|
||||
assert(bits[][1] == 0);
|
||||
assert(bits[][2] == 65);
|
||||
assert(bits[][3] == 13);
|
||||
assert(bits[][4] == 173);
|
||||
|
||||
bits = array2[0..2];
|
||||
assert(bits[][0] == 0);
|
||||
assert(bits[][1] == 0);
|
||||
assert(bits[][2] == 0);
|
||||
assert(bits[][3] == 65);
|
||||
assert(bits[][4] == 13);
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for bitwise operations.
|
||||
*
|
||||
* Params:
|
||||
* that = Another bit vector.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
BitVector opOpAssign(string op)(BitVector that) pure nothrow @safe @nogc
|
||||
if ((op == "^") || (op == "|") || (op == "&"))
|
||||
{
|
||||
return opOpAssign(op)(that.vector);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
BitVector opOpAssign(string op)(ubyte[] that) pure nothrow @safe @nogc
|
||||
if ((op == "^") || (op == "|") || (op == "&"))
|
||||
in
|
||||
{
|
||||
assert(that.length <= vector.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
for (int i = cast(int) vector.length - 1; i >= 0; --i)
|
||||
{
|
||||
mixin("vector[i] " ~ op ~ "= " ~ "that[i];");
|
||||
}
|
||||
immutable delta = vector.length - that.length;
|
||||
if (delta)
|
||||
{
|
||||
static if (op == "&")
|
||||
{
|
||||
vector[0..delta] = 0;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
// [01000001, 00001101, 10101101]
|
||||
ubyte[3] array1 = [65, 13, 173];
|
||||
ubyte[3] array2 = [0b01010010, 0b10111110, 0b10111110];
|
||||
auto bits = BitVector(array1);
|
||||
|
||||
bits |= array2;
|
||||
assert(bits[][0] == 0b01010011);
|
||||
assert(bits[][1] == 0b10111111);
|
||||
assert(bits[][2] == 0b10111111);
|
||||
|
||||
bits &= array2;
|
||||
assert(bits[][0] == array2[0]);
|
||||
assert(bits[][1] == array2[1]);
|
||||
assert(bits[][2] == array2[2]);
|
||||
|
||||
bits ^= array2;
|
||||
assert(bits[][0] == 0);
|
||||
assert(bits[][1] == 0);
|
||||
assert(bits[][2] == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for shift operations.
|
||||
*
|
||||
* Params:
|
||||
* n = Number of bits.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
BitVector opOpAssign(string op)(in size_t n) pure nothrow @safe @nogc
|
||||
if ((op == "<<") || (op == ">>"))
|
||||
|
||||
{
|
||||
if (n >= length)
|
||||
{
|
||||
vector[0..$] = 0;
|
||||
}
|
||||
else if (n != 0)
|
||||
{
|
||||
immutable bit = n % 8, step = n / 8;
|
||||
immutable delta = 8 - bit;
|
||||
size_t i, j;
|
||||
|
||||
static if (op == "<<")
|
||||
{
|
||||
for (j = step; j < vector.length - 1; ++i)
|
||||
{
|
||||
vector[i] = cast(ubyte)((vector[j] << bit)
|
||||
| vector[++j] >> delta);
|
||||
}
|
||||
vector[i] = cast(ubyte)(vector[j] << bit);
|
||||
vector[$ - step ..$] = 0;
|
||||
}
|
||||
else static if (op == ">>")
|
||||
{
|
||||
for (i = vector.length - 1, j = i - step; j > 0; --i)
|
||||
{
|
||||
vector[i] = cast(ubyte)((vector[j] >> bit)
|
||||
| vector[--j] << delta);
|
||||
}
|
||||
vector[i] = cast(ubyte)(vector[j] >> bit);
|
||||
vector[0..step] = 0;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
nothrow @safe @nogc unittest
|
||||
{
|
||||
ubyte[4] arr = [0b10111110, 0b11110010, 0b01010010, 0b01010011];
|
||||
auto bits = BitVector(arr);
|
||||
|
||||
bits <<= 0;
|
||||
assert(bits[][0] == 0b10111110 && bits[][1] == 0b11110010
|
||||
&& bits[][2] == 0b01010010 && bits[][3] == 0b01010011);
|
||||
|
||||
bits <<= 2;
|
||||
assert(bits[][0] == 0b11111011 && bits[][1] == 0b11001001
|
||||
&& bits[][2] == 0b01001001 && bits[][3] == 0b01001100);
|
||||
|
||||
bits <<= 4;
|
||||
assert(bits[][0] == 0b10111100 && bits[][1] == 0b10010100
|
||||
&& bits[][2] == 0b10010100 && bits[][3] == 0b11000000);
|
||||
|
||||
bits <<= 8;
|
||||
assert(bits[][0] == 0b10010100 && bits[][1] == 0b10010100
|
||||
&& bits[][2] == 0b11000000 && bits[][3] == 0b00000000);
|
||||
|
||||
bits <<= 7;
|
||||
assert(bits[][0] == 0b01001010 && bits[][1] == 0b01100000
|
||||
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
|
||||
|
||||
bits <<= 25;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
|
||||
|
||||
arr = [0b00110011, 0b11001100, 0b11111111, 0b01010101];
|
||||
bits <<= 24;
|
||||
assert(bits[][0] == 0b01010101 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
|
||||
|
||||
arr[1] = 0b11001100;
|
||||
arr[2] = 0b11111111;
|
||||
arr[3] = 0b01010101;
|
||||
bits <<= 12;
|
||||
assert(bits[][0] == 0b11001111 && bits[][1] == 0b11110101
|
||||
&& bits[][2] == 0b01010000 && bits[][3] == 0b00000000);
|
||||
|
||||
bits <<= 100;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
|
||||
|
||||
arr = [0b10111110, 0b11110010, 0b01010010, 0b01010011];
|
||||
bits >>= 0;
|
||||
assert(bits[][0] == 0b10111110 && bits[][1] == 0b11110010
|
||||
&& bits[][2] == 0b01010010 && bits[][3] == 0b01010011);
|
||||
|
||||
bits >>= 2;
|
||||
assert(bits[][0] == 0b00101111 && bits[][1] == 0b10111100
|
||||
&& bits[][2] == 0b10010100 && bits[][3] == 0b10010100);
|
||||
|
||||
bits >>= 4;
|
||||
assert(bits[][0] == 0b00000010 && bits[][1] == 0b11111011
|
||||
&& bits[][2] == 0b11001001 && bits[][3] == 0b01001001);
|
||||
|
||||
bits >>= 8;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000010
|
||||
&& bits[][2] == 0b11111011 && bits[][3] == 0b11001001);
|
||||
|
||||
bits >>= 7;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00000101 && bits[][3] == 0b11110111);
|
||||
|
||||
bits >>= 25;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
|
||||
|
||||
arr = [0b00110011, 0b11001100, 0b11111111, 0b01010101];
|
||||
bits >>= 24;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00000000 && bits[][3] == 0b00110011);
|
||||
|
||||
arr[1] = 0b11001100;
|
||||
arr[2] = 0b11111111;
|
||||
arr[3] = 0b01010101;
|
||||
bits >>= 12;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00001100 && bits[][3] == 0b11001111);
|
||||
|
||||
bits >>= 100;
|
||||
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
|
||||
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates all bits.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
BitVector opUnary(string op)() pure nothrow @safe @nogc
|
||||
if (op == "~")
|
||||
{
|
||||
foreach (ref b; vector)
|
||||
{
|
||||
b = ~b;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
// [01000001, 00001101, 10101101]
|
||||
ubyte[3] arr = [65, 13, 173];
|
||||
auto bits = BitVector(arr);
|
||||
|
||||
~bits;
|
||||
assert(bits[][0] == 0b10111110);
|
||||
assert(bits[][1] == 0b11110010);
|
||||
assert(bits[][2] == 0b01010010);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through all bits.
|
||||
*
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) delegate.
|
||||
*
|
||||
* Returns: By $(D_PARAM dg) returned value.
|
||||
*/
|
||||
int opApply(int delegate(size_t, bool) dg)
|
||||
{
|
||||
int result;
|
||||
foreach (i, ref v; vector)
|
||||
{
|
||||
foreach (c; 0..8)
|
||||
{
|
||||
result = dg(i * 8 + c, (v & (0x80 >> c)) != 0);
|
||||
if (result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int opApply(int delegate(bool) dg)
|
||||
{
|
||||
int result;
|
||||
foreach (ref v; vector)
|
||||
{
|
||||
foreach (c; 0..8)
|
||||
{
|
||||
result = dg((v & (0x80 >> c)) != 0);
|
||||
if (result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
ubyte[2] arr = [0b01000001, 0b00001101];
|
||||
auto bits = BitVector(arr);
|
||||
size_t c;
|
||||
|
||||
foreach (i, v; bits)
|
||||
{
|
||||
assert(i == c);
|
||||
if (i == 1 || i == 7 || i == 15 || i == 13 || i == 12)
|
||||
{
|
||||
assert(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(!v);
|
||||
}
|
||||
++c;
|
||||
}
|
||||
assert(c == 16);
|
||||
}
|
||||
}
|
@ -12,26 +12,22 @@ module tanya.container.buffer;
|
||||
|
||||
import tanya.memory;
|
||||
|
||||
@nogc:
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
private int fillBuffer(void* buffer,
|
||||
private int fillBuffer(ubyte[] buffer,
|
||||
in size_t size,
|
||||
int start = 0,
|
||||
int end = 10)
|
||||
int end = 10) @nogc pure nothrow
|
||||
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);
|
||||
buffer[i] = cast(ubyte) (start + i);
|
||||
}
|
||||
return numberRead;
|
||||
}
|
||||
@ -42,44 +38,69 @@ version (unittest)
|
||||
*/
|
||||
interface Buffer
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @safe pure nothrow;
|
||||
@property size_t capacity() const @nogc @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @safe pure nothrow;
|
||||
@property size_t length() const @nogc @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @safe pure nothrow;
|
||||
@property size_t free() const @nogc @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Appends some data to the buffer.
|
||||
*
|
||||
* Params:
|
||||
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
|
||||
* start = Start position.
|
||||
* end = End position.
|
||||
*
|
||||
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
|
||||
*/
|
||||
Buffer opOpAssign(string op)(void[] buffer)
|
||||
if (op == "~");
|
||||
@property ubyte[] opSlice(size_t start, size_t end)
|
||||
in
|
||||
{
|
||||
assert(start <= end);
|
||||
assert(end <= length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer that can be used with C functions accepting void pointer and
|
||||
* returning the number of the read bytes.
|
||||
* Returns: Length of available data.
|
||||
*/
|
||||
@property size_t opDollar() const pure nothrow @safe @nogc;
|
||||
|
||||
/**
|
||||
* Returns: Data chunk.
|
||||
*/
|
||||
@property ubyte[] opIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Self-expanding buffer, that can be used with functions returning the number
|
||||
* of the read bytes.
|
||||
*
|
||||
* This buffer supports asynchronous reading. It means you can pass a new chunk
|
||||
* to an asynchronous read function during you are working with already
|
||||
* available data. But only one asynchronous call at a time is supported. Be
|
||||
* sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result
|
||||
* of the pended asynchronous call.
|
||||
*/
|
||||
class ReadBuffer : Buffer
|
||||
{
|
||||
@nogc:
|
||||
/// Internal buffer.
|
||||
protected ubyte[] _buffer;
|
||||
protected ubyte[] buffer_;
|
||||
|
||||
/// Filled buffer length.
|
||||
protected size_t _length;
|
||||
protected size_t length_;
|
||||
|
||||
/// Start of available data.
|
||||
protected size_t start;
|
||||
|
||||
/// Last position returned with $(D_KEYWORD []).
|
||||
protected size_t ring;
|
||||
|
||||
/// Available space.
|
||||
protected immutable size_t minAvailable;
|
||||
@ -87,16 +108,16 @@ class ReadBuffer : Buffer
|
||||
/// Size by which the buffer will grow.
|
||||
protected immutable size_t blockSize;
|
||||
|
||||
private Allocator allocator;
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(_length <= _buffer.length);
|
||||
assert(length_ <= buffer_.length);
|
||||
assert(blockSize > 0);
|
||||
assert(minAvailable > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new read buffer.
|
||||
*
|
||||
* Params:
|
||||
* size = Initial buffer size and the size by which the buffer
|
||||
* will grow.
|
||||
@ -106,13 +127,11 @@ class ReadBuffer : Buffer
|
||||
* ).
|
||||
*/
|
||||
this(size_t size = 8192,
|
||||
size_t minAvailable = 1024,
|
||||
Allocator allocator = defaultAllocator)
|
||||
size_t minAvailable = 1024)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
this.minAvailable = minAvailable;
|
||||
this.blockSize = size;
|
||||
resizeArray!ubyte(this.allocator, _buffer, size);
|
||||
defaultAllocator.resizeArray!ubyte(buffer_, size);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,172 +139,193 @@ class ReadBuffer : Buffer
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(allocator, _buffer);
|
||||
defaultAllocator.dispose(buffer_);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
auto b = defaultAllocator.make!ReadBuffer;
|
||||
assert(b.capacity == 8192);
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @safe pure nothrow
|
||||
@property size_t capacity() const @nogc @safe pure nothrow
|
||||
{
|
||||
return _buffer.length;
|
||||
return buffer_.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @safe pure nothrow
|
||||
@property size_t length() const @nogc @safe pure nothrow
|
||||
{
|
||||
return _length;
|
||||
return length_ - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the buffer.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
ReadBuffer clear() pure nothrow @safe @nogc
|
||||
{
|
||||
start = length_ = ring;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @safe pure nothrow
|
||||
@property size_t free() const pure nothrow @safe @nogc
|
||||
{
|
||||
return capacity - length;
|
||||
return length > ring ? capacity - length : capacity - ring;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
auto b = defaultAllocator.make!ReadBuffer;
|
||||
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];
|
||||
|
||||
numberRead = fillBuffer(b[], b.free, 0, 10);
|
||||
b += numberRead;
|
||||
assert(b.free == b.blockSize - numberRead);
|
||||
b[];
|
||||
b.clear();
|
||||
assert(b.free == b.blockSize);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(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)!
|
||||
* Appends some data to the buffer.
|
||||
*
|
||||
* Params:
|
||||
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
|
||||
* length = Number of the bytes read.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
ReadBuffer opOpAssign(string op)(void[] buffer)
|
||||
if (op == "~")
|
||||
ReadBuffer opOpAssign(string op)(size_t length)
|
||||
if (op == "+")
|
||||
{
|
||||
_length += buffer.length;
|
||||
length_ += length;
|
||||
ring = start;
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
auto b = defaultAllocator.make!ReadBuffer;
|
||||
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];
|
||||
numberRead = fillBuffer(b[], b.free, 0, 10);
|
||||
b += numberRead;
|
||||
|
||||
result = b[];
|
||||
result = b[0..$];
|
||||
assert(result[0] == 0);
|
||||
assert(result[1] == 1);
|
||||
assert(result[9] == 9);
|
||||
b.clear();
|
||||
|
||||
// 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];
|
||||
numberRead = fillBuffer(b[], b.free, 0, 10);
|
||||
b += numberRead;
|
||||
|
||||
buf = b.buffer;
|
||||
numberRead = fillBuffer(buf, b.free, 20, 25);
|
||||
b ~= buf[0..numberRead];
|
||||
numberRead = fillBuffer(b[], b.free, 20, 25);
|
||||
b += numberRead;
|
||||
|
||||
result = b[];
|
||||
result = b[0..$];
|
||||
assert(result[0] == 0);
|
||||
assert(result[1] == 1);
|
||||
assert(result[9] == 9);
|
||||
assert(result[10] == 20);
|
||||
assert(result[14] == 24);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the buffer. The buffer is cleared after that. So you can get it
|
||||
* only one time.
|
||||
*
|
||||
* Returns: The buffer as array.
|
||||
* Returns: Length of available data.
|
||||
*/
|
||||
@property ubyte[] opIndex()
|
||||
@property size_t opDollar() const pure nothrow @safe @nogc
|
||||
{
|
||||
auto ret = _buffer[0.._length];
|
||||
_length = 0;
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* start = Start position.
|
||||
* end = End position.
|
||||
*
|
||||
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
|
||||
*/
|
||||
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
|
||||
{
|
||||
return buffer_[this.start + start .. this.start + end];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a free chunk of the buffer.
|
||||
*
|
||||
* Add ($(D_KEYWORD +=)) the number of the read bytes after using it.
|
||||
*
|
||||
* Returns: A free chunk of the buffer.
|
||||
*/
|
||||
ubyte[] opIndex()
|
||||
{
|
||||
if (start > 0)
|
||||
{
|
||||
auto ret = buffer_[0..start];
|
||||
ring = 0;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (capacity - length < minAvailable)
|
||||
{
|
||||
defaultAllocator.resizeArray!ubyte(buffer_, capacity + blockSize);
|
||||
}
|
||||
ring = length_;
|
||||
return buffer_[length_..$];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
auto b = defaultAllocator.make!ReadBuffer;
|
||||
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];
|
||||
numberRead = fillBuffer(b[], b.free, 0, 10);
|
||||
b += numberRead;
|
||||
|
||||
assert(b.length == 10);
|
||||
result = b[];
|
||||
result = b[0..$];
|
||||
assert(result[0] == 0);
|
||||
assert(result[9] == 9);
|
||||
b.clear();
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Circular, self-expanding buffer that can be used with C functions accepting
|
||||
* void pointer and returning the number of the read bytes.
|
||||
* Circular, self-expanding buffer with overflow support. Can be used with
|
||||
* functions returning returning the number of the transferred 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
|
||||
@ -294,9 +334,8 @@ class ReadBuffer : Buffer
|
||||
*/
|
||||
class WriteBuffer : Buffer
|
||||
{
|
||||
@nogc:
|
||||
/// Internal buffer.
|
||||
protected ubyte[] _buffer;
|
||||
protected ubyte[] buffer_;
|
||||
|
||||
/// Buffer start position.
|
||||
protected size_t start;
|
||||
@ -310,13 +349,11 @@ class WriteBuffer : Buffer
|
||||
/// 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);
|
||||
assert(position <= buffer_.length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -324,13 +361,11 @@ class WriteBuffer : Buffer
|
||||
* size = Initial buffer size and the size by which the buffer
|
||||
* will grow.
|
||||
*/
|
||||
this(size_t size = 8192,
|
||||
Allocator allocator = defaultAllocator)
|
||||
this(size_t size = 8192)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
blockSize = size;
|
||||
ring = size - 1;
|
||||
resizeArray!ubyte(this.allocator, _buffer, size);
|
||||
defaultAllocator.resizeArray!ubyte(buffer_, size);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,26 +373,26 @@ class WriteBuffer : Buffer
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(allocator, _buffer);
|
||||
defaultAllocator.dispose(buffer_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @safe pure nothrow
|
||||
@property size_t capacity() const @nogc @safe pure nothrow
|
||||
{
|
||||
return _buffer.length;
|
||||
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)
|
||||
* next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_KEYWORD +=)
|
||||
* until $(D_PSYMBOL length) returns 0.
|
||||
*
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @safe pure nothrow
|
||||
@property size_t length() const @nogc @safe pure nothrow
|
||||
{
|
||||
if (position > ring || position < start) // Buffer overflowed
|
||||
{
|
||||
@ -369,34 +404,42 @@ class WriteBuffer : Buffer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Length of available data.
|
||||
*/
|
||||
@property size_t opDollar() const pure nothrow @safe @nogc
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator, 4);
|
||||
auto b = defaultAllocator.make!WriteBuffer(4);
|
||||
ubyte[3] buf = [48, 23, 255];
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 3);
|
||||
b.written = 2;
|
||||
b += 2;
|
||||
assert(b.length == 1);
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 2);
|
||||
b.written = 2;
|
||||
b += 2;
|
||||
assert(b.length == 2);
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 5);
|
||||
b.written = b.length;
|
||||
b += b.length;
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @safe pure nothrow
|
||||
@property size_t free() const @nogc @safe pure nothrow
|
||||
{
|
||||
return capacity - length;
|
||||
}
|
||||
@ -422,7 +465,7 @@ class WriteBuffer : Buffer
|
||||
end = afterRing;
|
||||
}
|
||||
start = end - position;
|
||||
_buffer[position..end] = buffer[0..start];
|
||||
buffer_[position..end] = buffer[0..start];
|
||||
if (end == afterRing)
|
||||
{
|
||||
position = this.start == 0 ? afterRing : 0;
|
||||
@ -442,7 +485,7 @@ class WriteBuffer : Buffer
|
||||
end = this.start;
|
||||
}
|
||||
auto areaEnd = end - position + start;
|
||||
_buffer[position..end] = buffer[start..areaEnd];
|
||||
buffer_[position..end] = buffer[start..areaEnd];
|
||||
position = end == this.start ? ring + 1 : end - position;
|
||||
start = areaEnd;
|
||||
}
|
||||
@ -455,9 +498,9 @@ class WriteBuffer : Buffer
|
||||
{
|
||||
auto newSize = end / blockSize * blockSize + blockSize;
|
||||
|
||||
resizeArray!ubyte(this.allocator, _buffer, newSize);
|
||||
defaultAllocator.resizeArray!ubyte(buffer_, newSize);
|
||||
}
|
||||
_buffer[position..end] = buffer[start..$];
|
||||
buffer_[position..end] = buffer[start..$];
|
||||
position = end;
|
||||
if (this.start == 0)
|
||||
{
|
||||
@ -471,26 +514,26 @@ class WriteBuffer : Buffer
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator, 4);
|
||||
auto b = defaultAllocator.make!WriteBuffer(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);
|
||||
assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255);
|
||||
|
||||
b.written = 2;
|
||||
b += 2;
|
||||
b ~= buf;
|
||||
assert(b.capacity == 4);
|
||||
assert(b._buffer[0] == 23 && b._buffer[1] == 255
|
||||
&& b._buffer[2] == 255 && b._buffer[3] == 48);
|
||||
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
|
||||
&& b.buffer_[2] == 255 && b.buffer_[3] == 48);
|
||||
|
||||
b.written = 2;
|
||||
b += 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);
|
||||
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
|
||||
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
|
||||
b = make!WriteBuffer(defaultAllocator, 2);
|
||||
|
||||
@ -500,7 +543,7 @@ class WriteBuffer : Buffer
|
||||
assert(b.ring == 3);
|
||||
assert(b.position == 3);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -510,8 +553,11 @@ class WriteBuffer : Buffer
|
||||
*
|
||||
* Params:
|
||||
* length = Length of the written data.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
@property void written(size_t length) @safe pure nothrow
|
||||
@property WriteBuffer opOpAssign(string op)(size_t length) pure nothrow @safe @nogc
|
||||
if (op == "+")
|
||||
in
|
||||
{
|
||||
assert(length <= this.length);
|
||||
@ -523,7 +569,7 @@ class WriteBuffer : Buffer
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
return;
|
||||
return this;
|
||||
}
|
||||
else if (position <= afterRing)
|
||||
{
|
||||
@ -538,18 +584,18 @@ class WriteBuffer : Buffer
|
||||
auto overflow = position - afterRing;
|
||||
|
||||
if (overflow > length) {
|
||||
_buffer[start.. start + length] = _buffer[afterRing.. afterRing + length];
|
||||
_buffer[afterRing.. afterRing + length] = _buffer[afterRing + length ..position];
|
||||
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];
|
||||
buffer_[start.. start + overflow] = buffer_[afterRing..position];
|
||||
position -= overflow;
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffer[start.. start + overflow] = _buffer[afterRing..position];
|
||||
buffer_[start.. start + overflow] = buffer_[afterRing..position];
|
||||
position = overflow;
|
||||
}
|
||||
start += length;
|
||||
@ -568,74 +614,87 @@ class WriteBuffer : Buffer
|
||||
{
|
||||
start = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator);
|
||||
auto b = defaultAllocator.make!WriteBuffer;
|
||||
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 6);
|
||||
b.written = 2;
|
||||
b += 2;
|
||||
assert(b.length == 4);
|
||||
b.written = 4;
|
||||
b += 4;
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to a buffer chunk with data. You can pass it to
|
||||
* a function that requires such a buffer.
|
||||
* Returns a chunk with data.
|
||||
*
|
||||
* After calling it, set $(D_PSYMBOL written) to the length could be
|
||||
* After calling it, set $(D_KEYWORD +=) 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
|
||||
* to call it (and set $(D_KEYWORD +=) 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
|
||||
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
|
||||
{
|
||||
immutable internStart = this.start + start;
|
||||
|
||||
if (position > ring || position < start) // Buffer overflowed
|
||||
{
|
||||
return _buffer[start.. ring + 1].ptr;
|
||||
return buffer_[this.start.. ring + 1 - length + end];
|
||||
}
|
||||
else
|
||||
{
|
||||
return _buffer[start..position].ptr;
|
||||
return buffer_[this.start.. this.start + end];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator, 6);
|
||||
auto b = defaultAllocator.make!WriteBuffer(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;
|
||||
assert(b[0..$] == buf[0..6]);
|
||||
b += 2;
|
||||
|
||||
returnedBuf = b.buffer;
|
||||
assert(returnedBuf[0..b.length] == buf[2..6]);
|
||||
assert(b[0..$] == buf[2..6]);
|
||||
|
||||
b ~= buf;
|
||||
returnedBuf = b.buffer;
|
||||
assert(returnedBuf[0..b.length] == buf[2..6]);
|
||||
b.written = b.length;
|
||||
assert(b[0..$] == buf[2..6]);
|
||||
b += b.length;
|
||||
|
||||
returnedBuf = b.buffer;
|
||||
assert(returnedBuf[0..b.length] == buf[0..6]);
|
||||
b.written = b.length;
|
||||
assert(b[0..$] == buf[0..6]);
|
||||
b += b.length;
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
defaultAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* After calling it, set $(D_KEYWORD +=) 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_KEYWORD +=) 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 ubyte[] opIndex() pure nothrow @safe @nogc
|
||||
{
|
||||
return opSlice(0, length);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import tanya.memory;
|
||||
*/
|
||||
class SList(T)
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL SList).
|
||||
*
|
||||
@ -28,7 +27,7 @@ class SList(T)
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(Allocator allocator = defaultAllocator)
|
||||
this(shared Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
reset();
|
||||
@ -43,7 +42,7 @@ class SList(T)
|
||||
{
|
||||
static if (isFinalizable!T)
|
||||
{
|
||||
finalize(allocator, front);
|
||||
dispose(allocator, front);
|
||||
}
|
||||
popFront();
|
||||
}
|
||||
@ -88,7 +87,7 @@ class SList(T)
|
||||
l.front = values[1];
|
||||
assert(l.front == values[1]);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,7 +118,7 @@ class SList(T)
|
||||
assert(l.front == value);
|
||||
assert(!l.empty);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +144,7 @@ class SList(T)
|
||||
auto n = first.next.next;
|
||||
auto content = first.next.content;
|
||||
|
||||
finalize(allocator, first.next);
|
||||
dispose(allocator, first.next);
|
||||
first.next = n;
|
||||
|
||||
return content;
|
||||
@ -163,7 +162,7 @@ class SList(T)
|
||||
l.popFront();
|
||||
assert(l.front == values[0]);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +183,7 @@ class SList(T)
|
||||
auto temp = position.next.next;
|
||||
auto content = position.next.content;
|
||||
|
||||
finalize(allocator, position.next);
|
||||
dispose(allocator, position.next);
|
||||
position.next = temp;
|
||||
|
||||
return content;
|
||||
@ -204,7 +203,7 @@ class SList(T)
|
||||
assert(l.remove() == 8);
|
||||
assert(l.empty);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,7 +231,7 @@ class SList(T)
|
||||
l.reset();
|
||||
assert(l.current == 5);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,7 +240,7 @@ class SList(T)
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) body.
|
||||
*/
|
||||
int opApply(int delegate(ref size_t i, ref T) @nogc dg)
|
||||
int opApply(int delegate(ref size_t i, ref T) dg)
|
||||
{
|
||||
int result;
|
||||
size_t i;
|
||||
@ -276,11 +275,11 @@ class SList(T)
|
||||
assert(i != 2 || e == values[0]);
|
||||
}
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int opApply(int delegate(ref T) @nogc dg)
|
||||
int opApply(int delegate(ref T) dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
@ -316,7 +315,7 @@ class SList(T)
|
||||
++i;
|
||||
}
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -389,7 +388,7 @@ class SList(T)
|
||||
/// Current position in the list.
|
||||
protected Entry* position;
|
||||
|
||||
private Allocator allocator;
|
||||
private shared Allocator allocator;
|
||||
}
|
||||
|
||||
interface Stuff
|
||||
@ -401,5 +400,5 @@ unittest
|
||||
{
|
||||
auto l = make!(SList!Stuff)(defaultAllocator);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
dispose(defaultAllocator, l);
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import tanya.memory;
|
||||
*/
|
||||
class Queue(T)
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL Queue).
|
||||
*
|
||||
@ -28,7 +27,7 @@ class Queue(T)
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(Allocator allocator = defaultAllocator)
|
||||
this(shared Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
}
|
||||
@ -42,7 +41,7 @@ class Queue(T)
|
||||
{
|
||||
static if (isFinalizable!T)
|
||||
{
|
||||
finalize(allocator, e);
|
||||
dispose(allocator, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,7 +99,7 @@ class Queue(T)
|
||||
q.insertBack(values[1]);
|
||||
assert(q.front is values[0]);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
dispose(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +129,7 @@ class Queue(T)
|
||||
assert(q.front == value);
|
||||
assert(!q.empty);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
dispose(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,7 +150,7 @@ class Queue(T)
|
||||
q.insertBack(value);
|
||||
assert(!q.empty);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
dispose(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,7 +167,7 @@ class Queue(T)
|
||||
{
|
||||
auto n = first.next.next;
|
||||
|
||||
finalize(allocator, first.next);
|
||||
dispose(allocator, first.next);
|
||||
first.next = n;
|
||||
|
||||
return this;
|
||||
@ -186,7 +185,7 @@ class Queue(T)
|
||||
q.popFront();
|
||||
assert(q.front is values[1]);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
dispose(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,7 +206,7 @@ class Queue(T)
|
||||
/// The last element of the list.
|
||||
protected Entry* rear;
|
||||
|
||||
private Allocator allocator;
|
||||
private shared Allocator allocator;
|
||||
}
|
||||
|
||||
///
|
||||
@ -215,5 +214,5 @@ unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(defaultAllocator);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
dispose(defaultAllocator, q);
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ module tanya.container.vector;
|
||||
|
||||
import tanya.memory;
|
||||
|
||||
@nogc:
|
||||
|
||||
/**
|
||||
* One dimensional array. It allocates automatically if needed.
|
||||
*
|
||||
@ -24,7 +22,7 @@ import tanya.memory;
|
||||
*
|
||||
* v[1000] = value;
|
||||
*
|
||||
* finalize(defaultAllocator, v);
|
||||
* dispose(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.
|
||||
@ -34,7 +32,6 @@ import tanya.memory;
|
||||
*/
|
||||
class Vector(T)
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL Vector).
|
||||
*
|
||||
@ -43,14 +40,14 @@ class Vector(T)
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(size_t length, Allocator allocator = defaultAllocator)
|
||||
this(size_t length, shared Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
vector = makeArray!T(allocator, length);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
this(Allocator allocator = defaultAllocator)
|
||||
this(shared Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this(0, allocator);
|
||||
}
|
||||
@ -60,7 +57,7 @@ class Vector(T)
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(allocator, vector);
|
||||
dispose(allocator, vector);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,7 +94,7 @@ class Vector(T)
|
||||
v.length = 0;
|
||||
assert(v.length == 0);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,7 +116,7 @@ class Vector(T)
|
||||
void remove(size_t pos)
|
||||
{
|
||||
auto el = vector[pos];
|
||||
finalize(allocator, el);
|
||||
dispose(allocator, el);
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +151,7 @@ class Vector(T)
|
||||
v[4] = values[1];
|
||||
assert(v.length == 5);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,7 +182,7 @@ class Vector(T)
|
||||
v[0] = values[1];
|
||||
assert(v[0] is values[1]);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,7 +191,7 @@ class Vector(T)
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) body.
|
||||
*/
|
||||
int opApply(int delegate(ref T) @nogc dg)
|
||||
int opApply(int delegate(ref T) dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
@ -211,7 +208,7 @@ class Vector(T)
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int opApply(int delegate(ref size_t i, ref T) @nogc dg)
|
||||
int opApply(int delegate(ref size_t i, ref T) dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
@ -253,7 +250,7 @@ class Vector(T)
|
||||
assert(j != 2 || e is values[2]);
|
||||
}
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -292,7 +289,7 @@ class Vector(T)
|
||||
v.front = values[1];
|
||||
assert(v.front == 15);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -328,7 +325,7 @@ class Vector(T)
|
||||
v.popFront();
|
||||
assert(v.empty);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -367,7 +364,7 @@ class Vector(T)
|
||||
v.back = values[1];
|
||||
assert(v.back == 15);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -402,13 +399,13 @@ class Vector(T)
|
||||
v.popBack();
|
||||
assert(v.empty);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/// Container.
|
||||
protected T[] vector;
|
||||
|
||||
private Allocator allocator;
|
||||
private shared Allocator allocator;
|
||||
}
|
||||
|
||||
///
|
||||
@ -416,5 +413,5 @@ unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
dispose(defaultAllocator, v);
|
||||
}
|
||||
|
607
source/tanya/crypto/des.d
Normal file
607
source/tanya/crypto/des.d
Normal file
@ -0,0 +1,607 @@
|
||||
/* 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.des;
|
||||
|
||||
import tanya.container.bit;
|
||||
import tanya.crypto.symmetric;
|
||||
|
||||
/// Initial permutation table.
|
||||
private immutable ubyte[64] ipTable = [58, 50, 42, 34, 26, 18, 10, 2,
|
||||
60, 52, 44, 36, 28, 20, 12, 4,
|
||||
62, 54, 46, 38, 30, 22, 14, 6,
|
||||
64, 56, 48, 40, 32, 24, 16, 8,
|
||||
57, 49, 41, 33, 25, 17, 9, 1,
|
||||
59, 51, 43, 35, 27, 19, 11, 3,
|
||||
61, 53, 45, 37, 29, 21, 13, 5,
|
||||
63, 55, 47, 39, 31, 23, 15, 7];
|
||||
|
||||
/// Final permutation table.
|
||||
private immutable ubyte[64] fpTable = [40, 8, 48, 16, 56, 24, 64, 32,
|
||||
39, 7, 47, 15, 55, 23, 63, 31,
|
||||
38, 6, 46, 14, 54, 22, 62, 30,
|
||||
37, 5, 45, 13, 53, 21, 61, 29,
|
||||
36, 4, 44, 12, 52, 20, 60, 28,
|
||||
35, 3, 43, 11, 51, 19, 59, 27,
|
||||
34, 2, 42, 10, 50, 18, 58, 26,
|
||||
33, 1, 41, 9, 49, 17, 57, 25];
|
||||
|
||||
/// Key permutation table 1.
|
||||
private immutable ubyte[64] pc1Table = [57, 49, 41, 33, 25, 17, 9, 1,
|
||||
58, 50, 42, 34, 26, 18, 10, 2,
|
||||
59, 51, 43, 35, 27, 19, 11, 3,
|
||||
60, 52, 44, 36, 63, 55, 47, 39,
|
||||
31, 23, 15, 7, 62, 54, 46, 38,
|
||||
30, 22, 14, 6, 61, 53, 45, 37,
|
||||
29, 21, 13, 5, 28, 20, 12, 4];
|
||||
|
||||
/// Key permutation table 2.
|
||||
private immutable ubyte[48] pc2Table = [14, 17, 11, 24, 1, 5, 3, 28,
|
||||
15, 6, 21, 10, 23, 19, 12, 4,
|
||||
26, 8, 16, 7, 27, 20, 13, 2,
|
||||
41, 52, 31, 37, 47, 55, 30, 40,
|
||||
51, 45, 33, 48, 44, 49, 39, 56,
|
||||
34, 53, 46, 42, 50, 36, 29, 32];
|
||||
|
||||
/// Expansion table.
|
||||
private immutable ubyte[48] expansionTable = [32, 1, 2, 3, 4, 5, 4, 5,
|
||||
6, 7, 8, 9, 8, 9, 10, 11,
|
||||
12, 13, 12, 13, 14, 15, 16, 17,
|
||||
16, 17, 18, 19, 20, 21, 20, 21,
|
||||
22, 23, 24, 25, 24, 25, 26, 27,
|
||||
28, 29, 28, 29, 30, 31, 32, 1];
|
||||
|
||||
/// Final input block permutation.
|
||||
private immutable ubyte[32] pTable = [16, 7, 20, 21, 29, 12, 28, 17,
|
||||
1, 15, 23, 26, 5, 18, 31, 10,
|
||||
2, 8, 24, 14, 32, 27, 3, 9,
|
||||
19, 13, 30, 6, 22, 11, 4, 25];
|
||||
|
||||
/// The (in)famous S-boxes.
|
||||
private immutable ubyte[64][8] sBox = [[
|
||||
14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1,
|
||||
3, 10, 10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8,
|
||||
4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7,
|
||||
15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13,
|
||||
],[
|
||||
15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14,
|
||||
9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5,
|
||||
0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2,
|
||||
5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9,
|
||||
],[
|
||||
10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10,
|
||||
1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1,
|
||||
13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7,
|
||||
11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12,
|
||||
],[
|
||||
7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3,
|
||||
1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9,
|
||||
10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8,
|
||||
15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14,
|
||||
],[
|
||||
2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1,
|
||||
8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6,
|
||||
4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13,
|
||||
15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3,
|
||||
],[
|
||||
12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5,
|
||||
0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8,
|
||||
9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10,
|
||||
7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13,
|
||||
],[
|
||||
4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10,
|
||||
3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6,
|
||||
1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7,
|
||||
10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12,
|
||||
],[
|
||||
13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4,
|
||||
10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2,
|
||||
7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13,
|
||||
0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11,
|
||||
]];
|
||||
|
||||
/**
|
||||
* Data Encryption Standard.
|
||||
*
|
||||
* Params:
|
||||
* L = Number of keys.
|
||||
*/
|
||||
class DES(ushort L = 1) : BlockCipher
|
||||
if (L == 1)
|
||||
{
|
||||
mixin FixedBlockSize!8;
|
||||
mixin KeyLength!8;
|
||||
|
||||
private enum expansionBlockSize = 6;
|
||||
private enum pc1KeyLength = 7;
|
||||
private enum subkeyLength = 6;
|
||||
private ubyte[] key_;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* key = Key.
|
||||
*/
|
||||
@property void key(ubyte[] key) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(key.length >= minKeyLength);
|
||||
assert(key.length <= maxKeyLength);
|
||||
}
|
||||
body
|
||||
{
|
||||
key_ = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a block.
|
||||
*
|
||||
* Params:
|
||||
* plain = Plain text, input.
|
||||
* cipher = Cipher text, output.
|
||||
*/
|
||||
void encrypt(in ubyte[] plain, ubyte[] cipher)
|
||||
nothrow
|
||||
in
|
||||
{
|
||||
assert(plain.length == blockSize);
|
||||
assert(cipher.length == blockSize);
|
||||
}
|
||||
body
|
||||
{
|
||||
operateBlock!(Direction.encryption)(plain, cipher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a block.
|
||||
*
|
||||
* Params:
|
||||
* cipher = Cipher text, input.
|
||||
* plain = Plain text, output.
|
||||
*/
|
||||
void decrypt(in ubyte[] cipher, ubyte[] plain)
|
||||
nothrow
|
||||
in
|
||||
{
|
||||
assert(plain.length == blockSize);
|
||||
assert(cipher.length == blockSize);
|
||||
}
|
||||
body
|
||||
{
|
||||
operateBlock!(Direction.decryption)(cipher, plain);
|
||||
}
|
||||
|
||||
private void operateBlock(Direction D)(in ubyte[] source, ref ubyte[] target)
|
||||
{
|
||||
ubyte[blockSize_] ipBlock;
|
||||
ubyte[expansionBlockSize] expansionBlock;
|
||||
ubyte[4] substitutionBlock;
|
||||
ubyte[4] pBoxTarget;
|
||||
ubyte[pc1KeyLength] pc1Key;
|
||||
ubyte[subkeyLength] subkey;
|
||||
|
||||
// Initial permutation
|
||||
permute(source, ipBlock, ipTable, blockSize);
|
||||
|
||||
// Key schedule computation
|
||||
permute(key_, pc1Key, pc1Table, pc1KeyLength);
|
||||
|
||||
// Feistel function
|
||||
for (ubyte round; round < 16; ++round)
|
||||
{
|
||||
auto bitVector = BitVector(expansionBlock);
|
||||
/* Expansion. This permutation only looks at the first 4 bytes (32
|
||||
bits of ipBlock); 16 of these are repeated in expansion table.*/
|
||||
permute(BitVector(ipBlock[4..$]), bitVector, expansionTable, 6);
|
||||
|
||||
// Key mixing
|
||||
static if (D == Direction.encryption)
|
||||
{
|
||||
rotateLeft(pc1Key);
|
||||
if (!(round <= 1 || round == 8 || round == 15))
|
||||
{
|
||||
// Rotate twice.
|
||||
rotateLeft(pc1Key);
|
||||
}
|
||||
}
|
||||
permute(pc1Key, subkey, pc2Table, subkeyLength);
|
||||
static if (D == Direction.decryption)
|
||||
{
|
||||
rotateRight(pc1Key);
|
||||
if (!(round >= 14 || round == 7 || round == 0))
|
||||
{
|
||||
// Rotate twice.
|
||||
rotateRight(pc1Key);
|
||||
}
|
||||
}
|
||||
|
||||
bitVector ^= subkey;
|
||||
|
||||
// Substitution; copy from updated expansion block to ciphertext block
|
||||
substitutionBlock[0] = cast(ubyte) (sBox[0][(expansionBlock[0] & 0xfc ) >> 2] << 4);
|
||||
substitutionBlock[0] |= sBox[1][(expansionBlock[0] & 0x03) << 4 | (expansionBlock[1] & 0xf0) >> 4];
|
||||
substitutionBlock[1] = cast(ubyte) (sBox[2][(expansionBlock[1] & 0x0f) << 2 | (expansionBlock[2] & 0xc0) >> 6] << 4);
|
||||
substitutionBlock[1] |= sBox[3][(expansionBlock[2] & 0x3f)];
|
||||
substitutionBlock[2] = cast(ubyte) (sBox[4][(expansionBlock[3] & 0xfc) >> 2 ] << 4);
|
||||
substitutionBlock[2] |= sBox[5][(expansionBlock[3] & 0x03) << 4 | (expansionBlock[4] & 0xf0) >> 4];
|
||||
substitutionBlock[3] = cast(ubyte) (sBox[6][(expansionBlock[4] & 0x0F) << 2 | (expansionBlock[5] & 0xc0) >> 6] << 4);
|
||||
substitutionBlock[3] |= sBox[7][(expansionBlock[5] & 0x3f)];
|
||||
|
||||
// Permutation
|
||||
bitVector = BitVector(substitutionBlock);
|
||||
permute(bitVector, pBoxTarget, pTable, blockSize / 2);
|
||||
|
||||
// Swap the halves.
|
||||
substitutionBlock = ipBlock[0..4];
|
||||
ipBlock[0..4] = ipBlock[4..$];
|
||||
|
||||
bitVector ^= pBoxTarget;
|
||||
ipBlock[4..$] = substitutionBlock;
|
||||
}
|
||||
|
||||
substitutionBlock = ipBlock[0..4];
|
||||
ipBlock[0..4] = ipBlock[4..$];
|
||||
ipBlock[4..$] = substitutionBlock;
|
||||
|
||||
// Final permutaion (undo initial permuation).
|
||||
permute(ipBlock, target, fpTable, blockSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the left rotation operation on the key.
|
||||
*
|
||||
* Params:
|
||||
* key = The key to rotate.
|
||||
*/
|
||||
private void rotateLeft(ref ubyte[7] key) const pure nothrow @safe @nogc
|
||||
{
|
||||
immutable carryLeft = (key[0] & 0x80) >> 3;
|
||||
|
||||
key[0] = cast(ubyte) ((key[0] << 1) | ((key[1] & 0x80) >> 7));
|
||||
key[1] = cast(ubyte) ((key[1] << 1) | ((key[2] & 0x80) >> 7));
|
||||
key[2] = cast(ubyte) ((key[2] << 1) | ((key[3] & 0x80) >> 7));
|
||||
|
||||
immutable carryRight = (key[3] & 0x08) >> 3;
|
||||
key[3] = cast(ubyte) ((((key[3] << 1) | ((key[4] & 0x80) >> 7)) & ~0x10) | carryLeft);
|
||||
|
||||
key[4] = cast(ubyte) ((key[4] << 1) | ((key[5] & 0x80) >> 7));
|
||||
key[5] = cast(ubyte) ((key[5] << 1) | ((key[6] & 0x80) >> 7));
|
||||
key[6] = cast(ubyte) ((key[6] << 1) | carryRight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the right rotation operation on the key.
|
||||
*
|
||||
* Params:
|
||||
* key = The key to rotate.
|
||||
*/
|
||||
private void rotateRight(ref ubyte[7] key) const pure nothrow @safe @nogc
|
||||
{
|
||||
immutable carryRight = (key[6] & 0x01) << 3;
|
||||
|
||||
key[6] = cast(ubyte) ((key[6] >> 1) | ((key[5] & 0x01) << 7));
|
||||
key[5] = cast(ubyte) ((key[5] >> 1) | ((key[4] & 0x01) << 7));
|
||||
key[4] = cast(ubyte) ((key[4] >> 1) | ((key[3] & 0x01) << 7));
|
||||
|
||||
immutable carryLeft = (key[3] & 0x10) << 3;
|
||||
key[3] = cast(ubyte) ((((key[3] >> 1) | ((key[2] & 0x01) << 7)) & ~0x08) | carryRight);
|
||||
|
||||
key[2] = cast(ubyte) ((key[2] >> 1) | ((key[1] & 0x01) << 7));
|
||||
key[1] = cast(ubyte) ((key[1] >> 1) | ((key[0] & 0x01) << 7));
|
||||
key[0] = cast(ubyte) ((key[0] >> 1) | carryLeft);
|
||||
}
|
||||
|
||||
private void permute(in ubyte[] source, ubyte[] target, immutable(ubyte[]) permuteTable, size_t length)
|
||||
const pure nothrow @safe @nogc
|
||||
{
|
||||
const sourceVector = const BitVector(source);
|
||||
auto targetVector = BitVector(target);
|
||||
|
||||
permute(sourceVector, targetVector, permuteTable, length);
|
||||
}
|
||||
|
||||
private void permute(in BitVector source, ubyte[] target, immutable(ubyte[]) permuteTable, size_t length)
|
||||
const pure nothrow @safe @nogc
|
||||
{
|
||||
auto targetVector = BitVector(target);
|
||||
|
||||
permute(source, targetVector, permuteTable, length);
|
||||
}
|
||||
|
||||
private void permute(in BitVector source, ref BitVector target, immutable(ubyte[]) permuteTable, size_t length)
|
||||
const pure nothrow @safe @nogc
|
||||
{
|
||||
for (uint i; i < length * 8; ++i)
|
||||
{
|
||||
target[i] = source[permuteTable[i] - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
import std.typecons;
|
||||
|
||||
/* Test vectors for DES. Source:
|
||||
"Validating the Correctness of Hardware
|
||||
Implementations of the NBS Data Encryption Standard"
|
||||
NBS Special Publication 500-20, 1980. Appendix B */
|
||||
|
||||
// Initial and reverse Permutation and Expansion tests. Encrypt.
|
||||
ubyte[8][64] desTestVectors1 = [
|
||||
[0x95, 0xf8, 0xa5, 0xe5, 0xdd, 0x31, 0xd9, 0x00],
|
||||
[0xdd, 0x7f, 0x12, 0x1c, 0xa5, 0x01, 0x56, 0x19],
|
||||
[0x2e, 0x86, 0x53, 0x10, 0x4f, 0x38, 0x34, 0xea],
|
||||
[0x4b, 0xd3, 0x88, 0xff, 0x6c, 0xd8, 0x1d, 0x4f],
|
||||
[0x20, 0xb9, 0xe7, 0x67, 0xb2, 0xfb, 0x14, 0x56],
|
||||
[0x55, 0x57, 0x93, 0x80, 0xd7, 0x71, 0x38, 0xef],
|
||||
[0x6c, 0xc5, 0xde, 0xfa, 0xaf, 0x04, 0x51, 0x2f],
|
||||
[0x0d, 0x9f, 0x27, 0x9b, 0xa5, 0xd8, 0x72, 0x60],
|
||||
[0xd9, 0x03, 0x1b, 0x02, 0x71, 0xbd, 0x5a, 0x0a],
|
||||
[0x42, 0x42, 0x50, 0xb3, 0x7c, 0x3d, 0xd9, 0x51],
|
||||
[0xb8, 0x06, 0x1b, 0x7e, 0xcd, 0x9a, 0x21, 0xe5],
|
||||
[0xf1, 0x5d, 0x0f, 0x28, 0x6b, 0x65, 0xbd, 0x28],
|
||||
[0xad, 0xd0, 0xcc, 0x8d, 0x6e, 0x5d, 0xeb, 0xa1],
|
||||
[0xe6, 0xd5, 0xf8, 0x27, 0x52, 0xad, 0x63, 0xd1],
|
||||
[0xec, 0xbf, 0xe3, 0xbd, 0x3f, 0x59, 0x1a, 0x5e],
|
||||
[0xf3, 0x56, 0x83, 0x43, 0x79, 0xd1, 0x65, 0xcd],
|
||||
[0x2b, 0x9f, 0x98, 0x2f, 0x20, 0x03, 0x7f, 0xa9],
|
||||
[0x88, 0x9d, 0xe0, 0x68, 0xa1, 0x6f, 0x0b, 0xe6],
|
||||
[0xe1, 0x9e, 0x27, 0x5d, 0x84, 0x6a, 0x12, 0x98],
|
||||
[0x32, 0x9a, 0x8e, 0xd5, 0x23, 0xd7, 0x1a, 0xec],
|
||||
[0xe7, 0xfc, 0xe2, 0x25, 0x57, 0xd2, 0x3c, 0x97],
|
||||
[0x12, 0xa9, 0xf5, 0x81, 0x7f, 0xf2, 0xd6, 0x5d],
|
||||
[0xa4, 0x84, 0xc3, 0xad, 0x38, 0xdc, 0x9c, 0x19],
|
||||
[0xfb, 0xe0, 0x0a, 0x8a, 0x1e, 0xf8, 0xad, 0x72],
|
||||
[0x75, 0x0d, 0x07, 0x94, 0x07, 0x52, 0x13, 0x63],
|
||||
[0x64, 0xfe, 0xed, 0x9c, 0x72, 0x4c, 0x2f, 0xaf],
|
||||
[0xf0, 0x2b, 0x26, 0x3b, 0x32, 0x8e, 0x2b, 0x60],
|
||||
[0x9d, 0x64, 0x55, 0x5a, 0x9a, 0x10, 0xb8, 0x52],
|
||||
[0xd1, 0x06, 0xff, 0x0b, 0xed, 0x52, 0x55, 0xd7],
|
||||
[0xe1, 0x65, 0x2c, 0x6b, 0x13, 0x8c, 0x64, 0xa5],
|
||||
[0xe4, 0x28, 0x58, 0x11, 0x86, 0xec, 0x8f, 0x46],
|
||||
[0xae, 0xb5, 0xf5, 0xed, 0xe2, 0x2d, 0x1a, 0x36],
|
||||
[0xe9, 0x43, 0xd7, 0x56, 0x8a, 0xec, 0x0c, 0x5c],
|
||||
[0xdf, 0x98, 0xc8, 0x27, 0x6f, 0x54, 0xb0, 0x4b],
|
||||
[0xb1, 0x60, 0xe4, 0x68, 0x0f, 0x6c, 0x69, 0x6f],
|
||||
[0xfa, 0x07, 0x52, 0xb0, 0x7d, 0x9c, 0x4a, 0xb8],
|
||||
[0xca, 0x3a, 0x2b, 0x03, 0x6d, 0xbc, 0x85, 0x02],
|
||||
[0x5e, 0x09, 0x05, 0x51, 0x7b, 0xb5, 0x9b, 0xcf],
|
||||
[0x81, 0x4e, 0xeb, 0x3b, 0x91, 0xd9, 0x07, 0x26],
|
||||
[0x4d, 0x49, 0xdb, 0x15, 0x32, 0x91, 0x9c, 0x9f],
|
||||
[0x25, 0xeb, 0x5f, 0xc3, 0xf8, 0xcf, 0x06, 0x21],
|
||||
[0xab, 0x6a, 0x20, 0xc0, 0x62, 0x0d, 0x1c, 0x6f],
|
||||
[0x79, 0xe9, 0x0d, 0xbc, 0x98, 0xf9, 0x2c, 0xca],
|
||||
[0x86, 0x6e, 0xce, 0xdd, 0x80, 0x72, 0xbb, 0x0e],
|
||||
[0x8b, 0x54, 0x53, 0x6f, 0x2f, 0x3e, 0x64, 0xa8],
|
||||
[0xea, 0x51, 0xd3, 0x97, 0x55, 0x95, 0xb8, 0x6b],
|
||||
[0xca, 0xff, 0xc6, 0xac, 0x45, 0x42, 0xde, 0x31],
|
||||
[0x8d, 0xd4, 0x5a, 0x2d, 0xdf, 0x90, 0x79, 0x6c],
|
||||
[0x10, 0x29, 0xd5, 0x5e, 0x88, 0x0e, 0xc2, 0xd0],
|
||||
[0x5d, 0x86, 0xcb, 0x23, 0x63, 0x9d, 0xbe, 0xa9],
|
||||
[0x1d, 0x1c, 0xa8, 0x53, 0xae, 0x7c, 0x0c, 0x5f],
|
||||
[0xce, 0x33, 0x23, 0x29, 0x24, 0x8f, 0x32, 0x28],
|
||||
[0x84, 0x05, 0xd1, 0xab, 0xe2, 0x4f, 0xb9, 0x42],
|
||||
[0xe6, 0x43, 0xd7, 0x80, 0x90, 0xca, 0x42, 0x07],
|
||||
[0x48, 0x22, 0x1b, 0x99, 0x37, 0x74, 0x8a, 0x23],
|
||||
[0xdd, 0x7c, 0x0b, 0xbd, 0x61, 0xfa, 0xfd, 0x54],
|
||||
[0x2f, 0xbc, 0x29, 0x1a, 0x57, 0x0d, 0xb5, 0xc4],
|
||||
[0xe0, 0x7c, 0x30, 0xd7, 0xe4, 0xe2, 0x6e, 0x12],
|
||||
[0x09, 0x53, 0xe2, 0x25, 0x8e, 0x8e, 0x90, 0xa1],
|
||||
[0x5b, 0x71, 0x1b, 0xc4, 0xce, 0xeb, 0xf2, 0xee],
|
||||
[0xcc, 0x08, 0x3f, 0x1e, 0x6d, 0x9e, 0x85, 0xf6],
|
||||
[0xd2, 0xfd, 0x88, 0x67, 0xd5, 0x0d, 0x2d, 0xfe],
|
||||
[0x06, 0xe7, 0xea, 0x22, 0xce, 0x92, 0x70, 0x8f],
|
||||
[0x16, 0x6b, 0x40, 0xb4, 0x4a, 0xba, 0x4b, 0xd6],
|
||||
];
|
||||
|
||||
// Key Permutation test. Encrypt.
|
||||
// Test of right-shifts. Decrypt.
|
||||
ubyte[8][56] desTestVectors2 = [
|
||||
[0x95, 0xa8, 0xd7, 0x28, 0x13, 0xda, 0xa9, 0x4d],
|
||||
[0x0e, 0xec, 0x14, 0x87, 0xdd, 0x8c, 0x26, 0xd5],
|
||||
[0x7a, 0xd1, 0x6f, 0xfb, 0x79, 0xc4, 0x59, 0x26],
|
||||
[0xd3, 0x74, 0x62, 0x94, 0xca, 0x6a, 0x6c, 0xf3],
|
||||
[0x80, 0x9f, 0x5f, 0x87, 0x3c, 0x1f, 0xd7, 0x61],
|
||||
[0xc0, 0x2f, 0xaf, 0xfe, 0xc9, 0x89, 0xd1, 0xfc],
|
||||
[0x46, 0x15, 0xaa, 0x1d, 0x33, 0xe7, 0x2f, 0x10],
|
||||
[0x20, 0x55, 0x12, 0x33, 0x50, 0xc0, 0x08, 0x58],
|
||||
[0xdf, 0x3b, 0x99, 0xd6, 0x57, 0x73, 0x97, 0xc8],
|
||||
[0x31, 0xfe, 0x17, 0x36, 0x9b, 0x52, 0x88, 0xc9],
|
||||
[0xdf, 0xdd, 0x3c, 0xc6, 0x4d, 0xae, 0x16, 0x42],
|
||||
[0x17, 0x8c, 0x83, 0xce, 0x2b, 0x39, 0x9d, 0x94],
|
||||
[0x50, 0xf6, 0x36, 0x32, 0x4a, 0x9b, 0x7f, 0x80],
|
||||
[0xa8, 0x46, 0x8e, 0xe3, 0xbc, 0x18, 0xf0, 0x6d],
|
||||
[0xa2, 0xdc, 0x9e, 0x92, 0xfd, 0x3c, 0xde, 0x92],
|
||||
[0xca, 0xc0, 0x9f, 0x79, 0x7d, 0x03, 0x12, 0x87],
|
||||
[0x90, 0xba, 0x68, 0x0b, 0x22, 0xae, 0xb5, 0x25],
|
||||
[0xce, 0x7a, 0x24, 0xf3, 0x50, 0xe2, 0x80, 0xb6],
|
||||
[0x88, 0x2b, 0xff, 0x0a, 0xa0, 0x1a, 0x0b, 0x87],
|
||||
[0x25, 0x61, 0x02, 0x88, 0x92, 0x45, 0x11, 0xc2],
|
||||
[0xc7, 0x15, 0x16, 0xc2, 0x9c, 0x75, 0xd1, 0x70],
|
||||
[0x51, 0x99, 0xc2, 0x9a, 0x52, 0xc9, 0xf0, 0x59],
|
||||
[0xc2, 0x2f, 0x0a, 0x29, 0x4a, 0x71, 0xf2, 0x9f],
|
||||
[0xee, 0x37, 0x14, 0x83, 0x71, 0x4c, 0x02, 0xea],
|
||||
[0xa8, 0x1f, 0xbd, 0x44, 0x8f, 0x9e, 0x52, 0x2f],
|
||||
[0x4f, 0x64, 0x4c, 0x92, 0xe1, 0x92, 0xdf, 0xed],
|
||||
[0x1a, 0xfa, 0x9a, 0x66, 0xa6, 0xdf, 0x92, 0xae],
|
||||
[0xb3, 0xc1, 0xcc, 0x71, 0x5c, 0xb8, 0x79, 0xd8],
|
||||
[0x19, 0xd0, 0x32, 0xe6, 0x4a, 0xb0, 0xbd, 0x8b],
|
||||
[0x3c, 0xfa, 0xa7, 0xa7, 0xdc, 0x87, 0x20, 0xdc],
|
||||
[0xb7, 0x26, 0x5f, 0x7f, 0x44, 0x7a, 0xc6, 0xf3],
|
||||
[0x9d, 0xb7, 0x3b, 0x3c, 0x0d, 0x16, 0x3f, 0x54],
|
||||
[0x81, 0x81, 0xb6, 0x5b, 0xab, 0xf4, 0xa9, 0x75],
|
||||
[0x93, 0xc9, 0xb6, 0x40, 0x42, 0xea, 0xa2, 0x40],
|
||||
[0x55, 0x70, 0x53, 0x08, 0x29, 0x70, 0x55, 0x92],
|
||||
[0x86, 0x38, 0x80, 0x9e, 0x87, 0x87, 0x87, 0xa0],
|
||||
[0x41, 0xb9, 0xa7, 0x9a, 0xf7, 0x9a, 0xc2, 0x08],
|
||||
[0x7a, 0x9b, 0xe4, 0x2f, 0x20, 0x09, 0xa8, 0x92],
|
||||
[0x29, 0x03, 0x8d, 0x56, 0xba, 0x6d, 0x27, 0x45],
|
||||
[0x54, 0x95, 0xc6, 0xab, 0xf1, 0xe5, 0xdf, 0x51],
|
||||
[0xae, 0x13, 0xdb, 0xd5, 0x61, 0x48, 0x89, 0x33],
|
||||
[0x02, 0x4d, 0x1f, 0xfa, 0x89, 0x04, 0xe3, 0x89],
|
||||
[0xd1, 0x39, 0x97, 0x12, 0xf9, 0x9b, 0xf0, 0x2e],
|
||||
[0x14, 0xc1, 0xd7, 0xc1, 0xcf, 0xfe, 0xc7, 0x9e],
|
||||
[0x1d, 0xe5, 0x27, 0x9d, 0xae, 0x3b, 0xed, 0x6f],
|
||||
[0xe9, 0x41, 0xa3, 0x3f, 0x85, 0x50, 0x13, 0x03],
|
||||
[0xda, 0x99, 0xdb, 0xbc, 0x9a, 0x03, 0xf3, 0x79],
|
||||
[0xb7, 0xfc, 0x92, 0xf9, 0x1d, 0x8e, 0x92, 0xe9],
|
||||
[0xae, 0x8e, 0x5c, 0xaa, 0x3c, 0xa0, 0x4e, 0x85],
|
||||
[0x9c, 0xc6, 0x2d, 0xf4, 0x3b, 0x6e, 0xed, 0x74],
|
||||
[0xd8, 0x63, 0xdb, 0xb5, 0xc5, 0x9a, 0x91, 0xa0],
|
||||
[0xa1, 0xab, 0x21, 0x90, 0x54, 0x5b, 0x91, 0xd7],
|
||||
[0x08, 0x75, 0x04, 0x1e, 0x64, 0xc5, 0x70, 0xf7],
|
||||
[0x5a, 0x59, 0x45, 0x28, 0xbe, 0xbe, 0xf1, 0xcc],
|
||||
[0xfc, 0xdb, 0x32, 0x91, 0xde, 0x21, 0xf0, 0xc0],
|
||||
[0x86, 0x9e, 0xfd, 0x7f, 0x9f, 0x26, 0x5a, 0x09],
|
||||
];
|
||||
|
||||
// Data permutation test. Encrypt.
|
||||
ubyte[8][2][32] desTestVectors3 = [
|
||||
[[0x10, 0x46, 0x91, 0x34, 0x89, 0x98, 0x01, 0x31], [0x88, 0xd5, 0x5e, 0x54, 0xf5, 0x4c, 0x97, 0xb4]],
|
||||
[[0x10, 0x07, 0x10, 0x34, 0x89, 0x98, 0x80, 0x20], [0x0c, 0x0c, 0xc0, 0x0c, 0x83, 0xea, 0x48, 0xfd]],
|
||||
[[0x10, 0x07, 0x10, 0x34, 0xc8, 0x98, 0x01, 0x20], [0x83, 0xbc, 0x8e, 0xf3, 0xa6, 0x57, 0x01, 0x83]],
|
||||
[[0x10, 0x46, 0x10, 0x34, 0x89, 0x98, 0x80, 0x20], [0xdf, 0x72, 0x5d, 0xca, 0xd9, 0x4e, 0xa2, 0xe9]],
|
||||
[[0x10, 0x86, 0x91, 0x15, 0x19, 0x19, 0x01, 0x01], [0xe6, 0x52, 0xb5, 0x3b, 0x55, 0x0b, 0xe8, 0xb0]],
|
||||
[[0x10, 0x86, 0x91, 0x15, 0x19, 0x58, 0x01, 0x01], [0xaf, 0x52, 0x71, 0x20, 0xc4, 0x85, 0xcb, 0xb0]],
|
||||
[[0x51, 0x07, 0xb0, 0x15, 0x19, 0x58, 0x01, 0x01], [0x0f, 0x04, 0xce, 0x39, 0x3d, 0xb9, 0x26, 0xd5]],
|
||||
[[0x10, 0x07, 0xb0, 0x15, 0x19, 0x19, 0x01, 0x01], [0xc9, 0xf0, 0x0f, 0xfc, 0x74, 0x07, 0x90, 0x67]],
|
||||
[[0x31, 0x07, 0x91, 0x54, 0x98, 0x08, 0x01, 0x01], [0x7c, 0xfd, 0x82, 0xa5, 0x93, 0x25, 0x2b, 0x4e]],
|
||||
[[0x31, 0x07, 0x91, 0x94, 0x98, 0x08, 0x01, 0x01], [0xcb, 0x49, 0xa2, 0xf9, 0xe9, 0x13, 0x63, 0xe3]],
|
||||
[[0x10, 0x07, 0x91, 0x15, 0xb9, 0x08, 0x01, 0x40], [0x00, 0xb5, 0x88, 0xbe, 0x70, 0xd2, 0x3f, 0x56]],
|
||||
[[0x31, 0x07, 0x91, 0x15, 0x98, 0x08, 0x01, 0x40], [0x40, 0x6a, 0x9a, 0x6a, 0xb4, 0x33, 0x99, 0xae]],
|
||||
[[0x10, 0x07, 0xd0, 0x15, 0x89, 0x98, 0x01, 0x01], [0x6c, 0xb7, 0x73, 0x61, 0x1d, 0xca, 0x9a, 0xda]],
|
||||
[[0x91, 0x07, 0x91, 0x15, 0x89, 0x98, 0x01, 0x01], [0x67, 0xfd, 0x21, 0xc1, 0x7d, 0xbb, 0x5d, 0x70]],
|
||||
[[0x91, 0x07, 0xd0, 0x15, 0x89, 0x19, 0x01, 0x01], [0x95, 0x92, 0xcb, 0x41, 0x10, 0x43, 0x07, 0x87]],
|
||||
[[0x10, 0x07, 0xd0, 0x15, 0x98, 0x98, 0x01, 0x20], [0xa6, 0xb7, 0xff, 0x68, 0xa3, 0x18, 0xdd, 0xd3]],
|
||||
[[0x10, 0x07, 0x94, 0x04, 0x98, 0x19, 0x01, 0x01], [0x4d, 0x10, 0x21, 0x96, 0xc9, 0x14, 0xca, 0x16]],
|
||||
[[0x01, 0x07, 0x91, 0x04, 0x91, 0x19, 0x04, 0x01], [0x2d, 0xfa, 0x9f, 0x45, 0x73, 0x59, 0x49, 0x65]],
|
||||
[[0x01, 0x07, 0x91, 0x04, 0x91, 0x19, 0x01, 0x01], [0xb4, 0x66, 0x04, 0x81, 0x6c, 0x0e, 0x07, 0x74]],
|
||||
[[0x01, 0x07, 0x94, 0x04, 0x91, 0x19, 0x04, 0x01], [0x6e, 0x7e, 0x62, 0x21, 0xa4, 0xf3, 0x4e, 0x87]],
|
||||
[[0x19, 0x07, 0x92, 0x10, 0x98, 0x1a, 0x01, 0x01], [0xaa, 0x85, 0xe7, 0x46, 0x43, 0x23, 0x31, 0x99]],
|
||||
[[0x10, 0x07, 0x91, 0x19, 0x98, 0x19, 0x08, 0x01], [0x2e, 0x5a, 0x19, 0xdb, 0x4d, 0x19, 0x62, 0xd6]],
|
||||
[[0x10, 0x07, 0x91, 0x19, 0x98, 0x1a, 0x08, 0x01], [0x23, 0xa8, 0x66, 0xa8, 0x09, 0xd3, 0x08, 0x94]],
|
||||
[[0x10, 0x07, 0x92, 0x10, 0x98, 0x19, 0x01, 0x01], [0xd8, 0x12, 0xd9, 0x61, 0xf0, 0x17, 0xd3, 0x20]],
|
||||
[[0x10, 0x07, 0x91, 0x15, 0x98, 0x19, 0x01, 0x0b], [0x05, 0x56, 0x05, 0x81, 0x6e, 0x58, 0x60, 0x8f]],
|
||||
[[0x10, 0x04, 0x80, 0x15, 0x98, 0x19, 0x01, 0x01], [0xab, 0xd8, 0x8e, 0x8b, 0x1b, 0x77, 0x16, 0xf1]],
|
||||
[[0x10, 0x04, 0x80, 0x15, 0x98, 0x19, 0x01, 0x02], [0x53, 0x7a, 0xc9, 0x5b, 0xe6, 0x9d, 0xa1, 0xe1]],
|
||||
[[0x10, 0x04, 0x80, 0x15, 0x98, 0x19, 0x01, 0x08], [0xae, 0xd0, 0xf6, 0xae, 0x3c, 0x25, 0xcd, 0xd8]],
|
||||
[[0x10, 0x02, 0x91, 0x14, 0x98, 0x10, 0x01, 0x04], [0xb3, 0xe3, 0x5a, 0x5e, 0xe5, 0x3e, 0x7b, 0x8d]],
|
||||
[[0x10, 0x02, 0x91, 0x15, 0x98, 0x19, 0x01, 0x04], [0x61, 0xc7, 0x9c, 0x71, 0x92, 0x1a, 0x2e, 0xf8]],
|
||||
[[0x10, 0x02, 0x91, 0x15, 0x98, 0x10, 0x02, 0x01], [0xe2, 0xf5, 0x72, 0x8f, 0x09, 0x95, 0x01, 0x3c]],
|
||||
[[0x10, 0x02, 0x91, 0x16, 0x98, 0x10, 0x01, 0x01], [0x1a, 0xea, 0xc3, 0x9a, 0x61, 0xf0, 0xa4, 0x64]],
|
||||
];
|
||||
|
||||
// S-Box test. Encrypt.
|
||||
ubyte[8][3][19] desTestVectors4 = [
|
||||
[[0x7c, 0xa1, 0x10, 0x45, 0x4a, 0x1a, 0x6e, 0x57], [0x01, 0xa1, 0xd6, 0xd0, 0x39, 0x77, 0x67, 0x42],
|
||||
[0x69, 0x0f, 0x5b, 0x0d, 0x9a, 0x26, 0x93, 0x9b]],
|
||||
[[0x01, 0x31, 0xd9, 0x61, 0x9d, 0xc1, 0x37, 0x6e], [0x5c, 0xd5, 0x4c, 0xa8, 0x3d, 0xef, 0x57, 0xda],
|
||||
[0x7a, 0x38, 0x9d, 0x10, 0x35, 0x4b, 0xd2, 0x71]],
|
||||
[[0x07, 0xa1, 0x13, 0x3e, 0x4a, 0x0b, 0x26, 0x86], [0x02, 0x48, 0xd4, 0x38, 0x06, 0xf6, 0x71, 0x72],
|
||||
[0x86, 0x8e, 0xbb, 0x51, 0xca, 0xb4, 0x59, 0x9a]],
|
||||
[[0x38, 0x49, 0x67, 0x4c, 0x26, 0x02, 0x31, 0x9e], [0x51, 0x45, 0x4b, 0x58, 0x2d, 0xdf, 0x44, 0x0a],
|
||||
[0x71, 0x78, 0x87, 0x6e, 0x01, 0xf1, 0x9b, 0x2a]],
|
||||
[[0x04, 0xb9, 0x15, 0xba, 0x43, 0xfe, 0xb5, 0xb6], [0x42, 0xfd, 0x44, 0x30, 0x59, 0x57, 0x7f, 0xa2],
|
||||
[0xaf, 0x37, 0xfb, 0x42, 0x1f, 0x8c, 0x40, 0x95]],
|
||||
[[0x01, 0x13, 0xb9, 0x70, 0xfd, 0x34, 0xf2, 0xce], [0x05, 0x9b, 0x5e, 0x08, 0x51, 0xcf, 0x14, 0x3a],
|
||||
[0x86, 0xa5, 0x60, 0xf1, 0x0e, 0xc6, 0xd8, 0x5b]],
|
||||
[[0x01, 0x70, 0xf1, 0x75, 0x46, 0x8f, 0xb5, 0xe6], [0x07, 0x56, 0xd8, 0xe0, 0x77, 0x47, 0x61, 0xd2],
|
||||
[0x0c, 0xd3, 0xda, 0x02, 0x00, 0x21, 0xdc, 0x09]],
|
||||
[[0x43, 0x29, 0x7f, 0xad, 0x38, 0xe3, 0x73, 0xfe], [0x76, 0x25, 0x14, 0xb8, 0x29, 0xbf, 0x48, 0x6a],
|
||||
[0xea, 0x67, 0x6b, 0x2c, 0xb7, 0xdb, 0x2b, 0x7a]],
|
||||
[[0x07, 0xa7, 0x13, 0x70, 0x45, 0xda, 0x2a, 0x16], [0x3b, 0xdd, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02],
|
||||
[0xdf, 0xd6, 0x4a, 0x81, 0x5c, 0xaf, 0x1a, 0x0f]],
|
||||
[[0x04, 0x68, 0x91, 0x04, 0xc2, 0xfd, 0x3b, 0x2f], [0x26, 0x95, 0x5f, 0x68, 0x35, 0xaf, 0x60, 0x9a],
|
||||
[0x5c, 0x51, 0x3c, 0x9c, 0x48, 0x86, 0xc0, 0x88]],
|
||||
[[0x37, 0xd0, 0x6b, 0xb5, 0x16, 0xcb, 0x75, 0x46], [0x16, 0x4d, 0x5e, 0x40, 0x4f, 0x27, 0x52, 0x32],
|
||||
[0x0a, 0x2a, 0xee, 0xae, 0x3f, 0xf4, 0xab, 0x77]],
|
||||
[[0x1f, 0x08, 0x26, 0x0d, 0x1a, 0xc2, 0x46, 0x5e], [0x6b, 0x05, 0x6e, 0x18, 0x75, 0x9f, 0x5c, 0xca],
|
||||
[0xef, 0x1b, 0xf0, 0x3e, 0x5d, 0xfa, 0x57, 0x5a]],
|
||||
[[0x58, 0x40, 0x23, 0x64, 0x1a, 0xba, 0x61, 0x76], [0x00, 0x4b, 0xd6, 0xef, 0x09, 0x17, 0x60, 0x62],
|
||||
[0x88, 0xbf, 0x0d, 0xb6, 0xd7, 0x0d, 0xee, 0x56]],
|
||||
[[0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xb0, 0x07], [0x48, 0x0d, 0x39, 0x00, 0x6e, 0xe7, 0x62, 0xf2],
|
||||
[0xa1, 0xf9, 0x91, 0x55, 0x41, 0x02, 0x0b, 0x56]],
|
||||
[[0x49, 0x79, 0x3e, 0xbc, 0x79, 0xb3, 0x25, 0x8f], [0x43, 0x75, 0x40, 0xc8, 0x69, 0x8f, 0x3c, 0xfa],
|
||||
[0x6f, 0xbf, 0x1c, 0xaf, 0xcf, 0xfd, 0x05, 0x56]],
|
||||
[[0x4f, 0xb0, 0x5e, 0x15, 0x15, 0xab, 0x73, 0xa7], [0x07, 0x2d, 0x43, 0xa0, 0x77, 0x07, 0x52, 0x92],
|
||||
[0x2f, 0x22, 0xe4, 0x9b, 0xab, 0x7c, 0xa1, 0xac]],
|
||||
[[0x49, 0xe9, 0x5d, 0x6d, 0x4c, 0xa2, 0x29, 0xbf], [0x02, 0xfe, 0x55, 0x77, 0x81, 0x17, 0xf1, 0x2a],
|
||||
[0x5a, 0x6b, 0x61, 0x2c, 0xc2, 0x6c, 0xce, 0x4a]],
|
||||
[[0x01, 0x83, 0x10, 0xdc, 0x40, 0x9b, 0x26, 0xd6], [0x1d, 0x9d, 0x5c, 0x50, 0x18, 0xf7, 0x28, 0xc2],
|
||||
[0x5f, 0x4c, 0x03, 0x8e, 0xd1, 0x2b, 0x2e, 0x41]],
|
||||
[[0x1c, 0x58, 0x7f, 0x1c, 0x13, 0x92, 0x4f, 0xef], [0x30, 0x55, 0x32, 0x28, 0x6d, 0x6f, 0x29, 0x5a],
|
||||
[0x63, 0xfa, 0xc0, 0xd0, 0x34, 0xd9, 0xf7, 0x93]],
|
||||
];
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto des = scoped!(DES!1);
|
||||
ubyte[8] key = [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01];
|
||||
ubyte[8] plain = [0x80, 0, 0, 0, 0, 0, 0, 0];
|
||||
ubyte[8] cipher;
|
||||
|
||||
des.key = key;
|
||||
foreach (ubyte i; 0..64)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
plain[i / 8] = i % 8 ? plain[i / 8] >> 1 : 0x80;
|
||||
if (i % 8 == 0)
|
||||
{
|
||||
plain[i / 8 - 1] = 0;
|
||||
}
|
||||
}
|
||||
// Initial Permutation and Expansion test.
|
||||
des.encrypt(plain, cipher);
|
||||
assert(cipher == desTestVectors1[i]);
|
||||
|
||||
// Inverse Permutation and Expansion test.
|
||||
des.encrypt(cipher, cipher);
|
||||
assert(cipher == plain);
|
||||
}
|
||||
|
||||
plain[0..$] = 0;
|
||||
foreach (ubyte i; 0..56)
|
||||
{
|
||||
key[i / 7] = i % 7 ? key[i / 7] >> 1 : 0x80;
|
||||
if (i % 7 == 0 && i != 0)
|
||||
{
|
||||
key[i / 7 - 1] = 0x01;
|
||||
}
|
||||
des.key = key;
|
||||
|
||||
// Initial Permutation and Expansion test.
|
||||
des.encrypt(plain, cipher);
|
||||
assert(cipher == desTestVectors2[i]);
|
||||
|
||||
// Test of right-shifts in Decryption.
|
||||
des.decrypt(desTestVectors2[i], cipher);
|
||||
assert(cipher == plain);
|
||||
}
|
||||
|
||||
// Data permutation test.
|
||||
plain[0..$] = 0;
|
||||
foreach (i; desTestVectors3)
|
||||
{
|
||||
des.key = i[0];
|
||||
des.encrypt(plain, cipher);
|
||||
assert(cipher == i[1]);
|
||||
}
|
||||
|
||||
// S-Box test.
|
||||
foreach (i; desTestVectors4)
|
||||
{
|
||||
des.key = i[0];
|
||||
des.encrypt(i[1], cipher);
|
||||
assert(cipher == i[2]);
|
||||
}
|
||||
}
|
279
source/tanya/crypto/mode.d
Normal file
279
source/tanya/crypto/mode.d
Normal file
@ -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/. */
|
||||
|
||||
/**
|
||||
* Block cipher modes of operation.
|
||||
*
|
||||
* 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.mode;
|
||||
|
||||
import tanya.memory;
|
||||
import std.algorithm.iteration;
|
||||
import std.typecons;
|
||||
|
||||
/**
|
||||
* Supported padding mode.
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL pad)
|
||||
*/
|
||||
enum PaddingMode
|
||||
{
|
||||
zero,
|
||||
pkcs7,
|
||||
ansiX923,
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* input = Sequence that should be padded.
|
||||
* mode = Padding mode.
|
||||
* blockSize = Block size.
|
||||
* allocator = Allocator was used to allocate $(D_PARAM input).
|
||||
*
|
||||
* Returns: The function modifies the initial array and returns it.
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL PaddingMode)
|
||||
*/
|
||||
ubyte[] pad(ref ubyte[] input,
|
||||
in PaddingMode mode,
|
||||
in ushort blockSize,
|
||||
shared 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 (PaddingMode)
|
||||
{
|
||||
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);
|
||||
|
||||
pad(input, PaddingMode.zero, 64);
|
||||
assert(input.length == 64);
|
||||
|
||||
pad(input, PaddingMode.zero, 64);
|
||||
assert(input.length == 64);
|
||||
assert(input[63] == 0);
|
||||
|
||||
defaultAllocator.dispose(input);
|
||||
}
|
||||
{ // PKCS#7
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
}
|
||||
|
||||
pad(input, PaddingMode.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);
|
||||
}
|
||||
}
|
||||
|
||||
pad(input, PaddingMode.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.dispose(input);
|
||||
}
|
||||
{ // ANSI X.923
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
}
|
||||
|
||||
pad(input, PaddingMode.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);
|
||||
}
|
||||
}
|
||||
|
||||
pad(input, PaddingMode.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.dispose(input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* input = Sequence that should be padded.
|
||||
* mode = Padding mode.
|
||||
* blockSize = Block size.
|
||||
* allocator = Allocator was used to allocate $(D_PARAM input).
|
||||
*
|
||||
* Returns: The function modifies the initial array and returns it.
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL pad)
|
||||
*/
|
||||
ref ubyte[] unpad(ref ubyte[] input,
|
||||
in PaddingMode mode,
|
||||
in ushort blockSize,
|
||||
shared Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(input.length != 0);
|
||||
assert(input.length % 64 == 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
final switch (mode) with (PaddingMode)
|
||||
{
|
||||
case zero:
|
||||
break;
|
||||
case pkcs7:
|
||||
case ansiX923:
|
||||
immutable last = input[$ - 1];
|
||||
|
||||
allocator.shrinkArray(input, last ? last : blockSize);
|
||||
break;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
{ // Zeros
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = defaultAllocator.makeArray!ubyte(50);
|
||||
|
||||
pad(input, PaddingMode.zero, 64);
|
||||
pad(inputDup, PaddingMode.zero, 64);
|
||||
|
||||
unpad(input, PaddingMode.zero, 64);
|
||||
assert(input == inputDup);
|
||||
|
||||
defaultAllocator.dispose(input);
|
||||
defaultAllocator.dispose(inputDup);
|
||||
|
||||
}
|
||||
{ // PKCS#7
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
inputDup[i] = i;
|
||||
}
|
||||
|
||||
pad(input, PaddingMode.pkcs7, 64);
|
||||
unpad(input, PaddingMode.pkcs7, 64);
|
||||
assert(input == inputDup);
|
||||
|
||||
defaultAllocator.dispose(input);
|
||||
defaultAllocator.dispose(inputDup);
|
||||
}
|
||||
{ // ANSI X.923
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
inputDup[i] = i;
|
||||
}
|
||||
|
||||
pad(input, PaddingMode.pkcs7, 64);
|
||||
unpad(input, PaddingMode.pkcs7, 64);
|
||||
assert(input == inputDup);
|
||||
|
||||
defaultAllocator.dispose(input);
|
||||
defaultAllocator.dispose(inputDup);
|
||||
}
|
||||
}
|
@ -8,19 +8,11 @@
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.config;
|
||||
module tanya.crypto;
|
||||
|
||||
package version (DisableBackends)
|
||||
public
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
version (linux)
|
||||
{
|
||||
enum UseEpoll = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
enum UseEpoll = false;
|
||||
}
|
||||
import tanya.crypto.des;
|
||||
import tanya.crypto.mode;
|
||||
import tanya.crypto.symmetric;
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
/* 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);
|
||||
}
|
||||
}
|
177
source/tanya/crypto/symmetric.d
Normal file
177
source/tanya/crypto/symmetric.d
Normal file
@ -0,0 +1,177 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Interfaces for implementing secret key algorithms.
|
||||
*
|
||||
* 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.symmetric;
|
||||
|
||||
/**
|
||||
* Implemented by secret key algorithms.
|
||||
*/
|
||||
interface SymmetricCipher
|
||||
{
|
||||
/**
|
||||
* Returns: Key length.
|
||||
*/
|
||||
@property inout(uint) keyLength() inout const pure nothrow @safe @nogc;
|
||||
|
||||
/**
|
||||
* Returns: Minimum key length.
|
||||
*/
|
||||
@property inout(uint) minKeyLength() inout const pure nothrow @safe @nogc;
|
||||
|
||||
/**
|
||||
* Returns: Maximum key length.
|
||||
*/
|
||||
@property inout(uint) maxKeyLength() inout const pure nothrow @safe @nogc;
|
||||
|
||||
/// Cipher direction.
|
||||
protected enum Direction : ushort
|
||||
{
|
||||
encryption,
|
||||
decryption,
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* key = Key.
|
||||
*/
|
||||
@property void key(ubyte[] key) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(key.length >= minKeyLength);
|
||||
assert(key.length <= maxKeyLength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented by block ciphers.
|
||||
*/
|
||||
interface BlockCipher : SymmetricCipher
|
||||
{
|
||||
/**
|
||||
* Returns: Block size.
|
||||
*/
|
||||
@property inout(uint) blockSize() inout const pure nothrow @safe @nogc;
|
||||
|
||||
/**
|
||||
* Encrypts a block.
|
||||
*
|
||||
* Params:
|
||||
* plain = Plain text, input.
|
||||
* cipher = Cipher text, output.
|
||||
*/
|
||||
void encrypt(in ubyte[] plain, ubyte[] cipher)
|
||||
in
|
||||
{
|
||||
assert(plain.length == blockSize);
|
||||
assert(cipher.length == blockSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a block.
|
||||
*
|
||||
* Params:
|
||||
* cipher = Cipher text, input.
|
||||
* plain = Plain text, output.
|
||||
*/
|
||||
void decrypt(in ubyte[] cipher, ubyte[] plain)
|
||||
in
|
||||
{
|
||||
assert(plain.length == blockSize);
|
||||
assert(cipher.length == blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixed in by algorithms with fixed block size.
|
||||
*
|
||||
* Params:
|
||||
* N = Block size.
|
||||
*/
|
||||
mixin template FixedBlockSize(uint N)
|
||||
if (N != 0)
|
||||
{
|
||||
private enum uint blockSize_ = N;
|
||||
|
||||
/**
|
||||
* Returns: Fixed block size.
|
||||
*/
|
||||
final @property inout(uint) blockSize() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return blockSize_;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixed in by symmetric algorithms.
|
||||
* If $(D_PARAM Min) equals $(D_PARAM Max) fixed key length is assumed.
|
||||
*
|
||||
* Params:
|
||||
* Min = Minimum key length.
|
||||
* Max = Maximum key length.
|
||||
*/
|
||||
mixin template KeyLength(uint Min, uint Max = Min)
|
||||
if (Min != 0 && Max != 0)
|
||||
{
|
||||
static if (Min == Max)
|
||||
{
|
||||
private enum uint keyLength_ = Min;
|
||||
|
||||
/**
|
||||
* Returns: Key length.
|
||||
*/
|
||||
final @property inout(uint) keyLength() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return keyLength_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Minimum key length.
|
||||
*/
|
||||
final @property inout(uint) minKeyLength() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return keyLength_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Maximum key length.
|
||||
*/
|
||||
final @property inout(uint) maxKeyLength() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return keyLength_;
|
||||
}
|
||||
}
|
||||
else static if (Min < Max)
|
||||
{
|
||||
private enum uint minKeyLength_ = Min;
|
||||
private enum uint maxKeyLength_ = Max;
|
||||
|
||||
/**
|
||||
* Returns: Minimum key length.
|
||||
*/
|
||||
final @property inout(uint) minKeyLength() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return minKeyLength_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Maximum key length.
|
||||
*/
|
||||
final @property inout(uint) maxKeyLength() inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return maxKeyLength_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static assert(false, "Max should be larger or equal to Min");
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
/* 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;
|
||||
}
|
@ -1,217 +0,0 @@
|
||||
/* 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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
/* 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;
|
@ -1,46 +0,0 @@
|
||||
/* 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
|
||||
{
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
/* 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
|
||||
{
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,23 +10,28 @@
|
||||
*/
|
||||
module tanya.memory.allocator;
|
||||
|
||||
import std.experimental.allocator;
|
||||
import std.traits;
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
import tanya.memory : defaultAllocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface should be similar to $(D_PSYMBOL
|
||||
* std.experimental.allocator.IAllocator), but usable in
|
||||
* $(D_KEYWORD @nogc)-code.
|
||||
* Allocator interface.
|
||||
*/
|
||||
interface Allocator
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Allocates $(D_PARAM s) bytes of memory.
|
||||
* Allocates $(D_PARAM size) bytes of memory.
|
||||
*
|
||||
* Params:
|
||||
* s = Amount of memory to allocate.
|
||||
* size = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: The pointer to the new allocated memory.
|
||||
*/
|
||||
void[] allocate(size_t s) @safe;
|
||||
void[] allocate(size_t size) shared;
|
||||
|
||||
/**
|
||||
* Deallocates a memory block.
|
||||
@ -36,7 +41,7 @@ interface Allocator
|
||||
*
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool deallocate(void[] p) @safe;
|
||||
bool deallocate(void[] p) shared;
|
||||
|
||||
/**
|
||||
* Increases or decreases the size of a memory block.
|
||||
@ -47,12 +52,56 @@ interface Allocator
|
||||
*
|
||||
* Returns: Whether the reallocation was successful.
|
||||
*/
|
||||
bool reallocate(ref void[] p, size_t s) @safe;
|
||||
bool reallocate(ref void[] p, size_t size) shared;
|
||||
|
||||
/**
|
||||
* Static allocator instance and initializer.
|
||||
*
|
||||
* Returns: An $(D_PSYMBOL Allocator) instance.
|
||||
* Returns: The alignment offered.
|
||||
*/
|
||||
static @property Allocator instance() @safe;
|
||||
@property immutable(uint) alignment() shared const @safe pure nothrow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* T = Element type of the array being created.
|
||||
* allocator = The allocator used for getting memory.
|
||||
* array = A reference to the array being changed.
|
||||
* length = New array length.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
|
||||
* not be reallocated. In the latter
|
||||
*/
|
||||
bool resizeArray(T)(shared Allocator allocator,
|
||||
ref T[] array,
|
||||
in size_t length)
|
||||
{
|
||||
void[] buf = array;
|
||||
|
||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
array = cast(T[]) buf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
int[] p;
|
||||
|
||||
defaultAllocator.resizeArray(p, 20);
|
||||
assert(p.length == 20);
|
||||
|
||||
defaultAllocator.resizeArray(p, 30);
|
||||
assert(p.length == 30);
|
||||
|
||||
defaultAllocator.resizeArray(p, 10);
|
||||
assert(p.length == 10);
|
||||
|
||||
defaultAllocator.resizeArray(p, 0);
|
||||
assert(p is null);
|
||||
}
|
||||
|
||||
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|
||||
|| hasElaborateDestructor!T || isDynamicArray!T;
|
||||
|
173
source/tanya/memory/mallocator.d
Normal file
173
source/tanya/memory/mallocator.d
Normal file
@ -0,0 +1,173 @@
|
||||
/* 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.mallocator;
|
||||
|
||||
import tanya.memory.allocator;
|
||||
import core.exception;
|
||||
import core.stdc.stdlib;
|
||||
import std.algorithm.comparison;
|
||||
|
||||
/**
|
||||
* Wrapper for malloc/realloc/free from the C standard library.
|
||||
*/
|
||||
class Mallocator : Allocator
|
||||
{
|
||||
/**
|
||||
* 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) shared @nogc nothrow
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
auto p = malloc(size + psize);
|
||||
|
||||
if (!p)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
return p[psize.. psize + size];
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
auto p = Mallocator.instance.allocate(20);
|
||||
|
||||
assert(p.length == 20);
|
||||
|
||||
Mallocator.instance.deallocate(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) shared @nogc nothrow
|
||||
{
|
||||
if (p !is null)
|
||||
{
|
||||
free(p.ptr - psize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
void[] p;
|
||||
assert(Mallocator.instance.deallocate(p));
|
||||
|
||||
p = Mallocator.instance.allocate(10);
|
||||
assert(Mallocator.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) shared @nogc nothrow
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
deallocate(p);
|
||||
p = null;
|
||||
return true;
|
||||
}
|
||||
else if (p is null)
|
||||
{
|
||||
p = allocate(size);
|
||||
return true;
|
||||
}
|
||||
auto r = realloc(p.ptr - psize, size + psize);
|
||||
|
||||
if (!r)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
p = r[psize.. psize + size];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
void[] p;
|
||||
|
||||
Mallocator.instance.reallocate(p, 20);
|
||||
assert(p.length == 20);
|
||||
|
||||
Mallocator.instance.reallocate(p, 30);
|
||||
assert(p.length == 30);
|
||||
|
||||
Mallocator.instance.reallocate(p, 10);
|
||||
assert(p.length == 10);
|
||||
|
||||
Mallocator.instance.reallocate(p, 0);
|
||||
assert(p is null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The alignment offered.
|
||||
*/
|
||||
@property immutable(uint) alignment() shared const @safe pure nothrow
|
||||
{
|
||||
return cast(uint) max(double.alignof, real.alignof);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static allocator instance and initializer.
|
||||
*
|
||||
* Returns: The global $(D_PSYMBOL Allocator) instance.
|
||||
*/
|
||||
static @property ref shared(Mallocator) instance() @nogc nothrow
|
||||
{
|
||||
if (instance_ is null)
|
||||
{
|
||||
immutable size = __traits(classInstanceSize, Mallocator) + psize;
|
||||
void* p = malloc(size);
|
||||
|
||||
if (p is null)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
p[psize..size] = typeid(Mallocator).initializer[];
|
||||
instance_ = cast(shared Mallocator) p[psize..size].ptr;
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
assert(instance is instance);
|
||||
}
|
||||
|
||||
private enum psize = 8;
|
||||
|
||||
private shared static Mallocator instance_;
|
||||
}
|
486
source/tanya/memory/mmappool.d
Normal file
486
source/tanya/memory/mmappool.d
Normal file
@ -0,0 +1,486 @@
|
||||
/* 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.mmappool;
|
||||
|
||||
import tanya.memory.allocator;
|
||||
import core.atomic;
|
||||
import core.exception;
|
||||
|
||||
version (Posix)
|
||||
{
|
||||
import core.stdc.errno;
|
||||
import core.sys.posix.sys.mman;
|
||||
import core.sys.posix.unistd;
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
import core.sys.windows.winbase;
|
||||
import core.sys.windows.windows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* enough 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 complete
|
||||
* 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 | | |
|
||||
* | | | | | || | | |
|
||||
* --------------------------------------------------- ------------------------
|
||||
*
|
||||
* TODO:
|
||||
* $(UL
|
||||
* $(LI Thread safety (core.atomic.cas))
|
||||
* $(LI If two neighbour blocks are free, they can be merged)
|
||||
* $(LI Reallocation shoud check if there is enough free space in the
|
||||
* next block instead of always moving the memory)
|
||||
* $(LI Make 64 KB regions mininmal region size on Linux)
|
||||
* )
|
||||
*/
|
||||
class MmapPool : Allocator
|
||||
{
|
||||
@disable this();
|
||||
|
||||
shared static this()
|
||||
{
|
||||
version (Posix)
|
||||
{
|
||||
pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
pageSize = si.dwPageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) shared @nogc @trusted nothrow
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
immutable dataSize = addAlignment(size);
|
||||
|
||||
void* data = findBlock(dataSize);
|
||||
if (data is null)
|
||||
{
|
||||
data = initializeRegion(dataSize);
|
||||
}
|
||||
|
||||
return data is null ? null : data[0..size];
|
||||
}
|
||||
|
||||
///
|
||||
@nogc @safe nothrow unittest
|
||||
{
|
||||
auto p = MmapPool.instance.allocate(20);
|
||||
|
||||
assert(p);
|
||||
|
||||
MmapPool.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) shared @nogc 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;
|
||||
atomicOp!"+="(block1.region.blocks, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
block1.free = false;
|
||||
atomicOp!"+="(block1.region.blocks, 1);
|
||||
}
|
||||
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) shared @nogc @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;
|
||||
}
|
||||
version (Posix)
|
||||
{
|
||||
return munmap(cast(void*) block.region, block.region.size) == 0;
|
||||
}
|
||||
version (Windows)
|
||||
{
|
||||
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
block.free = true;
|
||||
atomicOp!"-="(block.region.blocks, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
@nogc @safe nothrow unittest
|
||||
{
|
||||
auto p = MmapPool.instance.allocate(20);
|
||||
|
||||
assert(MmapPool.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) shared @nogc @trusted nothrow
|
||||
{
|
||||
void[] reallocP;
|
||||
|
||||
if (size == p.length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (size > 0)
|
||||
{
|
||||
reallocP = allocate(size);
|
||||
if (reallocP is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (p !is null)
|
||||
{
|
||||
if (size > p.length)
|
||||
{
|
||||
reallocP[0..p.length] = p[0..$];
|
||||
}
|
||||
else if (size > 0)
|
||||
{
|
||||
reallocP[0..size] = p[0..size];
|
||||
}
|
||||
deallocate(p);
|
||||
}
|
||||
p = reallocP;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc @safe nothrow unittest
|
||||
{
|
||||
void[] p;
|
||||
MmapPool.instance.reallocate(p, 10 * int.sizeof);
|
||||
(cast(int[]) p)[7] = 123;
|
||||
|
||||
assert(p.length == 40);
|
||||
|
||||
MmapPool.instance.reallocate(p, 8 * int.sizeof);
|
||||
|
||||
assert(p.length == 32);
|
||||
assert((cast(int[]) p)[7] == 123);
|
||||
|
||||
MmapPool.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);
|
||||
|
||||
MmapPool.instance.reallocate(p, 8 * int.sizeof);
|
||||
|
||||
assert(p.length == 32);
|
||||
assert((cast(int[]) p)[7] == 123);
|
||||
|
||||
MmapPool.instance.deallocate(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static allocator instance and initializer.
|
||||
*
|
||||
* Returns: Global $(D_PSYMBOL MmapPool) instance.
|
||||
*/
|
||||
static @property ref shared(MmapPool) instance() @nogc @trusted nothrow
|
||||
{
|
||||
if (instance_ is null)
|
||||
{
|
||||
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
|
||||
|
||||
Region head; // Will become soon our region list head
|
||||
void* data = initializeRegion(instanceSize, head);
|
||||
if (data !is null)
|
||||
{
|
||||
data[0..instanceSize] = typeid(MmapPool).initializer[];
|
||||
instance_ = cast(shared MmapPool) data;
|
||||
instance_.head = head;
|
||||
}
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc @safe nothrow 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) @nogc nothrow
|
||||
{
|
||||
immutable regionSize = calculateRegionSize(size);
|
||||
|
||||
version (Posix)
|
||||
{
|
||||
void* p = mmap(null,
|
||||
regionSize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0);
|
||||
if (p is MAP_FAILED)
|
||||
{
|
||||
if (errno == ENOMEM)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
void* p = VirtualAlloc(null,
|
||||
regionSize,
|
||||
MEM_COMMIT,
|
||||
PAGE_READWRITE);
|
||||
if (p is null)
|
||||
{
|
||||
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
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) shared @nogc 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)
|
||||
@nogc @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)
|
||||
@nogc @safe pure nothrow
|
||||
out (result)
|
||||
{
|
||||
assert(result > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
x += regionEntrySize + blockEntrySize * 2;
|
||||
return x / pageSize * pageSize + pageSize;
|
||||
}
|
||||
|
||||
@property immutable(uint) alignment() shared const @nogc @safe pure nothrow
|
||||
{
|
||||
return alignment_;
|
||||
}
|
||||
private enum alignment_ = 8;
|
||||
|
||||
private shared static MmapPool instance_;
|
||||
|
||||
private shared static immutable size_t pageSize;
|
||||
|
||||
private shared struct RegionEntry
|
||||
{
|
||||
Region prev;
|
||||
Region next;
|
||||
uint blocks;
|
||||
size_t size;
|
||||
}
|
||||
private alias Region = shared RegionEntry*;
|
||||
private enum regionEntrySize = 32;
|
||||
|
||||
private shared Region head;
|
||||
|
||||
private shared struct BlockEntry
|
||||
{
|
||||
Block prev;
|
||||
Block next;
|
||||
bool free;
|
||||
size_t size;
|
||||
Region region;
|
||||
}
|
||||
private alias Block = shared BlockEntry*;
|
||||
private enum blockEntrySize = 40;
|
||||
}
|
@ -10,198 +10,20 @@
|
||||
*/
|
||||
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)
|
||||
public
|
||||
{
|
||||
import core.sys.windows.windows;
|
||||
}
|
||||
else version (Posix)
|
||||
{
|
||||
public import tanya.memory.ullocator;
|
||||
import core.sys.posix.pthread;
|
||||
import tanya.memory.allocator;
|
||||
import std.experimental.allocator : make, dispose, shrinkArray, expandArray, makeArray, dispose;
|
||||
}
|
||||
|
||||
@nogc:
|
||||
shared Allocator allocator;
|
||||
|
||||
version (Windows)
|
||||
@property ref shared(Allocator) defaultAllocator()
|
||||
{
|
||||
package alias Mutex = CRITICAL_SECTION;
|
||||
package alias destroyMutex = DeleteCriticalSection;
|
||||
import tanya.memory.mallocator;
|
||||
if (allocator is null)
|
||||
{
|
||||
allocator = Mallocator.instance;
|
||||
}
|
||||
else version (Posix)
|
||||
{
|
||||
package alias Mutex = pthread_mutex_t;
|
||||
package void destroyMutex(pthread_mutex_t* mtx)
|
||||
{
|
||||
pthread_mutex_destroy(mtx) && assert(0);
|
||||
return allocator;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
@ -1,423 +0,0 @@
|
||||
/* 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;
|
||||
}
|
1373
source/tanya/network/socket.d
Normal file
1373
source/tanya/network/socket.d
Normal file
File diff suppressed because it is too large
Load Diff
@ -14,8 +14,6 @@ import tanya.memory;
|
||||
import std.digest.sha;
|
||||
import std.typecons;
|
||||
|
||||
@nogc:
|
||||
|
||||
/// Block size of entropy accumulator (SHA-512).
|
||||
enum blockSize = 64;
|
||||
|
||||
@ -27,7 +25,6 @@ enum maxGather = 128;
|
||||
*/
|
||||
class EntropyException : Exception
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Params:
|
||||
* msg = Message to output.
|
||||
@ -38,7 +35,7 @@ class EntropyException : Exception
|
||||
this(string msg,
|
||||
string file = __FILE__,
|
||||
size_t line = __LINE__,
|
||||
Throwable next = null) pure @safe nothrow const
|
||||
Throwable next = null) pure @safe nothrow const @nogc
|
||||
{
|
||||
super(msg, file, line, next);
|
||||
}
|
||||
@ -49,7 +46,6 @@ class EntropyException : Exception
|
||||
*/
|
||||
abstract class EntropySource
|
||||
{
|
||||
@nogc:
|
||||
/// Amount of already generated entropy.
|
||||
protected ushort size_;
|
||||
|
||||
@ -103,7 +99,6 @@ version (linux)
|
||||
*/
|
||||
class PlatformEntropySource : EntropySource
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Returns: Minimum bytes required from the entropy source.
|
||||
*/
|
||||
@ -164,13 +159,12 @@ version (linux)
|
||||
*/
|
||||
class Entropy
|
||||
{
|
||||
@nogc:
|
||||
/// Entropy sources.
|
||||
protected EntropySource[] sources;
|
||||
|
||||
private ubyte sourceCount_;
|
||||
|
||||
private Allocator allocator;
|
||||
private shared Allocator allocator;
|
||||
|
||||
/// Entropy accumulator.
|
||||
protected SHA!(maxGather * 8, 512) accumulator;
|
||||
@ -181,7 +175,7 @@ class Entropy
|
||||
* allocator = Allocator to allocate entropy sources available on the
|
||||
* system.
|
||||
*/
|
||||
this(size_t maxSources = 20, Allocator allocator = defaultAllocator)
|
||||
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(maxSources > 0 && maxSources <= ubyte.max);
|
||||
|
Reference in New Issue
Block a user