Compare commits
90 Commits
Author | SHA1 | Date | |
---|---|---|---|
70632d975d | |||
d210a39249 | |||
e86ff63f91 | |||
3454a1965a | |||
c41fa2e98f | |||
a012ca4003 | |||
b74e5aa4ee | |||
44ac15ab78 | |||
b1b652b943 | |||
530a482402 | |||
f9023cf0ab | |||
0e91ea6786 | |||
63c6226a2a | |||
48a49c2a2d | |||
43c28b749d | |||
241767df13 | |||
b2baba9237 | |||
3e36ec0984 | |||
5be89e4858 | |||
a48d9cb739 | |||
a7206cbd02 | |||
1450a6adfe | |||
5fa9bd7b49 | |||
c7eb233fc7 | |||
20c8b659d1 | |||
4ea9c2b740 | |||
48205b2fc9 | |||
f5fe7bec4a | |||
c567b88d5d | |||
fe884541fc | |||
8973bdb2af | |||
4c4e65b373 | |||
7bed7f039f | |||
8ddea0aa46 | |||
cb6cc65113 | |||
4de42ca227 | |||
ab930657b6 | |||
291920b479 | |||
999c9bdb0f | |||
405b6d9f9f | |||
87b74b2542 | |||
d6514cb515 | |||
976eb4bfbc | |||
fb843e3473 | |||
f3d48234c0 | |||
254b881da6 | |||
8e0b742748 | |||
a35e04c049 | |||
4271c8583e | |||
e27d0fe58c | |||
67952dabdb | |||
b8d5d4c2bd | |||
b6413823cd | |||
48e355b87f | |||
b3f4ea572e | |||
c73e704421 | |||
0561e96f21 | |||
86d87430da | |||
0156c5a883 | |||
c966b42ac3 | |||
200fff3714 | |||
28755b4d01 | |||
8bd6a14988 | |||
b41dcc9f37 | |||
38addb7a5b | |||
f7fb89fed0 | |||
e32af2d09e | |||
f1bc4dc2e2 | |||
40857e69b7 | |||
c1fb89af99 | |||
061cd6264b | |||
f437dafa6b | |||
54d0597657 | |||
ab9f96e0c7 | |||
711855474c | |||
b20f367aa8 | |||
a2dadda511 | |||
77dca31261 | |||
b87aed4395 | |||
42bbb3b023 | |||
4309a30dfe | |||
9362287938 | |||
78bd901339 | |||
c8e6d44f7b | |||
f75433e0e6 | |||
fa607141e4 | |||
b3fdd6fd4a | |||
86c08e7af6 | |||
1c5796eb96 | |||
f7f92e7906 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,3 +4,8 @@
|
|||||||
# D
|
# D
|
||||||
.dub
|
.dub
|
||||||
__test__*__
|
__test__*__
|
||||||
|
__test__*__.core
|
||||||
|
/tanya-test-library
|
||||||
|
|
||||||
|
/docs/
|
||||||
|
/docs.json
|
||||||
|
20
.travis.yml
Normal file
20
.travis.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
sudo: false
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
language: d
|
||||||
|
|
||||||
|
d:
|
||||||
|
- dmd-2.073.0
|
||||||
|
- dmd-2.072.2
|
||||||
|
- dmd-2.071.2
|
||||||
|
- dmd-2.070.2
|
||||||
|
|
||||||
|
env:
|
||||||
|
matrix:
|
||||||
|
- ARCH=x86_64
|
||||||
|
|
||||||
|
script:
|
||||||
|
- dub test --arch=$ARCH
|
68
README.md
68
README.md
@ -1,4 +1,66 @@
|
|||||||
# tanya
|
# Tanya
|
||||||
|
|
||||||
tanya's mission is to provide a GC-free, general purpose library for D
|
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||||
programming language for developing servers.
|
[](https://code.dlang.org/packages/tanya)
|
||||||
|
[](https://code.dlang.org/packages/tanya)
|
||||||
|
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
||||||
|
|
||||||
|
Tanya is a general purpose library for D programming language.
|
||||||
|
|
||||||
|
Its aim is to simplify the manual memory management in D and to provide a
|
||||||
|
guarantee with @nogc attribute that there are no hidden allocations on the
|
||||||
|
Garbage Collector heap. Everything in the library is usable in @nogc code.
|
||||||
|
Tanya extends Phobos functionality and provides alternative implementations for
|
||||||
|
data structures and utilities that depend on the Garbage Collector in Phobos.
|
||||||
|
|
||||||
|
* [Bug tracker](https://issues.caraus.io/projects/tanya)
|
||||||
|
* [Documentation](https://docs.caraus.io/tanya)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Tanya consists of the following packages:
|
||||||
|
|
||||||
|
* `async`: Event loop (epoll, kqueue and IOCP).
|
||||||
|
* `container`: Queue, Vector, Singly linked list, buffers.
|
||||||
|
* `math`: Arbitrary precision integer and a set of functions.
|
||||||
|
* `memory`: Tools for manual memory management (allocator, reference counting,
|
||||||
|
helper functions).
|
||||||
|
* `network`: URL-Parsing, sockets.
|
||||||
|
|
||||||
|
### Supported compilers
|
||||||
|
|
||||||
|
* dmd 2.073.0
|
||||||
|
* dmd 2.072.2
|
||||||
|
* dmd 2.071.2
|
||||||
|
* dmd 2.070.2
|
||||||
|
|
||||||
|
### Current status
|
||||||
|
|
||||||
|
The library is currently under development, but the API is becoming gradually
|
||||||
|
stable.
|
||||||
|
|
||||||
|
`container`s are being extended to support ranges. Also following modules are
|
||||||
|
coming soon:
|
||||||
|
* UTF-8 string.
|
||||||
|
* Hash table.
|
||||||
|
|
||||||
|
`math` package contains an arbitrary precision integer implementation that
|
||||||
|
needs more test cases, better performance and some additional features
|
||||||
|
(constructing from a string and an ubyte array, and converting it back).
|
||||||
|
|
||||||
|
### Further characteristics
|
||||||
|
|
||||||
|
* Tanya is a native D library.
|
||||||
|
|
||||||
|
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
|
||||||
|
is being tested on Windows and FreeBSD as well.
|
||||||
|
|
||||||
|
* The library isn't thread-safe. Thread-safity should be added later.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Since I'm mostly busy writing new code and implementing new features I would
|
||||||
|
appreciate, if anyone uses the library. It would help me to improve the
|
||||||
|
codebase and fix issues.
|
||||||
|
|
||||||
|
Feel free to contact me if you have any questions.
|
||||||
|
2
dub.json
2
dub.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tanya",
|
"name": "tanya",
|
||||||
"description": "D library with event loop",
|
"description": "General purpose, @nogc library",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"copyright": "(c) Eugene Wissner <info@caraus.de>",
|
"copyright": "(c) Eugene Wissner <info@caraus.de>",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.event.epoll;
|
module tanya.async.event.epoll;
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ import tanya.async.event.selector;
|
|||||||
import tanya.async.loop;
|
import tanya.async.loop;
|
||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
|
import tanya.container.vector;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
@ -26,30 +27,36 @@ import core.sys.posix.unistd;
|
|||||||
import core.time;
|
import core.time;
|
||||||
import std.algorithm.comparison;
|
import std.algorithm.comparison;
|
||||||
|
|
||||||
class EpollLoop : SelectorLoop
|
extern (C) nothrow @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EpollLoop : SelectorLoop
|
||||||
{
|
{
|
||||||
protected int fd;
|
protected int fd;
|
||||||
private epoll_event[] events;
|
private Vector!epoll_event events;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the loop.
|
* Initializes the loop.
|
||||||
*/
|
*/
|
||||||
this()
|
this() @nogc
|
||||||
{
|
{
|
||||||
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
||||||
{
|
{
|
||||||
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
|
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
||||||
}
|
}
|
||||||
super();
|
super();
|
||||||
events = MmapPool.instance.makeArray!epoll_event(maxEvents);
|
events = Vector!epoll_event(maxEvents, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free loop internals.
|
* Frees loop internals.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this() @nogc
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(events);
|
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,12 +70,9 @@ class EpollLoop : SelectorLoop
|
|||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
protected override bool reify(ConnectionWatcher watcher, EventMask oldEvents, EventMask events)
|
protected override bool reify(SocketWatcher watcher,
|
||||||
in
|
EventMask oldEvents,
|
||||||
{
|
EventMask events) @nogc
|
||||||
assert(watcher !is null);
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
{
|
||||||
int op = EPOLL_CTL_DEL;
|
int op = EPOLL_CTL_DEL;
|
||||||
epoll_event ev;
|
epoll_event ev;
|
||||||
@ -97,46 +101,47 @@ class EpollLoop : SelectorLoop
|
|||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
protected override void poll()
|
protected override void poll() @nogc
|
||||||
{
|
{
|
||||||
// Don't block
|
// Don't block
|
||||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||||
auto eventCount = epoll_wait(fd, events.ptr, maxEvents, timeout);
|
auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout);
|
||||||
|
|
||||||
if (eventCount < 0)
|
if (eventCount < 0)
|
||||||
{
|
{
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!BadLoopException();
|
throw defaultAllocator.make!BadLoopException();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto i = 0; i < eventCount; ++i)
|
for (auto i = 0; i < eventCount; ++i)
|
||||||
{
|
{
|
||||||
auto io = cast(IOWatcher) connections[events[i].data.fd];
|
auto transport = cast(StreamTransport) connections[events[i].data.fd];
|
||||||
|
|
||||||
if (io is null)
|
if (transport is null)
|
||||||
{
|
{
|
||||||
acceptConnections(connections[events[i].data.fd]);
|
auto connection = cast(ConnectionWatcher) connections[events[i].data.fd];
|
||||||
|
assert(connection !is null);
|
||||||
|
|
||||||
|
acceptConnections(connection);
|
||||||
}
|
}
|
||||||
else if (events[i].events & EPOLLERR)
|
else if (events[i].events & EPOLLERR)
|
||||||
{
|
{
|
||||||
kill(io, null);
|
kill(transport);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
|
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
|
||||||
{
|
{
|
||||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
SocketException exception;
|
SocketException exception;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ptrdiff_t received;
|
ptrdiff_t received;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
received = transport.socket.receive(io.output[]);
|
received = transport.socket.receive(transport.output[]);
|
||||||
io.output += received;
|
transport.output += received;
|
||||||
}
|
}
|
||||||
while (received);
|
while (received);
|
||||||
}
|
}
|
||||||
@ -146,18 +151,16 @@ class EpollLoop : SelectorLoop
|
|||||||
}
|
}
|
||||||
if (transport.socket.disconnected)
|
if (transport.socket.disconnected)
|
||||||
{
|
{
|
||||||
kill(io, exception);
|
kill(transport, exception);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (io.output.length)
|
else if (transport.output.length)
|
||||||
{
|
{
|
||||||
swapPendings.insertBack(io);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (events[i].events & EPOLLOUT)
|
if (events[i].events & EPOLLOUT)
|
||||||
{
|
{
|
||||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
transport.writeReady = true;
|
transport.writeReady = true;
|
||||||
if (transport.input.length)
|
if (transport.input.length)
|
||||||
{
|
{
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.event.iocp;
|
module tanya.async.event.iocp;
|
||||||
|
|
||||||
@ -26,36 +26,68 @@ import core.sys.windows.winbase;
|
|||||||
import core.sys.windows.windef;
|
import core.sys.windows.windef;
|
||||||
import core.sys.windows.winsock2;
|
import core.sys.windows.winsock2;
|
||||||
|
|
||||||
class IOCPStreamTransport : StreamTransport
|
/**
|
||||||
|
* Transport for stream sockets.
|
||||||
|
*/
|
||||||
|
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||||
{
|
{
|
||||||
private OverlappedConnectedSocket socket_;
|
private SocketException exception;
|
||||||
|
|
||||||
private WriteBuffer input;
|
private ReadBuffer!ubyte output;
|
||||||
|
|
||||||
|
private WriteBuffer!ubyte input;
|
||||||
|
|
||||||
|
private Protocol protocol_;
|
||||||
|
|
||||||
|
private bool closing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new completion port transport.
|
* Creates new completion port transport.
|
||||||
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* socket = Socket.
|
* socket = Socket.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE socket !is null)
|
||||||
*/
|
*/
|
||||||
this(OverlappedConnectedSocket socket)
|
this(OverlappedConnectedSocket socket) @nogc
|
||||||
in
|
{
|
||||||
|
super(socket);
|
||||||
|
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||||
|
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Socket.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE socket !is null)
|
||||||
|
*/
|
||||||
|
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
|
||||||
|
out (socket)
|
||||||
{
|
{
|
||||||
assert(socket !is null);
|
assert(socket !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
socket_ = socket;
|
return cast(OverlappedConnectedSocket) socket_;
|
||||||
input = MmapPool.instance.make!WriteBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~this()
|
/**
|
||||||
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
|
*/
|
||||||
|
bool isClosing() const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(input);
|
return closing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property inout(OverlappedConnectedSocket) socket() inout pure nothrow @safe @nogc
|
/**
|
||||||
|
* Close the transport.
|
||||||
|
*
|
||||||
|
* Buffered data will be flushed. No more data will be received.
|
||||||
|
*/
|
||||||
|
void close() pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return socket_;
|
closing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,10 +96,50 @@ class IOCPStreamTransport : StreamTransport
|
|||||||
* Params:
|
* Params:
|
||||||
* data = Data to send.
|
* data = Data to send.
|
||||||
*/
|
*/
|
||||||
void write(ubyte[] data)
|
void write(ubyte[] data) @nogc
|
||||||
|
{
|
||||||
|
input ~= data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Application protocol.
|
||||||
|
*/
|
||||||
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return protocol_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the protocol.
|
||||||
|
*
|
||||||
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* protocol = Application protocol.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
|
*/
|
||||||
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
protocol_ = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the watcher callback.
|
||||||
|
*/
|
||||||
|
override void invoke() @nogc
|
||||||
|
{
|
||||||
|
if (output.length)
|
||||||
{
|
{
|
||||||
immutable empty = input.length == 0;
|
immutable empty = input.length == 0;
|
||||||
input ~= data;
|
protocol.received(output[0 .. $]);
|
||||||
|
output.clear();
|
||||||
if (empty)
|
if (empty)
|
||||||
{
|
{
|
||||||
SocketState overlapped;
|
SocketState overlapped;
|
||||||
@ -83,9 +155,17 @@ class IOCPStreamTransport : StreamTransport
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
protocol.disconnected(exception);
|
||||||
|
MmapPool.instance.dispose(protocol_);
|
||||||
|
defaultAllocator.dispose(exception);
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IOCPLoop : Loop
|
final class IOCPLoop : Loop
|
||||||
{
|
{
|
||||||
protected HANDLE completionPort;
|
protected HANDLE completionPort;
|
||||||
|
|
||||||
@ -94,14 +174,15 @@ class IOCPLoop : Loop
|
|||||||
/**
|
/**
|
||||||
* Initializes the loop.
|
* Initializes the loop.
|
||||||
*/
|
*/
|
||||||
this()
|
this() @nogc
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
||||||
if (!completionPort)
|
if (!completionPort)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!BadLoopException("Creating completion port failed");
|
throw make!BadLoopException(defaultAllocator,
|
||||||
|
"Creating completion port failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,9 +196,9 @@ class IOCPLoop : Loop
|
|||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
override protected bool reify(ConnectionWatcher watcher,
|
override protected bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events)
|
EventMask events) @nogc
|
||||||
{
|
{
|
||||||
SocketState overlapped;
|
SocketState overlapped;
|
||||||
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
||||||
@ -141,17 +222,14 @@ class IOCPLoop : Loop
|
|||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
theAllocator.dispose(e);
|
defaultAllocator.dispose(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(oldEvents & Event.read) && (events & Event.read)
|
if (!(oldEvents & Event.read) && (events & Event.read)
|
||||||
|| !(oldEvents & Event.write) && (events & Event.write))
|
|| !(oldEvents & Event.write) && (events & Event.write))
|
||||||
{
|
{
|
||||||
auto io = cast(IOWatcher) watcher;
|
auto transport = cast(StreamTransport) watcher;
|
||||||
assert(io !is null);
|
|
||||||
|
|
||||||
auto transport = cast(IOCPStreamTransport) io.transport;
|
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
|
|
||||||
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
||||||
@ -168,12 +246,12 @@ class IOCPLoop : Loop
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
overlapped = MmapPool.instance.make!SocketState;
|
overlapped = MmapPool.instance.make!SocketState;
|
||||||
transport.socket.beginReceive(io.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
theAllocator.dispose(e);
|
defaultAllocator.dispose(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,10 +259,24 @@ class IOCPLoop : Loop
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void kill(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(transport !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
transport.socket.shutdown();
|
||||||
|
defaultAllocator.dispose(transport.socket);
|
||||||
|
transport.exception = exception;
|
||||||
|
pendings.enqueue(transport);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
override protected void poll()
|
override protected void poll() @nogc
|
||||||
{
|
{
|
||||||
DWORD lpNumberOfBytes;
|
DWORD lpNumberOfBytes;
|
||||||
ULONG_PTR key;
|
ULONG_PTR key;
|
||||||
@ -218,29 +310,26 @@ class IOCPLoop : Loop
|
|||||||
assert(listener !is null);
|
assert(listener !is null);
|
||||||
|
|
||||||
auto socket = listener.endAccept(overlapped);
|
auto socket = listener.endAccept(overlapped);
|
||||||
auto transport = MmapPool.instance.make!IOCPStreamTransport(socket);
|
auto transport = MmapPool.instance.make!StreamTransport(socket);
|
||||||
auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol);
|
|
||||||
|
|
||||||
connection.incoming.insertBack(io);
|
connection.incoming.enqueue(transport);
|
||||||
|
|
||||||
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
|
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||||
|
|
||||||
swapPendings.insertBack(connection);
|
pendings.enqueue(connection);
|
||||||
listener.beginAccept(overlapped);
|
listener.beginAccept(overlapped);
|
||||||
break;
|
break;
|
||||||
case OverlappedSocketEvent.read:
|
case OverlappedSocketEvent.read:
|
||||||
auto io = cast(IOWatcher) (cast(void*) key);
|
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||||
assert(io !is null);
|
assert(transport !is null);
|
||||||
if (!io.active)
|
|
||||||
|
if (!transport.active)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(io);
|
MmapPool.instance.dispose(transport);
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto transport = cast(IOCPStreamTransport) io.transport;
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
int received;
|
int received;
|
||||||
SocketException exception;
|
SocketException exception;
|
||||||
try
|
try
|
||||||
@ -253,38 +342,39 @@ class IOCPLoop : Loop
|
|||||||
}
|
}
|
||||||
if (transport.socket.disconnected)
|
if (transport.socket.disconnected)
|
||||||
{
|
{
|
||||||
// We want to get one last notification to destroy the watcher
|
// We want to get one last notification to destroy the watcher.
|
||||||
transport.socket.beginReceive(io.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
kill(io, exception);
|
kill(transport, exception);
|
||||||
}
|
}
|
||||||
else if (received > 0)
|
else if (received > 0)
|
||||||
{
|
{
|
||||||
immutable full = io.output.free == received;
|
immutable full = transport.output.free == received;
|
||||||
|
|
||||||
io.output += received;
|
transport.output += received;
|
||||||
// Receive was interrupted because the buffer is full. We have to continue
|
// Receive was interrupted because the buffer is full. We have to continue.
|
||||||
if (full)
|
if (full)
|
||||||
{
|
{
|
||||||
transport.socket.beginReceive(io.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
}
|
}
|
||||||
swapPendings.insertBack(io);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OverlappedSocketEvent.write:
|
case OverlappedSocketEvent.write:
|
||||||
auto io = cast(IOWatcher) (cast(void*) key);
|
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||||
assert(io !is null);
|
|
||||||
|
|
||||||
auto transport = cast(IOCPStreamTransport) io.transport;
|
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
|
|
||||||
transport.input += transport.socket.endSend(overlapped);
|
transport.input += transport.socket.endSend(overlapped);
|
||||||
if (transport.input.length)
|
if (transport.input.length > 0)
|
||||||
{
|
{
|
||||||
transport.socket.beginSend(transport.input[], overlapped);
|
transport.socket.beginSend(transport.input[], overlapped);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
transport.socket.beginReceive(io.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
|
if (transport.isClosing())
|
||||||
|
{
|
||||||
|
kill(transport);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -3,46 +3,62 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.event.kqueue;
|
module tanya.async.event.kqueue;
|
||||||
|
|
||||||
version (OSX)
|
version (OSX)
|
||||||
{
|
{
|
||||||
version = MissingKevent;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (iOS)
|
else version (iOS)
|
||||||
{
|
{
|
||||||
version = MissingKevent;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (TVOS)
|
else version (TVOS)
|
||||||
{
|
{
|
||||||
version = MissingKevent;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (WatchOS)
|
else version (WatchOS)
|
||||||
{
|
{
|
||||||
version = MissingKevent;
|
version = MacBSD;
|
||||||
|
}
|
||||||
|
else version (FreeBSD)
|
||||||
|
{
|
||||||
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (OpenBSD)
|
else version (OpenBSD)
|
||||||
{
|
{
|
||||||
version = MissingKevent;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (DragonFlyBSD)
|
else version (DragonFlyBSD)
|
||||||
{
|
{
|
||||||
version = MissingKevent;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
|
|
||||||
version (MissingKevent)
|
version (MacBSD):
|
||||||
{
|
|
||||||
extern (C):
|
|
||||||
nothrow:
|
|
||||||
@nogc:
|
|
||||||
|
|
||||||
import core.stdc.stdint; // intptr_t, uintptr_t
|
import core.stdc.errno;
|
||||||
import core.sys.posix.time; // timespec
|
import core.sys.posix.time; // timespec
|
||||||
|
import core.sys.posix.unistd;
|
||||||
|
import core.time;
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
import tanya.async.event.selector;
|
||||||
|
import tanya.async.loop;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.async.watcher;
|
||||||
|
import tanya.container.vector;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
|
||||||
|
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
|
||||||
|
{
|
||||||
|
*kevp = kevent_t(args);
|
||||||
|
}
|
||||||
|
|
||||||
enum : short
|
enum : short
|
||||||
{
|
{
|
||||||
@ -60,11 +76,6 @@ version (MissingKevent)
|
|||||||
EVFILT_SYSCOUNT = 11
|
EVFILT_SYSCOUNT = 11
|
||||||
}
|
}
|
||||||
|
|
||||||
extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args)
|
|
||||||
{
|
|
||||||
*kevp = kevent_t(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct kevent_t
|
struct kevent_t
|
||||||
{
|
{
|
||||||
uintptr_t ident; /* identifier for this event */
|
uintptr_t ident; /* identifier for this event */
|
||||||
@ -97,92 +108,54 @@ version (MissingKevent)
|
|||||||
EV_ERROR = 0x4000, /* error, data contains errno */
|
EV_ERROR = 0x4000, /* error, data contains errno */
|
||||||
}
|
}
|
||||||
|
|
||||||
int kqueue();
|
extern(C) int kqueue() nothrow @nogc;
|
||||||
int kevent(int kq, const kevent_t *changelist, int nchanges,
|
extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
|
||||||
kevent_t *eventlist, int nevents,
|
kevent_t *eventlist, int nevents, const timespec *timeout)
|
||||||
const timespec *timeout);
|
nothrow @nogc;
|
||||||
}
|
|
||||||
|
|
||||||
version (OSX)
|
final class KqueueLoop : SelectorLoop
|
||||||
{
|
|
||||||
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 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.sys.posix.sys.time;
|
|
||||||
import core.time;
|
|
||||||
import std.algorithm.comparison;
|
|
||||||
|
|
||||||
class KqueueLoop : SelectorLoop
|
|
||||||
{
|
{
|
||||||
protected int fd;
|
protected int fd;
|
||||||
private kevent_t[] events;
|
private Vector!kevent_t events;
|
||||||
private kevent_t[] changes;
|
private Vector!kevent_t changes;
|
||||||
private size_t changeCount;
|
private size_t changeCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Maximal event count can be got at a time
|
* Returns: Maximal event count can be got at a time
|
||||||
* (should be supported by the backend).
|
* (should be supported by the backend).
|
||||||
*/
|
*/
|
||||||
override protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
|
override protected @property uint maxEvents()
|
||||||
|
const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return cast(uint) events.length;
|
return cast(uint) events.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
this()
|
this() @nogc
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if ((fd = kqueue()) == -1)
|
if ((fd = kqueue()) == -1)
|
||||||
{
|
{
|
||||||
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
|
throw make!BadLoopException(defaultAllocator,
|
||||||
|
"kqueue initialization failed");
|
||||||
}
|
}
|
||||||
events = MmapPool.instance.makeArray!kevent_t(64);
|
events = Vector!kevent_t(64, MmapPool.instance);
|
||||||
changes = MmapPool.instance.makeArray!kevent_t(64);
|
changes = Vector!kevent_t(64, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free loop internals.
|
* Frees loop internals.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this() @nogc
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(events);
|
|
||||||
MmapPool.instance.dispose(changes);
|
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set(socket_t socket, short filter, ushort flags)
|
private void set(socket_t socket, short filter, ushort flags) @nogc
|
||||||
{
|
{
|
||||||
if (changes.length <= changeCount)
|
if (changes.length <= changeCount)
|
||||||
{
|
{
|
||||||
MmapPool.instance.resizeArray(changes, changeCount + maxEvents);
|
changes.length = changeCount + maxEvents;
|
||||||
}
|
}
|
||||||
EV_SET(&changes[changeCount],
|
EV_SET(&changes[changeCount],
|
||||||
cast(ulong) socket,
|
cast(ulong) socket,
|
||||||
@ -204,9 +177,9 @@ class KqueueLoop : SelectorLoop
|
|||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
override protected bool reify(ConnectionWatcher watcher,
|
override protected bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events)
|
EventMask events) @nogc
|
||||||
{
|
{
|
||||||
if (events != oldEvents)
|
if (events != oldEvents)
|
||||||
{
|
{
|
||||||
@ -233,24 +206,29 @@ class KqueueLoop : SelectorLoop
|
|||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
protected override void poll()
|
protected override void poll() @nogc
|
||||||
{
|
{
|
||||||
timespec ts;
|
timespec ts;
|
||||||
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
||||||
|
|
||||||
if (changeCount > maxEvents)
|
if (changeCount > maxEvents)
|
||||||
{
|
{
|
||||||
MmapPool.instance.resizeArray(events, changes.length);
|
events.length = changes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto eventCount = kevent(fd, changes.ptr, cast(int) changeCount, events.ptr, maxEvents, &ts);
|
auto eventCount = kevent(fd,
|
||||||
|
changes.get().ptr,
|
||||||
|
cast(int) changeCount,
|
||||||
|
events.get().ptr,
|
||||||
|
maxEvents,
|
||||||
|
&ts);
|
||||||
changeCount = 0;
|
changeCount = 0;
|
||||||
|
|
||||||
if (eventCount < 0)
|
if (eventCount < 0)
|
||||||
{
|
{
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!BadLoopException();
|
throw defaultAllocator.make!BadLoopException();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -259,29 +237,29 @@ class KqueueLoop : SelectorLoop
|
|||||||
{
|
{
|
||||||
assert(connections.length > events[i].ident);
|
assert(connections.length > events[i].ident);
|
||||||
|
|
||||||
IOWatcher io = cast(IOWatcher) connections[events[i].ident];
|
auto transport = cast(StreamTransport) connections[events[i].ident];
|
||||||
// If it is a ConnectionWatcher. Accept connections.
|
// If it is a ConnectionWatcher. Accept connections.
|
||||||
if (io is null)
|
if (transport is null)
|
||||||
{
|
{
|
||||||
acceptConnections(connections[events[i].ident]);
|
auto connection = cast(ConnectionWatcher) connections[events[i].ident];
|
||||||
|
assert(connection !is null);
|
||||||
|
|
||||||
|
acceptConnections(connection);
|
||||||
}
|
}
|
||||||
else if (events[i].flags & EV_ERROR)
|
else if (events[i].flags & EV_ERROR)
|
||||||
{
|
{
|
||||||
kill(io, null);
|
kill(transport);
|
||||||
}
|
}
|
||||||
else if (events[i].filter == EVFILT_READ)
|
else if (events[i].filter == EVFILT_READ)
|
||||||
{
|
{
|
||||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
SocketException exception;
|
SocketException exception;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ptrdiff_t received;
|
ptrdiff_t received;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
received = transport.socket.receive(io.output[]);
|
received = transport.socket.receive(transport.output[]);
|
||||||
io.output += received;
|
transport.output += received;
|
||||||
}
|
}
|
||||||
while (received);
|
while (received);
|
||||||
}
|
}
|
||||||
@ -291,18 +269,15 @@ class KqueueLoop : SelectorLoop
|
|||||||
}
|
}
|
||||||
if (transport.socket.disconnected)
|
if (transport.socket.disconnected)
|
||||||
{
|
{
|
||||||
kill(io, exception);
|
kill(transport, exception);
|
||||||
}
|
}
|
||||||
else if (io.output.length)
|
else if (transport.output.length)
|
||||||
{
|
{
|
||||||
swapPendings.insertBack(io);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (events[i].filter == EVFILT_WRITE)
|
else if (events[i].filter == EVFILT_WRITE)
|
||||||
{
|
{
|
||||||
auto transport = cast(SelectorStreamTransport) io.transport;
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
transport.writeReady = true;
|
transport.writeReady = true;
|
||||||
if (transport.input.length)
|
if (transport.input.length)
|
||||||
{
|
{
|
||||||
@ -316,7 +291,7 @@ class KqueueLoop : SelectorLoop
|
|||||||
* Returns: The blocking time.
|
* Returns: The blocking time.
|
||||||
*/
|
*/
|
||||||
override protected @property inout(Duration) blockTime()
|
override protected @property inout(Duration) blockTime()
|
||||||
inout @safe pure nothrow
|
inout @nogc @safe pure nothrow
|
||||||
{
|
{
|
||||||
return min(super.blockTime, 1.dur!"seconds");
|
return min(super.blockTime, 1.dur!"seconds");
|
||||||
}
|
}
|
||||||
@ -331,9 +306,10 @@ class KqueueLoop : SelectorLoop
|
|||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
||||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||||
* transport is be destroyed then).
|
* transport will be destroyed then).
|
||||||
*/
|
*/
|
||||||
protected override bool feed(SelectorStreamTransport transport, SocketException exception = null)
|
protected override bool feed(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
{
|
{
|
||||||
if (!super.feed(transport, exception))
|
if (!super.feed(transport, exception))
|
||||||
{
|
{
|
||||||
|
@ -3,37 +3,42 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.event.selector;
|
module tanya.async.event.selector;
|
||||||
|
|
||||||
version (Posix):
|
version (Posix):
|
||||||
|
|
||||||
import tanya.async.loop;
|
import tanya.async.loop;
|
||||||
|
import tanya.async.protocol;
|
||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
import tanya.container.buffer;
|
import tanya.container.buffer;
|
||||||
|
import tanya.container.vector;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
import core.sys.posix.netinet.in_;
|
|
||||||
import core.stdc.errno;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transport for stream sockets.
|
* Transport for stream sockets.
|
||||||
*/
|
*/
|
||||||
class SelectorStreamTransport : StreamTransport
|
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||||
{
|
{
|
||||||
private ConnectedSocket socket_;
|
|
||||||
|
|
||||||
/// Input buffer.
|
|
||||||
package WriteBuffer input;
|
|
||||||
|
|
||||||
private SelectorLoop loop;
|
private SelectorLoop loop;
|
||||||
|
|
||||||
|
private SocketException exception;
|
||||||
|
|
||||||
|
package ReadBuffer!ubyte output;
|
||||||
|
|
||||||
|
package WriteBuffer!ubyte input;
|
||||||
|
|
||||||
|
private Protocol protocol_;
|
||||||
|
|
||||||
|
private bool closing;
|
||||||
|
|
||||||
/// Received notification that the underlying socket is write-ready.
|
/// Received notification that the underlying socket is write-ready.
|
||||||
package bool writeReady;
|
package bool writeReady;
|
||||||
|
|
||||||
@ -41,28 +46,117 @@ class SelectorStreamTransport : StreamTransport
|
|||||||
* Params:
|
* Params:
|
||||||
* loop = Event loop.
|
* loop = Event loop.
|
||||||
* socket = Socket.
|
* socket = Socket.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE loop !is null && socket !is null)
|
||||||
*/
|
*/
|
||||||
this(SelectorLoop loop, ConnectedSocket socket)
|
this(SelectorLoop loop, ConnectedSocket socket) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(loop !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
super(socket);
|
||||||
|
this.loop = loop;
|
||||||
|
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||||
|
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Socket.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE socket !is null)
|
||||||
|
*/
|
||||||
|
override @property ConnectedSocket socket() pure nothrow @safe @nogc
|
||||||
|
out (socket)
|
||||||
|
{
|
||||||
|
assert(socket !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return cast(ConnectedSocket) socket_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(socket !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
{
|
{
|
||||||
socket_ = socket;
|
socket_ = socket;
|
||||||
this.loop = loop;
|
|
||||||
input = MmapPool.instance.make!WriteBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the transport and deallocate the data buffers.
|
* Returns: Application protocol.
|
||||||
*/
|
*/
|
||||||
~this()
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(input);
|
return protocol_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Transport socket.
|
* Switches the protocol.
|
||||||
|
*
|
||||||
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* protocol = Application protocol.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
*/
|
*/
|
||||||
inout(ConnectedSocket) socket() inout pure nothrow @safe @nogc
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
{
|
{
|
||||||
return socket_;
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
protocol_ = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
|
*/
|
||||||
|
bool isClosing() const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the transport.
|
||||||
|
*
|
||||||
|
* Buffered data will be flushed. No more data will be received.
|
||||||
|
*/
|
||||||
|
void close() @nogc
|
||||||
|
{
|
||||||
|
closing = true;
|
||||||
|
loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the watcher callback.
|
||||||
|
*/
|
||||||
|
override void invoke() @nogc
|
||||||
|
{
|
||||||
|
if (output.length)
|
||||||
|
{
|
||||||
|
protocol.received(output[0 .. $]);
|
||||||
|
output.clear();
|
||||||
|
if (isClosing() && input.length == 0)
|
||||||
|
{
|
||||||
|
loop.kill(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
protocol.disconnected(exception);
|
||||||
|
MmapPool.instance.dispose(protocol_);
|
||||||
|
defaultAllocator.dispose(exception);
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,7 +165,7 @@ class SelectorStreamTransport : StreamTransport
|
|||||||
* Params:
|
* Params:
|
||||||
* data = Data to send.
|
* data = Data to send.
|
||||||
*/
|
*/
|
||||||
void write(ubyte[] data)
|
void write(ubyte[] data) @nogc
|
||||||
{
|
{
|
||||||
if (!data.length)
|
if (!data.length)
|
||||||
{
|
{
|
||||||
@ -111,28 +205,60 @@ class SelectorStreamTransport : StreamTransport
|
|||||||
abstract class SelectorLoop : Loop
|
abstract class SelectorLoop : Loop
|
||||||
{
|
{
|
||||||
/// Pending connections.
|
/// Pending connections.
|
||||||
protected ConnectionWatcher[] connections;
|
protected Vector!SocketWatcher connections;
|
||||||
|
|
||||||
this()
|
this() @nogc
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
connections = MmapPool.instance.makeArray!ConnectionWatcher(maxEvents);
|
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
~this()
|
~this() @nogc
|
||||||
{
|
{
|
||||||
foreach (ref connection; connections)
|
foreach (ref connection; connections)
|
||||||
{
|
{
|
||||||
// We want to free only IOWatchers. ConnectionWatcher are created by the
|
// We want to free only the transports. ConnectionWatcher are created by the
|
||||||
// user and should be freed by himself.
|
// user and should be freed by himself.
|
||||||
auto io = cast(IOWatcher) connection;
|
if (cast(StreamTransport) connection !is null)
|
||||||
if (io !is null)
|
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(io);
|
MmapPool.instance.dispose(connection);
|
||||||
connection = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MmapPool.instance.dispose(connections);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 abstract protected bool reify(SocketWatcher watcher,
|
||||||
|
EventMask oldEvents,
|
||||||
|
EventMask events) @nogc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills the watcher and closes the connection.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* transport = Transport.
|
||||||
|
* exception = Occurred exception.
|
||||||
|
*/
|
||||||
|
protected void kill(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(transport !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
transport.socket.shutdown();
|
||||||
|
defaultAllocator.dispose(transport.socket);
|
||||||
|
transport.exception = exception;
|
||||||
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,7 +273,13 @@ abstract class SelectorLoop : Loop
|
|||||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||||
* transport will be destroyed then).
|
* transport will be destroyed then).
|
||||||
*/
|
*/
|
||||||
protected bool feed(SelectorStreamTransport transport, SocketException exception = null)
|
protected bool feed(StreamTransport transport,
|
||||||
|
SocketException exception = null) @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(transport !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
{
|
{
|
||||||
while (transport.input.length && transport.writeReady)
|
while (transport.input.length && transport.writeReady)
|
||||||
{
|
{
|
||||||
@ -171,12 +303,13 @@ abstract class SelectorLoop : Loop
|
|||||||
}
|
}
|
||||||
if (exception !is null)
|
if (exception !is null)
|
||||||
{
|
{
|
||||||
auto watcher = cast(IOWatcher) connections[transport.socket.handle];
|
kill(transport, exception);
|
||||||
assert(watcher !is null);
|
|
||||||
|
|
||||||
kill(watcher, exception);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (transport.input.length == 0 && transport.isClosing())
|
||||||
|
{
|
||||||
|
kill(transport);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +319,7 @@ abstract class SelectorLoop : Loop
|
|||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
*/
|
*/
|
||||||
override void start(ConnectionWatcher watcher)
|
override void start(ConnectionWatcher watcher) @nogc
|
||||||
{
|
{
|
||||||
if (watcher.active)
|
if (watcher.active)
|
||||||
{
|
{
|
||||||
@ -195,7 +328,7 @@ abstract class SelectorLoop : Loop
|
|||||||
|
|
||||||
if (connections.length <= watcher.socket)
|
if (connections.length <= watcher.socket)
|
||||||
{
|
{
|
||||||
MmapPool.instance.resizeArray(connections, watcher.socket.handle + maxEvents / 2);
|
connections.length = watcher.socket.handle + maxEvents / 2;
|
||||||
}
|
}
|
||||||
connections[watcher.socket.handle] = watcher;
|
connections[watcher.socket.handle] = watcher;
|
||||||
|
|
||||||
@ -208,7 +341,7 @@ abstract class SelectorLoop : Loop
|
|||||||
* Params:
|
* Params:
|
||||||
* connection = Connection watcher ready to accept.
|
* connection = Connection watcher ready to accept.
|
||||||
*/
|
*/
|
||||||
package void acceptConnections(ConnectionWatcher connection)
|
package void acceptConnections(ConnectionWatcher connection) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(connection !is null);
|
assert(connection !is null);
|
||||||
@ -224,7 +357,7 @@ abstract class SelectorLoop : Loop
|
|||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
theAllocator.dispose(e);
|
defaultAllocator.dispose(e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (client is null)
|
if (client is null)
|
||||||
@ -232,35 +365,33 @@ abstract class SelectorLoop : Loop
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
IOWatcher io;
|
StreamTransport transport;
|
||||||
auto transport = MmapPool.instance.make!SelectorStreamTransport(this, client);
|
|
||||||
|
|
||||||
if (connections.length > client.handle)
|
if (connections.length > client.handle)
|
||||||
{
|
{
|
||||||
io = cast(IOWatcher) connections[client.handle];
|
transport = cast(StreamTransport) connections[client.handle];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MmapPool.instance.resizeArray(connections, client.handle + maxEvents / 2);
|
connections.length = client.handle + maxEvents / 2;
|
||||||
}
|
}
|
||||||
if (io is null)
|
if (transport is null)
|
||||||
{
|
{
|
||||||
io = MmapPool.instance.make!IOWatcher(transport,
|
transport = MmapPool.instance.make!StreamTransport(this, client);
|
||||||
connection.protocol);
|
connections[client.handle] = transport;
|
||||||
connections[client.handle] = io;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
io(transport, connection.protocol);
|
transport.socket = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
|
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||||
connection.incoming.insertBack(io);
|
connection.incoming.enqueue(transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connection.incoming.empty)
|
if (!connection.incoming.empty)
|
||||||
{
|
{
|
||||||
swapPendings.insertBack(connection);
|
pendings.enqueue(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.iocp;
|
module tanya.async.iocp;
|
||||||
|
|
||||||
|
@ -3,74 +3,78 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*
|
*
|
||||||
* ---
|
* ---
|
||||||
* import tanya.async;
|
* import tanya.async;
|
||||||
|
* import tanya.memory;
|
||||||
* import tanya.network.socket;
|
* import tanya.network.socket;
|
||||||
*
|
*
|
||||||
* class EchoProtocol : TransmissionControlProtocol
|
* class EchoProtocol : TransmissionControlProtocol
|
||||||
* {
|
* {
|
||||||
* private DuplexTransport transport;
|
* private DuplexTransport transport;
|
||||||
*
|
*
|
||||||
* void received(ubyte[] data)
|
* void received(in ubyte[] data) @nogc
|
||||||
* {
|
* {
|
||||||
* transport.write(data);
|
* transport.write(data);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* void connected(DuplexTransport transport)
|
* void connected(DuplexTransport transport) @nogc
|
||||||
* {
|
* {
|
||||||
* this.transport = transport;
|
* this.transport = transport;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* void disconnected(SocketException e = null)
|
* void disconnected(SocketException e) @nogc
|
||||||
* {
|
* {
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* void main()
|
* void main()
|
||||||
* {
|
* {
|
||||||
* auto address = new InternetAddress("127.0.0.1", cast(ushort) 8192);
|
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
|
||||||
*
|
*
|
||||||
* version (Windows)
|
* version (Windows)
|
||||||
* {
|
* {
|
||||||
* auto sock = new OverlappedStreamSocket(AddressFamily.INET);
|
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
|
||||||
* }
|
* }
|
||||||
* else
|
* else
|
||||||
* {
|
* {
|
||||||
* auto sock = new StreamSocket(AddressFamily.INET);
|
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
|
||||||
* sock.blocking = false;
|
* sock.blocking = false;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* sock.bind(address);
|
* sock.bind(address);
|
||||||
* sock.listen(5);
|
* sock.listen(5);
|
||||||
*
|
*
|
||||||
* auto io = new ConnectionWatcher(sock);
|
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
||||||
* io.setProtocol!EchoProtocol;
|
* io.setProtocol!EchoProtocol;
|
||||||
*
|
*
|
||||||
* defaultLoop.start(io);
|
* defaultLoop.start(io);
|
||||||
* defaultLoop.run();
|
* defaultLoop.run();
|
||||||
*
|
*
|
||||||
* sock.shutdown();
|
* sock.shutdown();
|
||||||
|
* defaultAllocator.dispose(io);
|
||||||
|
* defaultAllocator.dispose(sock);
|
||||||
|
* defaultAllocator.dispose(address);
|
||||||
* }
|
* }
|
||||||
* ---
|
* ---
|
||||||
*/
|
*/
|
||||||
module tanya.async.loop;
|
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 core.time;
|
||||||
import std.algorithm.iteration;
|
import std.algorithm.iteration;
|
||||||
import std.algorithm.mutation;
|
import std.algorithm.mutation;
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
|
import tanya.async.transport;
|
||||||
|
import tanya.async.watcher;
|
||||||
|
import tanya.container.buffer;
|
||||||
|
import tanya.container.queue;
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
import tanya.network.socket;
|
||||||
|
|
||||||
version (DisableBackends)
|
version (DisableBackends)
|
||||||
{
|
{
|
||||||
@ -125,16 +129,17 @@ alias EventMask = BitFlags!Event;
|
|||||||
*/
|
*/
|
||||||
abstract class Loop
|
abstract class Loop
|
||||||
{
|
{
|
||||||
/// Pending watchers.
|
private bool done;
|
||||||
protected PendingQueue!Watcher pendings;
|
|
||||||
|
|
||||||
protected PendingQueue!Watcher swapPendings;
|
/// Pending watchers.
|
||||||
|
protected Queue!Watcher pendings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Maximal event count can be got at a time
|
* Returns: Maximal event count can be got at a time
|
||||||
* (should be supported by the backend).
|
* (should be supported by the backend).
|
||||||
*/
|
*/
|
||||||
protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
|
protected @property uint maxEvents()
|
||||||
|
const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return 128U;
|
return 128U;
|
||||||
}
|
}
|
||||||
@ -142,45 +147,47 @@ abstract class Loop
|
|||||||
/**
|
/**
|
||||||
* Initializes the loop.
|
* Initializes the loop.
|
||||||
*/
|
*/
|
||||||
this()
|
this() @nogc
|
||||||
{
|
{
|
||||||
pendings = MmapPool.instance.make!(PendingQueue!Watcher);
|
pendings = Queue!Watcher(MmapPool.instance);
|
||||||
swapPendings = MmapPool.instance.make!(PendingQueue!Watcher);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees loop internals.
|
* Frees loop internals.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this() @nogc
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(pendings);
|
foreach (w; pendings)
|
||||||
MmapPool.instance.dispose(swapPendings);
|
{
|
||||||
|
MmapPool.instance.dispose(w);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the loop.
|
* Starts the loop.
|
||||||
*/
|
*/
|
||||||
void run()
|
void run() @nogc
|
||||||
{
|
{
|
||||||
done_ = false;
|
done = false;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
poll();
|
poll();
|
||||||
|
|
||||||
// Invoke pendings
|
// Invoke pendings
|
||||||
swapPendings.each!((ref p) => p.invoke());
|
foreach (ref w; pendings)
|
||||||
|
{
|
||||||
swap(pendings, swapPendings);
|
w.invoke();
|
||||||
}
|
}
|
||||||
while (!done_);
|
}
|
||||||
|
while (!done);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Break out of the loop.
|
* Break out of the loop.
|
||||||
*/
|
*/
|
||||||
void unloop() @safe pure nothrow
|
void unloop() @safe pure nothrow @nogc
|
||||||
{
|
{
|
||||||
done_ = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -189,13 +196,14 @@ abstract class Loop
|
|||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
*/
|
*/
|
||||||
void start(ConnectionWatcher watcher)
|
void start(ConnectionWatcher watcher) @nogc
|
||||||
{
|
{
|
||||||
if (watcher.active)
|
if (watcher.active)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
watcher.active = true;
|
watcher.active = true;
|
||||||
|
|
||||||
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
|
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +213,7 @@ abstract class Loop
|
|||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
*/
|
*/
|
||||||
void stop(ConnectionWatcher watcher)
|
void stop(ConnectionWatcher watcher) @nogc
|
||||||
{
|
{
|
||||||
if (!watcher.active)
|
if (!watcher.active)
|
||||||
{
|
{
|
||||||
@ -226,18 +234,18 @@ abstract class Loop
|
|||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
abstract protected bool reify(ConnectionWatcher watcher,
|
abstract protected bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events);
|
EventMask events) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: The blocking time.
|
* Returns: The blocking time.
|
||||||
*/
|
*/
|
||||||
protected @property inout(Duration) blockTime()
|
protected @property inout(Duration) blockTime()
|
||||||
inout @safe pure nothrow
|
inout @safe pure nothrow @nogc
|
||||||
{
|
{
|
||||||
// Don't block if we have to do.
|
// Don't block if we have to do.
|
||||||
return swapPendings.empty ? blockTime_ : Duration.zero;
|
return pendings.empty ? blockTime_ : Duration.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -247,7 +255,7 @@ abstract class Loop
|
|||||||
* blockTime = The blocking time. Cannot be larger than
|
* blockTime = The blocking time. Cannot be larger than
|
||||||
* $(D_PSYMBOL maxBlockTime).
|
* $(D_PSYMBOL maxBlockTime).
|
||||||
*/
|
*/
|
||||||
protected @property void blockTime(in Duration blockTime) @safe pure nothrow
|
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
||||||
@ -258,25 +266,10 @@ abstract class Loop
|
|||||||
blockTime_ = blockTime;
|
blockTime_ = blockTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Kills the watcher and closes the connection.
|
|
||||||
*/
|
|
||||||
protected void kill(IOWatcher watcher, SocketException exception)
|
|
||||||
{
|
|
||||||
watcher.socket.shutdown();
|
|
||||||
theAllocator.dispose(watcher.socket);
|
|
||||||
MmapPool.instance.dispose(watcher.transport);
|
|
||||||
watcher.exception = exception;
|
|
||||||
swapPendings.insertBack(watcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
abstract protected void poll();
|
abstract protected void poll() @nogc;
|
||||||
|
|
||||||
/// Whether the event loop should be stopped.
|
|
||||||
private bool done_;
|
|
||||||
|
|
||||||
/// Maximal block time.
|
/// Maximal block time.
|
||||||
protected Duration blockTime_ = 1.dur!"minutes";
|
protected Duration blockTime_ = 1.dur!"minutes";
|
||||||
@ -287,7 +280,6 @@ abstract class Loop
|
|||||||
*/
|
*/
|
||||||
class BadLoopException : Exception
|
class BadLoopException : Exception
|
||||||
{
|
{
|
||||||
@nogc:
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* file = The file where the exception occurred.
|
* file = The file where the exception occurred.
|
||||||
@ -295,7 +287,7 @@ class BadLoopException : Exception
|
|||||||
* next = The previous exception in the chain of exceptions, if any.
|
* next = The previous exception in the chain of exceptions, if any.
|
||||||
*/
|
*/
|
||||||
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
||||||
pure @safe nothrow const
|
pure nothrow const @safe @nogc
|
||||||
{
|
{
|
||||||
super("Event loop cannot be initialized.", file, line, next);
|
super("Event loop cannot be initialized.", file, line, next);
|
||||||
}
|
}
|
||||||
@ -308,7 +300,7 @@ class BadLoopException : Exception
|
|||||||
*
|
*
|
||||||
* Returns: The default event loop.
|
* Returns: The default event loop.
|
||||||
*/
|
*/
|
||||||
@property Loop defaultLoop()
|
@property Loop defaultLoop() @nogc
|
||||||
{
|
{
|
||||||
if (defaultLoop_ !is null)
|
if (defaultLoop_ !is null)
|
||||||
{
|
{
|
||||||
@ -341,7 +333,7 @@ class BadLoopException : Exception
|
|||||||
* Params:
|
* Params:
|
||||||
* loop = The event loop.
|
* loop = The event loop.
|
||||||
*/
|
*/
|
||||||
@property void defaultLoop(Loop loop)
|
@property void defaultLoop(Loop loop) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(loop !is null);
|
assert(loop !is null);
|
||||||
@ -352,132 +344,3 @@ body
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Loop defaultLoop_;
|
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;
|
|
||||||
}
|
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async;
|
module tanya.async;
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.protocol;
|
module tanya.async.protocol;
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ interface Protocol
|
|||||||
* Params:
|
* Params:
|
||||||
* data = Read data.
|
* data = Read data.
|
||||||
*/
|
*/
|
||||||
void received(ubyte[] data);
|
void received(in ubyte[] data) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a connection is made.
|
* Called when a connection is made.
|
||||||
@ -30,7 +30,7 @@ interface Protocol
|
|||||||
* Params:
|
* Params:
|
||||||
* transport = Protocol transport.
|
* transport = Protocol transport.
|
||||||
*/
|
*/
|
||||||
void connected(DuplexTransport transport);
|
void connected(DuplexTransport transport) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a connection is lost.
|
* Called when a connection is lost.
|
||||||
@ -39,7 +39,7 @@ interface Protocol
|
|||||||
* exception = $(D_PSYMBOL Exception) if an error caused
|
* exception = $(D_PSYMBOL Exception) if an error caused
|
||||||
* the disconnect, $(D_KEYWORD null) otherwise.
|
* the disconnect, $(D_KEYWORD null) otherwise.
|
||||||
*/
|
*/
|
||||||
void disconnected(SocketException exception = null);
|
void disconnected(SocketException exception) @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.transport;
|
module tanya.async.transport;
|
||||||
|
|
||||||
|
import tanya.async.protocol;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,7 +38,7 @@ interface WriteTransport : Transport
|
|||||||
* Params:
|
* Params:
|
||||||
* data = Data to send.
|
* data = Data to send.
|
||||||
*/
|
*/
|
||||||
void write(ubyte[] data);
|
void write(ubyte[] data) @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,6 +46,46 @@ interface WriteTransport : Transport
|
|||||||
*/
|
*/
|
||||||
interface DuplexTransport : ReadTransport, WriteTransport
|
interface DuplexTransport : ReadTransport, WriteTransport
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Returns: Application protocol.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE protocol !is null)
|
||||||
|
*/
|
||||||
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
|
out (protocol)
|
||||||
|
{
|
||||||
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the protocol.
|
||||||
|
*
|
||||||
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* protocol = Application protocol.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
|
*/
|
||||||
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(protocol !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
|
*/
|
||||||
|
bool isClosing() const pure nothrow @safe @nogc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the transport.
|
||||||
|
*
|
||||||
|
* Buffered data will be flushed. No more data will be received.
|
||||||
|
*/
|
||||||
|
void close() @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,12 +93,8 @@ interface DuplexTransport : ReadTransport, WriteTransport
|
|||||||
*/
|
*/
|
||||||
interface SocketTransport : Transport
|
interface SocketTransport : Transport
|
||||||
{
|
{
|
||||||
@property inout(Socket) socket() inout pure nothrow @safe @nogc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a connection-oriented socket transport.
|
* Returns: Socket.
|
||||||
*/
|
*/
|
||||||
package interface StreamTransport : DuplexTransport, SocketTransport
|
@property Socket socket() pure nothrow @safe @nogc;
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
@ -3,31 +3,23 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.async.watcher;
|
module tanya.async.watcher;
|
||||||
|
|
||||||
|
import std.functional;
|
||||||
|
import std.exception;
|
||||||
import tanya.async.loop;
|
import tanya.async.loop;
|
||||||
import tanya.async.protocol;
|
import tanya.async.protocol;
|
||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.container.buffer;
|
import tanya.container.buffer;
|
||||||
|
import tanya.container.queue;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
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
|
* A watcher is an opaque structure that you allocate and register to record
|
||||||
@ -41,202 +33,85 @@ abstract class Watcher
|
|||||||
/**
|
/**
|
||||||
* Invoke some action on event.
|
* Invoke some action on event.
|
||||||
*/
|
*/
|
||||||
void invoke();
|
void invoke() @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConnectionWatcher : Watcher
|
/**
|
||||||
|
* Socket watcher.
|
||||||
|
*/
|
||||||
|
abstract class SocketWatcher : Watcher
|
||||||
{
|
{
|
||||||
/// Watched socket.
|
/// Watched socket.
|
||||||
private Socket socket_;
|
protected Socket socket_;
|
||||||
|
|
||||||
/// Protocol factory.
|
|
||||||
protected Protocol delegate() protocolFactory;
|
|
||||||
|
|
||||||
package PendingQueue!IOWatcher incoming;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* socket = Socket.
|
* socket = Socket.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE socket !is null)
|
||||||
*/
|
*/
|
||||||
this(Socket socket)
|
this(Socket socket) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(socket !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
{
|
{
|
||||||
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.
|
* Returns: Socket.
|
||||||
*/
|
*/
|
||||||
@property inout(Socket) socket() inout pure nothrow @nogc
|
@property Socket socket() pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return socket_;
|
return socket_;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: New protocol instance.
|
* Connection watcher.
|
||||||
*/
|
*/
|
||||||
@property Protocol protocol()
|
class ConnectionWatcher : SocketWatcher
|
||||||
|
{
|
||||||
|
/// Incoming connection queue.
|
||||||
|
Queue!DuplexTransport incoming;
|
||||||
|
|
||||||
|
private Protocol delegate() @nogc protocolFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* socket = Socket.
|
||||||
|
*/
|
||||||
|
this(Socket socket) @nogc
|
||||||
|
{
|
||||||
|
super(socket);
|
||||||
|
incoming = Queue!DuplexTransport(MmapPool.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* P = Protocol should be used.
|
||||||
|
*/
|
||||||
|
void setProtocol(P : Protocol)() @nogc
|
||||||
|
{
|
||||||
|
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes new connection callback.
|
||||||
|
*/
|
||||||
|
override void invoke() @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(protocolFactory !is null, "Protocol isn't set.");
|
assert(protocolFactory !is null, "Protocol isn't set.");
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
return protocolFactory();
|
foreach (transport; incoming)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes new connection callback.
|
|
||||||
*/
|
|
||||||
override void invoke()
|
|
||||||
{
|
{
|
||||||
foreach (io; incoming)
|
transport.protocol = protocolFactory();
|
||||||
{
|
transport.protocol.connected(transport);
|
||||||
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 @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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,510 +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.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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,13 +3,14 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.container.buffer;
|
module tanya.container.buffer;
|
||||||
|
|
||||||
|
import std.traits;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
version (unittest)
|
version (unittest)
|
||||||
@ -33,51 +34,6 @@ version (unittest)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for implemeting input/output buffers.
|
|
||||||
*/
|
|
||||||
interface Buffer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Returns: The size of the internal buffer.
|
|
||||||
*/
|
|
||||||
@property size_t capacity() const @nogc @safe pure nothrow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Data size.
|
|
||||||
*/
|
|
||||||
@property size_t length() const @nogc @safe pure nothrow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Available space.
|
|
||||||
*/
|
|
||||||
@property size_t free() const @nogc @safe pure nothrow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(start <= end);
|
|
||||||
assert(end <= length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Self-expanding buffer, that can be used with functions returning the number
|
||||||
* of the read bytes.
|
* of the read bytes.
|
||||||
@ -87,26 +43,30 @@ interface Buffer
|
|||||||
* available data. But only one asynchronous call at a time is supported. Be
|
* 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
|
* sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result
|
||||||
* of the pended asynchronous call.
|
* of the pended asynchronous call.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Buffer type.
|
||||||
*/
|
*/
|
||||||
class ReadBuffer : Buffer
|
struct ReadBuffer(T = ubyte)
|
||||||
|
if (isScalarType!T)
|
||||||
{
|
{
|
||||||
/// Internal buffer.
|
/// Internal buffer.
|
||||||
protected ubyte[] buffer_;
|
private T[] buffer_;
|
||||||
|
|
||||||
/// Filled buffer length.
|
/// Filled buffer length.
|
||||||
protected size_t length_;
|
private size_t length_;
|
||||||
|
|
||||||
/// Start of available data.
|
/// Start of available data.
|
||||||
protected size_t start;
|
private size_t start;
|
||||||
|
|
||||||
/// Last position returned with $(D_KEYWORD []).
|
/// Last position returned with $(D_KEYWORD []).
|
||||||
protected size_t ring;
|
private size_t ring;
|
||||||
|
|
||||||
/// Available space.
|
/// Available space.
|
||||||
protected immutable size_t minAvailable;
|
private immutable size_t minAvailable = 1024;
|
||||||
|
|
||||||
/// Size by which the buffer will grow.
|
/// Size by which the buffer will grow.
|
||||||
protected immutable size_t blockSize;
|
private immutable size_t blockSize = 8192;
|
||||||
|
|
||||||
invariant
|
invariant
|
||||||
{
|
{
|
||||||
@ -123,39 +83,50 @@ class ReadBuffer : Buffer
|
|||||||
* will grow.
|
* will grow.
|
||||||
* minAvailable = minimal size should be always available to fill.
|
* minAvailable = minimal size should be always available to fill.
|
||||||
* So it will reallocate if $(D_INLINECODE
|
* So it will reallocate if $(D_INLINECODE
|
||||||
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)
|
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
|
||||||
* ).
|
* allocator = Allocator.
|
||||||
*/
|
*/
|
||||||
this(size_t size = 8192,
|
this(in size_t size,
|
||||||
size_t minAvailable = 1024)
|
in size_t minAvailable = 1024,
|
||||||
|
shared Allocator allocator = defaultAllocator) @trusted
|
||||||
{
|
{
|
||||||
|
this(allocator);
|
||||||
this.minAvailable = minAvailable;
|
this.minAvailable = minAvailable;
|
||||||
this.blockSize = size;
|
this.blockSize = size;
|
||||||
theAllocator.resizeArray!ubyte(buffer_, size);
|
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator_ is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
allocator_ = allocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deallocates the internal buffer.
|
* Deallocates the internal buffer.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this() @trusted
|
||||||
{
|
{
|
||||||
theAllocator.dispose(buffer_);
|
allocator.deallocate(buffer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!ReadBuffer;
|
ReadBuffer!ubyte b;
|
||||||
assert(b.capacity == 8192);
|
assert(b.capacity == 0);
|
||||||
assert(b.length == 0);
|
assert(b.length == 0);
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: The size of the internal buffer.
|
* Returns: The size of the internal buffer.
|
||||||
*/
|
*/
|
||||||
@property size_t capacity() const @nogc @safe pure nothrow
|
@property size_t capacity() const
|
||||||
{
|
{
|
||||||
return buffer_.length;
|
return buffer_.length;
|
||||||
}
|
}
|
||||||
@ -163,26 +134,28 @@ class ReadBuffer : Buffer
|
|||||||
/**
|
/**
|
||||||
* Returns: Data size.
|
* Returns: Data size.
|
||||||
*/
|
*/
|
||||||
@property size_t length() const @nogc @safe pure nothrow
|
@property size_t length() const
|
||||||
{
|
{
|
||||||
return length_ - start;
|
return length_ - start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
alias opDollar = length;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the buffer.
|
* Clears the buffer.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD this).
|
* Returns: $(D_KEYWORD this).
|
||||||
*/
|
*/
|
||||||
ReadBuffer clear() pure nothrow @safe @nogc
|
void clear()
|
||||||
{
|
{
|
||||||
start = length_ = ring;
|
start = length_ = ring;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Available space.
|
* Returns: Available space.
|
||||||
*/
|
*/
|
||||||
@property size_t free() const pure nothrow @safe @nogc
|
@property size_t free() const
|
||||||
{
|
{
|
||||||
return length > ring ? capacity - length : capacity - ring;
|
return length > ring ? capacity - length : capacity - ring;
|
||||||
}
|
}
|
||||||
@ -190,19 +163,17 @@ class ReadBuffer : Buffer
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!ReadBuffer;
|
ReadBuffer!ubyte b;
|
||||||
size_t numberRead;
|
size_t numberRead;
|
||||||
|
|
||||||
// Fills the buffer with values 0..10
|
assert(b.free == 0);
|
||||||
assert(b.free == b.blockSize);
|
|
||||||
|
|
||||||
|
// Fills the buffer with values 0..10
|
||||||
numberRead = fillBuffer(b[], b.free, 0, 10);
|
numberRead = fillBuffer(b[], b.free, 0, 10);
|
||||||
b += numberRead;
|
b += numberRead;
|
||||||
assert(b.free == b.blockSize - numberRead);
|
assert(b.free == b.blockSize - numberRead);
|
||||||
b.clear();
|
b.clear();
|
||||||
assert(b.free == b.blockSize);
|
assert(b.free == b.blockSize);
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -213,7 +184,7 @@ class ReadBuffer : Buffer
|
|||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD this).
|
* Returns: $(D_KEYWORD this).
|
||||||
*/
|
*/
|
||||||
ReadBuffer opOpAssign(string op)(size_t length)
|
ref ReadBuffer opOpAssign(string op)(in size_t length)
|
||||||
if (op == "+")
|
if (op == "+")
|
||||||
{
|
{
|
||||||
length_ += length;
|
length_ += length;
|
||||||
@ -224,7 +195,7 @@ class ReadBuffer : Buffer
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!ReadBuffer;
|
ReadBuffer!ubyte b;
|
||||||
size_t numberRead;
|
size_t numberRead;
|
||||||
ubyte[] result;
|
ubyte[] result;
|
||||||
|
|
||||||
@ -251,16 +222,6 @@ class ReadBuffer : Buffer
|
|||||||
assert(result[9] == 9);
|
assert(result[9] == 9);
|
||||||
assert(result[10] == 20);
|
assert(result[10] == 20);
|
||||||
assert(result[14] == 24);
|
assert(result[14] == 24);
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Length of available data.
|
|
||||||
*/
|
|
||||||
@property size_t opDollar() const pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -270,7 +231,7 @@ class ReadBuffer : Buffer
|
|||||||
*
|
*
|
||||||
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
|
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
|
||||||
*/
|
*/
|
||||||
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
|
T[] opSlice(in size_t start, in size_t end)
|
||||||
{
|
{
|
||||||
return buffer_[this.start + start .. this.start + end];
|
return buffer_[this.start + start .. this.start + end];
|
||||||
}
|
}
|
||||||
@ -282,7 +243,7 @@ class ReadBuffer : Buffer
|
|||||||
*
|
*
|
||||||
* Returns: A free chunk of the buffer.
|
* Returns: A free chunk of the buffer.
|
||||||
*/
|
*/
|
||||||
ubyte[] opIndex()
|
T[] opIndex()
|
||||||
{
|
{
|
||||||
if (start > 0)
|
if (start > 0)
|
||||||
{
|
{
|
||||||
@ -294,7 +255,12 @@ class ReadBuffer : Buffer
|
|||||||
{
|
{
|
||||||
if (capacity - length < minAvailable)
|
if (capacity - length < minAvailable)
|
||||||
{
|
{
|
||||||
theAllocator.resizeArray!ubyte(buffer_, capacity + blockSize);
|
void[] buf = buffer_;
|
||||||
|
immutable cap = capacity;
|
||||||
|
() @trusted {
|
||||||
|
allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
|
||||||
|
buffer_ = cast(T[]) buf;
|
||||||
|
}();
|
||||||
}
|
}
|
||||||
ring = length_;
|
ring = length_;
|
||||||
return buffer_[length_ .. $];
|
return buffer_[length_ .. $];
|
||||||
@ -304,7 +270,7 @@ class ReadBuffer : Buffer
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!ReadBuffer;
|
ReadBuffer!ubyte b;
|
||||||
size_t numberRead;
|
size_t numberRead;
|
||||||
ubyte[] result;
|
ubyte[] result;
|
||||||
|
|
||||||
@ -318,81 +284,102 @@ class ReadBuffer : Buffer
|
|||||||
assert(result[9] == 9);
|
assert(result[9] == 9);
|
||||||
b.clear();
|
b.clear();
|
||||||
assert(b.length == 0);
|
assert(b.length == 0);
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixin DefaultAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
static assert(is(ReadBuffer!int));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Circular, self-expanding buffer with overflow support. Can be used with
|
* Circular, self-expanding buffer with overflow support. Can be used with
|
||||||
* functions returning returning the number of the transferred bytes.
|
* functions returning the number of the transferred bytes.
|
||||||
*
|
*
|
||||||
* The buffer is optimized for situations where you read all the data from it
|
* 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
|
* at once (without writing to it occasionally). It can become ineffective if
|
||||||
* you permanently keep some data in the buffer and alternate writing and
|
* you permanently keep some data in the buffer and alternate writing and
|
||||||
* reading, because it may allocate and move elements.
|
* reading, because it may allocate and move elements.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Buffer type.
|
||||||
*/
|
*/
|
||||||
class WriteBuffer : Buffer
|
struct WriteBuffer(T = ubyte)
|
||||||
|
if (isScalarType!T)
|
||||||
{
|
{
|
||||||
/// Internal buffer.
|
/// Internal buffer.
|
||||||
protected ubyte[] buffer_;
|
private T[] buffer_;
|
||||||
|
|
||||||
/// Buffer start position.
|
/// Buffer start position.
|
||||||
protected size_t start;
|
private size_t start;
|
||||||
|
|
||||||
/// Buffer ring area size. After this position begins buffer overflow area.
|
/// Buffer ring area size. After this position begins buffer overflow area.
|
||||||
protected size_t ring;
|
private size_t ring;
|
||||||
|
|
||||||
/// Size by which the buffer will grow.
|
/// Size by which the buffer will grow.
|
||||||
protected immutable size_t blockSize;
|
private immutable size_t blockSize;
|
||||||
|
|
||||||
/// The position of the free area in the buffer.
|
/// The position of the free area in the buffer.
|
||||||
protected size_t position;
|
private size_t position;
|
||||||
|
|
||||||
invariant
|
invariant
|
||||||
{
|
{
|
||||||
assert(blockSize > 0);
|
assert(blockSize > 0);
|
||||||
// position can refer to an element outside the buffer if the buffer is full.
|
// Position can refer to an element outside the buffer if the buffer is full.
|
||||||
assert(position <= buffer_.length);
|
assert(position <= buffer_.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* size = Initial buffer size and the size by which the buffer
|
* size = Initial buffer size and the size by which the buffer will
|
||||||
* will grow.
|
* grow.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE size > 0 && allocator !is null)
|
||||||
*/
|
*/
|
||||||
this(size_t size = 8192)
|
this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(size > 0);
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
{
|
{
|
||||||
blockSize = size;
|
blockSize = size;
|
||||||
ring = size - 1;
|
ring = size - 1;
|
||||||
theAllocator.resizeArray!ubyte(buffer_, size);
|
allocator_ = allocator;
|
||||||
|
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@disable this();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deallocates the internal buffer.
|
* Deallocates the internal buffer.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this()
|
||||||
{
|
{
|
||||||
theAllocator.dispose(buffer_);
|
allocator.deallocate(buffer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: The size of the internal buffer.
|
* Returns: The size of the internal buffer.
|
||||||
*/
|
*/
|
||||||
@property size_t capacity() const @nogc @safe pure nothrow
|
@property size_t capacity() const
|
||||||
{
|
{
|
||||||
return buffer_.length;
|
return buffer_.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note that $(D_PSYMBOL length) doesn't return the real length of the data,
|
* 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)
|
* but only the array length that will be returned with $(D_PSYMBOL opIndex)
|
||||||
* next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_KEYWORD +=)
|
* next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=)
|
||||||
* until $(D_PSYMBOL length) returns 0.
|
* until $(D_PSYMBOL length) returns 0.
|
||||||
*
|
*
|
||||||
* Returns: Data size.
|
* Returns: Data size.
|
||||||
*/
|
*/
|
||||||
@property size_t length() const @nogc @safe pure nothrow
|
@property size_t length() const
|
||||||
{
|
{
|
||||||
if (position > ring || position < start) // Buffer overflowed
|
if (position > ring || position < start) // Buffer overflowed
|
||||||
{
|
{
|
||||||
@ -404,18 +391,13 @@ class WriteBuffer : Buffer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Ditto.
|
||||||
* Returns: Length of available data.
|
alias opDollar = length;
|
||||||
*/
|
|
||||||
@property size_t opDollar() const pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!WriteBuffer(4);
|
auto b = WriteBuffer!ubyte(4);
|
||||||
ubyte[3] buf = [48, 23, 255];
|
ubyte[3] buf = [48, 23, 255];
|
||||||
|
|
||||||
b ~= buf;
|
b ~= buf;
|
||||||
@ -432,14 +414,12 @@ class WriteBuffer : Buffer
|
|||||||
assert(b.length == 5);
|
assert(b.length == 5);
|
||||||
b += b.length;
|
b += b.length;
|
||||||
assert(b.length == 0);
|
assert(b.length == 0);
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Available space.
|
* Returns: Available space.
|
||||||
*/
|
*/
|
||||||
@property size_t free() const @nogc @safe pure nothrow
|
@property size_t free() const
|
||||||
{
|
{
|
||||||
return capacity - length;
|
return capacity - length;
|
||||||
}
|
}
|
||||||
@ -448,9 +428,9 @@ class WriteBuffer : Buffer
|
|||||||
* Appends data to the buffer.
|
* Appends data to the buffer.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
|
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
|
||||||
*/
|
*/
|
||||||
WriteBuffer opOpAssign(string op)(ubyte[] buffer)
|
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
|
||||||
if (op == "~")
|
if (op == "~")
|
||||||
{
|
{
|
||||||
size_t end, start;
|
size_t end, start;
|
||||||
@ -496,9 +476,12 @@ class WriteBuffer : Buffer
|
|||||||
end = position + buffer.length - start;
|
end = position + buffer.length - start;
|
||||||
if (end > capacity)
|
if (end > capacity)
|
||||||
{
|
{
|
||||||
auto newSize = end / blockSize * blockSize + blockSize;
|
auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
|
||||||
|
() @trusted {
|
||||||
theAllocator.resizeArray!ubyte(buffer_, newSize);
|
void[] buf = buffer_;
|
||||||
|
allocator.reallocate(buf, newSize);
|
||||||
|
buffer_ = cast(T[]) buf;
|
||||||
|
}();
|
||||||
}
|
}
|
||||||
buffer_[position .. end] = buffer[start .. $];
|
buffer_[position .. end] = buffer[start .. $];
|
||||||
position = end;
|
position = end;
|
||||||
@ -514,7 +497,7 @@ class WriteBuffer : Buffer
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!WriteBuffer(4);
|
auto b = WriteBuffer!ubyte(4);
|
||||||
ubyte[3] buf = [48, 23, 255];
|
ubyte[3] buf = [48, 23, 255];
|
||||||
|
|
||||||
b ~= buf;
|
b ~= buf;
|
||||||
@ -532,31 +515,31 @@ class WriteBuffer : Buffer
|
|||||||
assert(b.capacity == 8);
|
assert(b.capacity == 8);
|
||||||
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
|
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
|
||||||
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
|
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
|
||||||
|
}
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
///
|
||||||
|
unittest
|
||||||
b = make!WriteBuffer(theAllocator, 2);
|
{
|
||||||
|
auto b = WriteBuffer!ubyte(2);
|
||||||
|
ubyte[3] buf = [48, 23, 255];
|
||||||
|
|
||||||
b ~= buf;
|
b ~= buf;
|
||||||
assert(b.start == 0);
|
assert(b.start == 0);
|
||||||
assert(b.capacity == 4);
|
assert(b.capacity == 4);
|
||||||
assert(b.ring == 3);
|
assert(b.ring == 3);
|
||||||
assert(b.position == 3);
|
assert(b.position == 3);
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets how many bytes were written. It will shrink the buffer
|
* Sets how many bytes were written. It will shrink the buffer
|
||||||
* appropriately. Always set this property after calling
|
* appropriately. Always call it after $(D_PSYMBOL opIndex).
|
||||||
* $(D_PSYMBOL buffer).
|
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* length = Length of the written data.
|
* length = Length of the written data.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD this).
|
* Returns: $(D_KEYWORD this).
|
||||||
*/
|
*/
|
||||||
@property WriteBuffer opOpAssign(string op)(size_t length) pure nothrow @safe @nogc
|
ref WriteBuffer opOpAssign(string op)(in size_t length)
|
||||||
if (op == "+")
|
if (op == "+")
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@ -583,9 +566,11 @@ class WriteBuffer : Buffer
|
|||||||
{
|
{
|
||||||
auto overflow = position - afterRing;
|
auto overflow = position - afterRing;
|
||||||
|
|
||||||
if (overflow > length) {
|
if (overflow > length)
|
||||||
buffer_[start.. start + length] = buffer_[afterRing.. afterRing + length];
|
{
|
||||||
buffer_[afterRing.. afterRing + length] = buffer_[afterRing + length ..position];
|
immutable afterLength = afterRing + length;
|
||||||
|
buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
|
||||||
|
buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
|
||||||
position -= length;
|
position -= length;
|
||||||
}
|
}
|
||||||
else if (overflow == length)
|
else if (overflow == length)
|
||||||
@ -620,7 +605,7 @@ class WriteBuffer : Buffer
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!WriteBuffer;
|
auto b = WriteBuffer!ubyte(6);
|
||||||
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
||||||
|
|
||||||
b ~= buf;
|
b ~= buf;
|
||||||
@ -629,8 +614,6 @@ class WriteBuffer : Buffer
|
|||||||
assert(b.length == 4);
|
assert(b.length == 4);
|
||||||
b += 4;
|
b += 4;
|
||||||
assert(b.length == 0);
|
assert(b.length == 0);
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -639,14 +622,14 @@ class WriteBuffer : Buffer
|
|||||||
* After calling it, set $(D_KEYWORD +=) to the length could be
|
* After calling it, set $(D_KEYWORD +=) to the length could be
|
||||||
* written.
|
* written.
|
||||||
*
|
*
|
||||||
* $(D_PSYMBOL buffer) may return only part of the data. You may need
|
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
|
||||||
* to call it (and set $(D_KEYWORD +=) 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,
|
* $(D_PSYMBOL length) is 0. If all the data can be written,
|
||||||
* maximally 3 calls are required.
|
* maximally 3 calls are required.
|
||||||
*
|
*
|
||||||
* Returns: A chunk of data buffer.
|
* Returns: A chunk of data buffer.
|
||||||
*/
|
*/
|
||||||
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
|
T[] opSlice(in size_t start, in size_t end)
|
||||||
{
|
{
|
||||||
immutable internStart = this.start + start;
|
immutable internStart = this.start + start;
|
||||||
|
|
||||||
@ -663,7 +646,7 @@ class WriteBuffer : Buffer
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto b = theAllocator.make!WriteBuffer(6);
|
auto b = WriteBuffer!ubyte(6);
|
||||||
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
||||||
|
|
||||||
b ~= buf;
|
b ~= buf;
|
||||||
@ -678,23 +661,28 @@ class WriteBuffer : Buffer
|
|||||||
|
|
||||||
assert(b[0 .. $] == buf[0 .. 6]);
|
assert(b[0 .. $] == buf[0 .. 6]);
|
||||||
b += b.length;
|
b += b.length;
|
||||||
|
|
||||||
theAllocator.dispose(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After calling it, set $(D_KEYWORD +=) to the length could be
|
* After calling it, set $(D_KEYWORD +=) to the length could be
|
||||||
* written.
|
* written.
|
||||||
*
|
*
|
||||||
* $(D_PSYMBOL buffer) may return only part of the data. You may need
|
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
|
||||||
* to call it (and set $(D_KEYWORD +=) 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,
|
* $(D_PSYMBOL length) is 0. If all the data can be written,
|
||||||
* maximally 3 calls are required.
|
* maximally 3 calls are required.
|
||||||
*
|
*
|
||||||
* Returns: A chunk of data buffer.
|
* Returns: A chunk of data buffer.
|
||||||
*/
|
*/
|
||||||
@property ubyte[] opIndex() pure nothrow @safe @nogc
|
T[] opIndex()
|
||||||
{
|
{
|
||||||
return opSlice(0, length);
|
return opSlice(0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixin DefaultAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
static assert(is(typeof(WriteBuffer!int(5))));
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,21 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Internal package used by containers that rely on entries/nodes.
|
||||||
|
*
|
||||||
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.crypto;
|
module tanya.container.entry;
|
||||||
|
|
||||||
public
|
package struct SEntry(T)
|
||||||
{
|
{
|
||||||
import tanya.crypto.des;
|
/// Item content.
|
||||||
import tanya.crypto.mode;
|
T content;
|
||||||
import tanya.crypto.symmetric;
|
|
||||||
|
/// Next item.
|
||||||
|
SEntry* next;
|
||||||
}
|
}
|
@ -3,34 +3,89 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.container.list;
|
module tanya.container.list;
|
||||||
|
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
import std.algorithm.searching;
|
||||||
|
import std.traits;
|
||||||
|
import tanya.container.entry;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
|
private struct Range(E)
|
||||||
|
if (__traits(isSame, TemplateOf!E, SEntry))
|
||||||
|
{
|
||||||
|
private alias T = typeof(E.content);
|
||||||
|
|
||||||
|
private E* head;
|
||||||
|
|
||||||
|
private this(E* head)
|
||||||
|
{
|
||||||
|
this.head = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property Range save()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property size_t length() const
|
||||||
|
{
|
||||||
|
return count(opIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@property bool empty() const
|
||||||
|
{
|
||||||
|
return head is null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref inout(T) front() inout
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!empty);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return head.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
void popFront()
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!empty);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
head = head.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range opIndex()
|
||||||
|
{
|
||||||
|
return typeof(return)(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
Range!(const E) opIndex() const
|
||||||
|
{
|
||||||
|
return typeof(return)(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singly linked list.
|
* Singly-linked list.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* T = Content type.
|
* T = Content type.
|
||||||
*/
|
*/
|
||||||
class SList(T)
|
struct SList(T)
|
||||||
{
|
{
|
||||||
/**
|
private alias Entry = SEntry!T;
|
||||||
* Creates a new $(D_PSYMBOL SList).
|
|
||||||
*
|
// 0th element of the list.
|
||||||
* Params:
|
private Entry* head;
|
||||||
* allocator = The allocator should be used for the element
|
|
||||||
* allocations.
|
|
||||||
*/
|
|
||||||
this(IAllocator allocator = theAllocator)
|
|
||||||
{
|
|
||||||
this.allocator = allocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all elements from the list.
|
* Removes all elements from the list.
|
||||||
@ -41,27 +96,25 @@ class SList(T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all contents from the $(D_PSYMBOL SList).
|
* Removes all contents from the list.
|
||||||
*/
|
*/
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
while (!empty)
|
while (!empty)
|
||||||
{
|
{
|
||||||
popFront();
|
removeFront();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(theAllocator);
|
SList!int l;
|
||||||
|
|
||||||
l.insertFront(8);
|
l.insertFront(8);
|
||||||
l.insertFront(5);
|
l.insertFront(5);
|
||||||
l.clear();
|
l.clear();
|
||||||
assert(l.empty);
|
assert(l.empty);
|
||||||
|
|
||||||
dispose(theAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,7 +127,7 @@ class SList(T)
|
|||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
return first.next.content;
|
return head.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,13 +136,19 @@ class SList(T)
|
|||||||
* Params:
|
* Params:
|
||||||
* x = New element.
|
* x = New element.
|
||||||
*/
|
*/
|
||||||
void insertFront(T x)
|
void insertFront(ref T x)
|
||||||
{
|
{
|
||||||
Entry* temp = make!Entry(allocator);
|
auto temp = allocator.make!Entry;
|
||||||
|
|
||||||
temp.content = x;
|
temp.content = x;
|
||||||
temp.next = first.next;
|
temp.next = head;
|
||||||
first.next = temp;
|
head = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
void insertFront(T x)
|
||||||
|
{
|
||||||
|
insertFront(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
@ -98,63 +157,126 @@ class SList(T)
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(theAllocator);
|
SList!int l;
|
||||||
|
|
||||||
l.insertFront(8);
|
l.insertFront(8);
|
||||||
assert(l.front == 8);
|
assert(l.front == 8);
|
||||||
l.insertFront(9);
|
l.insertFront(9);
|
||||||
assert(l.front == 9);
|
assert(l.front == 9);
|
||||||
|
}
|
||||||
|
|
||||||
dispose(theAllocator, l);
|
/**
|
||||||
|
* Returns: How many elements are in the list.
|
||||||
|
*/
|
||||||
|
@property size_t length() const
|
||||||
|
{
|
||||||
|
return count(opIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
SList!int l;
|
||||||
|
|
||||||
|
l.insertFront(8);
|
||||||
|
l.insertFront(9);
|
||||||
|
assert(l.length == 2);
|
||||||
|
l.removeFront();
|
||||||
|
assert(l.length == 1);
|
||||||
|
l.removeFront();
|
||||||
|
assert(l.length == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparison for equality.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* that = The list to compare with.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the lists are equal, $(D_KEYWORD false)
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
bool opEquals()(auto ref typeof(this) that) @trusted
|
||||||
|
{
|
||||||
|
return equal(opIndex(), that[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
bool opEquals()(in auto ref typeof(this) that) const @trusted
|
||||||
|
{
|
||||||
|
return equal(opIndex(), that[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
SList!int l1, l2;
|
||||||
|
|
||||||
|
l1.insertFront(8);
|
||||||
|
l1.insertFront(9);
|
||||||
|
l2.insertFront(8);
|
||||||
|
l2.insertFront(10);
|
||||||
|
assert(l1 != l2);
|
||||||
|
|
||||||
|
l1.removeFront();
|
||||||
|
assert(l1 != l2);
|
||||||
|
|
||||||
|
l2.removeFront();
|
||||||
|
assert(l1 == l2);
|
||||||
|
|
||||||
|
l1.removeFront();
|
||||||
|
assert(l1 != l2);
|
||||||
|
|
||||||
|
l2.removeFront();
|
||||||
|
assert(l1 == l2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: $(D_KEYWORD true) if the list is empty.
|
* Returns: $(D_KEYWORD true) if the list is empty.
|
||||||
*/
|
*/
|
||||||
@property bool empty() inout const
|
@property bool empty() const
|
||||||
{
|
{
|
||||||
return first.next is null;
|
return head is null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first element and moves to the next one.
|
* Returns the first element and moves to the next one.
|
||||||
*
|
*
|
||||||
* Returns: The first element.
|
* Returns: The first element.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE !empty)
|
||||||
*/
|
*/
|
||||||
T popFront()
|
void removeFront()
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty);
|
assert(!empty);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
auto n = first.next.next;
|
auto n = head.next;
|
||||||
auto content = first.next.content;
|
|
||||||
|
|
||||||
dispose(allocator, first.next);
|
allocator.dispose(head);
|
||||||
first.next = n;
|
head = n;
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(theAllocator);
|
SList!int l;
|
||||||
|
|
||||||
l.insertFront(8);
|
l.insertFront(8);
|
||||||
l.insertFront(9);
|
l.insertFront(9);
|
||||||
assert(l.front == 9);
|
assert(l.front == 9);
|
||||||
l.popFront();
|
l.removeFront();
|
||||||
assert(l.front == 8);
|
assert(l.front == 8);
|
||||||
|
l.removeFront();
|
||||||
dispose(theAllocator, l);
|
assert(l.empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes $(D_PARAM howMany) elements from the list.
|
* Removes $(D_PARAM howMany) elements from the list.
|
||||||
*
|
*
|
||||||
* Unlike $(D_PSYMBOL popFront()), this method doesn't fail, if it could not
|
* Unlike $(D_PSYMBOL removeFront()), this method doesn't fail, if it could not
|
||||||
* remove $(D_PARAM howMany) elements. Instead, if $(D_PARAM howMany) is
|
* remove $(D_PARAM howMany) elements. Instead, if $(D_PARAM howMany) is
|
||||||
* greater than the list length, all elements are removed.
|
* greater than the list length, all elements are removed.
|
||||||
*
|
*
|
||||||
@ -163,12 +285,17 @@ class SList(T)
|
|||||||
*
|
*
|
||||||
* Returns: The number of elements removed.
|
* Returns: The number of elements removed.
|
||||||
*/
|
*/
|
||||||
size_t removeFront(in size_t howMany = 1)
|
size_t removeFront(in size_t howMany)
|
||||||
|
out (removed)
|
||||||
|
{
|
||||||
|
assert(removed <= howMany);
|
||||||
|
}
|
||||||
|
body
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
for (; i < howMany && !empty; ++i)
|
for (; i < howMany && !empty; ++i)
|
||||||
{
|
{
|
||||||
popFront();
|
removeFront();
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@ -179,7 +306,7 @@ class SList(T)
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(theAllocator);
|
SList!int l;
|
||||||
|
|
||||||
l.insertFront(8);
|
l.insertFront(8);
|
||||||
l.insertFront(5);
|
l.insertFront(5);
|
||||||
@ -188,8 +315,6 @@ class SList(T)
|
|||||||
assert(l.removeFront(2) == 2);
|
assert(l.removeFront(2) == 2);
|
||||||
assert(l.removeFront(3) == 1);
|
assert(l.removeFront(3) == 1);
|
||||||
assert(l.removeFront(3) == 0);
|
assert(l.removeFront(3) == 0);
|
||||||
|
|
||||||
dispose(theAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,13 +322,15 @@ class SList(T)
|
|||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* dg = $(D_KEYWORD foreach) body.
|
* dg = $(D_KEYWORD foreach) body.
|
||||||
|
*
|
||||||
|
* Returns: The value returned from $(D_PARAM dg).
|
||||||
*/
|
*/
|
||||||
int opApply(scope int delegate(ref size_t i, ref T) dg)
|
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
for (auto pos = first.next; pos; pos = pos.next, ++i)
|
for (auto pos = head; pos; pos = pos.next, ++i)
|
||||||
{
|
{
|
||||||
result = dg(i, pos.content);
|
result = dg(i, pos.content);
|
||||||
|
|
||||||
@ -216,11 +343,11 @@ class SList(T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
int opApply(scope int delegate(ref T) dg)
|
int opApply(scope int delegate(ref T) @nogc dg)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
for (auto pos = first.next; pos; pos = pos.next)
|
for (auto pos = head; pos; pos = pos.next)
|
||||||
{
|
{
|
||||||
result = dg(pos.content);
|
result = dg(pos.content);
|
||||||
|
|
||||||
@ -235,7 +362,7 @@ class SList(T)
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(theAllocator);
|
SList!int l;
|
||||||
|
|
||||||
l.insertFront(5);
|
l.insertFront(5);
|
||||||
l.insertFront(4);
|
l.insertFront(4);
|
||||||
@ -246,32 +373,25 @@ class SList(T)
|
|||||||
assert(i != 1 || e == 4);
|
assert(i != 1 || e == 4);
|
||||||
assert(i != 2 || e == 5);
|
assert(i != 2 || e == 5);
|
||||||
}
|
}
|
||||||
dispose(theAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Range!Entry opIndex()
|
||||||
* List entry.
|
|
||||||
*/
|
|
||||||
protected struct Entry
|
|
||||||
{
|
{
|
||||||
/// List item content.
|
return typeof(return)(head);
|
||||||
T content;
|
|
||||||
|
|
||||||
/// Next list item.
|
|
||||||
Entry* next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0th element of the list.
|
Range!(const Entry) opIndex() const
|
||||||
protected Entry first;
|
{
|
||||||
|
return typeof(return)(head);
|
||||||
|
}
|
||||||
|
|
||||||
/// Allocator.
|
mixin DefaultAllocator;
|
||||||
protected IAllocator allocator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto l = make!(SList!int)(theAllocator);
|
SList!int l;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
l.insertFront(5);
|
l.insertFront(5);
|
||||||
@ -285,17 +405,12 @@ unittest
|
|||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
assert(i == 3);
|
assert(i == 3);
|
||||||
|
|
||||||
dispose(theAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private unittest
|
unittest
|
||||||
{
|
{
|
||||||
interface Stuff
|
interface Stuff
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
static assert(is(SList!Stuff));
|
||||||
auto l = make!(SList!Stuff)(theAllocator);
|
|
||||||
|
|
||||||
dispose(theAllocator, l);
|
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.container;
|
module tanya.container;
|
||||||
|
|
||||||
public import tanya.container.bit;
|
|
||||||
public import tanya.container.buffer;
|
public import tanya.container.buffer;
|
||||||
public import tanya.container.list;
|
public import tanya.container.list;
|
||||||
public import tanya.container.vector;
|
public import tanya.container.vector;
|
||||||
|
@ -3,79 +3,96 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.container.queue;
|
module tanya.container.queue;
|
||||||
|
|
||||||
|
import core.exception;
|
||||||
|
import std.traits;
|
||||||
|
import std.algorithm.mutation;
|
||||||
|
import tanya.container.entry;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue.
|
* FIFO queue.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* T = Content type.
|
* T = Content type.
|
||||||
*/
|
*/
|
||||||
class Queue(T)
|
struct Queue(T)
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new $(D_PSYMBOL Queue).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* allocator = The allocator should be used for the element
|
|
||||||
* allocations.
|
|
||||||
*/
|
|
||||||
this(IAllocator allocator = theAllocator)
|
|
||||||
{
|
|
||||||
this.allocator = allocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all elements from the queue.
|
* Removes all elements from the queue.
|
||||||
*/
|
*/
|
||||||
~this()
|
~this()
|
||||||
{
|
{
|
||||||
clear();
|
while (!empty)
|
||||||
|
{
|
||||||
|
dequeue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all elements from the queue.
|
* Returns how many elements are in the queue. It iterates through the queue
|
||||||
|
* to count the elements.
|
||||||
|
*
|
||||||
|
* Returns: How many elements are in the queue.
|
||||||
*/
|
*/
|
||||||
void clear()
|
size_t length() const
|
||||||
{
|
{
|
||||||
while (!empty)
|
size_t len;
|
||||||
|
for (const(SEntry!T)* i = first; i !is null; i = i.next)
|
||||||
{
|
{
|
||||||
popFront();
|
++len;
|
||||||
}
|
}
|
||||||
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = theAllocator.make!(Queue!int);
|
Queue!int q;
|
||||||
|
|
||||||
assert(q.empty);
|
assert(q.length == 0);
|
||||||
q.insertBack(8);
|
q.enqueue(5);
|
||||||
q.insertBack(9);
|
assert(q.length == 1);
|
||||||
q.clear();
|
q.enqueue(4);
|
||||||
assert(q.empty);
|
assert(q.length == 2);
|
||||||
|
q.enqueue(9);
|
||||||
|
assert(q.length == 3);
|
||||||
|
|
||||||
theAllocator.dispose(q);
|
q.dequeue();
|
||||||
|
assert(q.length == 2);
|
||||||
|
q.dequeue();
|
||||||
|
assert(q.length == 1);
|
||||||
|
q.dequeue();
|
||||||
|
assert(q.length == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void enqueueEntry(ref SEntry!T* entry)
|
||||||
* Returns: First element.
|
|
||||||
*/
|
|
||||||
@property ref inout(T) front() inout
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(!empty);
|
if (empty)
|
||||||
|
{
|
||||||
|
first = rear = entry;
|
||||||
}
|
}
|
||||||
body
|
else
|
||||||
{
|
{
|
||||||
return first.next.content;
|
rear.next = entry;
|
||||||
|
rear = rear.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SEntry!T* allocateEntry()
|
||||||
|
{
|
||||||
|
auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
|
||||||
|
if (temp is null)
|
||||||
|
{
|
||||||
|
onOutOfMemoryError();
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,124 +101,125 @@ class Queue(T)
|
|||||||
* Params:
|
* Params:
|
||||||
* x = New element.
|
* x = New element.
|
||||||
*/
|
*/
|
||||||
void insertBack(T x)
|
void enqueue(ref T x)
|
||||||
{
|
{
|
||||||
Entry* temp = make!Entry(allocator);
|
auto temp = allocateEntry();
|
||||||
|
|
||||||
|
*temp = SEntry!T.init;
|
||||||
temp.content = x;
|
temp.content = x;
|
||||||
|
|
||||||
if (empty)
|
enqueueEntry(temp);
|
||||||
{
|
|
||||||
first.next = rear = temp;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rear.next = temp;
|
|
||||||
rear = rear.next;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
alias insert = insertBack;
|
void enqueue(T x)
|
||||||
|
{
|
||||||
|
auto temp = allocateEntry();
|
||||||
|
|
||||||
|
moveEmplace(x, (*temp).content);
|
||||||
|
(*temp).next = null;
|
||||||
|
|
||||||
|
enqueueEntry(temp);
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(theAllocator);
|
Queue!int q;
|
||||||
|
|
||||||
assert(q.empty);
|
assert(q.empty);
|
||||||
q.insertBack(8);
|
q.enqueue(8);
|
||||||
assert(q.front == 8);
|
q.enqueue(9);
|
||||||
q.insertBack(9);
|
assert(q.dequeue() == 8);
|
||||||
assert(q.front == 8);
|
assert(q.dequeue() == 9);
|
||||||
|
|
||||||
dispose(theAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||||
*/
|
*/
|
||||||
@property bool empty() inout const
|
@property bool empty() const
|
||||||
{
|
{
|
||||||
return first.next is null;
|
return first is null;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(theAllocator);
|
Queue!int q;
|
||||||
int value = 7;
|
int value = 7;
|
||||||
|
|
||||||
assert(q.empty);
|
assert(q.empty);
|
||||||
q.insertBack(value);
|
q.enqueue(value);
|
||||||
assert(!q.empty);
|
assert(!q.empty);
|
||||||
|
|
||||||
dispose(theAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the position to the next element.
|
* Move the position to the next element.
|
||||||
|
*
|
||||||
|
* Returns: Dequeued element.
|
||||||
*/
|
*/
|
||||||
void popFront()
|
T dequeue()
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(!empty);
|
assert(!empty);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
auto n = first.next.next;
|
auto n = first.next;
|
||||||
|
T ret = move(first.content);
|
||||||
|
|
||||||
dispose(allocator, first.next);
|
allocator.dispose(first);
|
||||||
first.next = n;
|
first = n;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = make!(Queue!int)(theAllocator);
|
Queue!int q;
|
||||||
|
|
||||||
q.insertBack(8);
|
q.enqueue(8);
|
||||||
q.insertBack(9);
|
q.enqueue(9);
|
||||||
assert(q.front == 8);
|
assert(q.dequeue() == 8);
|
||||||
q.popFront();
|
assert(q.dequeue() == 9);
|
||||||
assert(q.front == 9);
|
|
||||||
|
|
||||||
dispose(theAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* $(D_KEYWORD foreach) iteration.
|
* $(D_KEYWORD foreach) iteration. The elements will be automatically
|
||||||
|
* dequeued.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* dg = $(D_KEYWORD foreach) body.
|
* dg = $(D_KEYWORD foreach) body.
|
||||||
|
*
|
||||||
|
* Returns: The value returned from $(D_PARAM dg).
|
||||||
*/
|
*/
|
||||||
int opApply(scope int delegate(ref size_t i, ref T) dg)
|
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
for (size_t i = 0; !empty; ++i)
|
for (size_t i = 0; !empty; ++i)
|
||||||
{
|
{
|
||||||
if ((result = dg(i, front)) != 0)
|
auto e = dequeue();
|
||||||
|
if ((result = dg(i, e)) != 0)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
popFront();
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
int opApply(scope int delegate(ref T) dg)
|
int opApply(scope int delegate(ref T) @nogc dg)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
while (!empty)
|
while (!empty)
|
||||||
{
|
{
|
||||||
if ((result = dg(front)) != 0)
|
auto e = dequeue();
|
||||||
|
if ((result = dg(e)) != 0)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
popFront();
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -209,12 +227,12 @@ class Queue(T)
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = theAllocator.make!(Queue!int);
|
Queue!int q;
|
||||||
|
|
||||||
size_t j;
|
size_t j;
|
||||||
q.insertBack(5);
|
q.enqueue(5);
|
||||||
q.insertBack(4);
|
q.enqueue(4);
|
||||||
q.insertBack(9);
|
q.enqueue(9);
|
||||||
foreach (i, e; q)
|
foreach (i, e; q)
|
||||||
{
|
{
|
||||||
assert(i != 2 || e == 9);
|
assert(i != 2 || e == 9);
|
||||||
@ -226,9 +244,9 @@ class Queue(T)
|
|||||||
assert(q.empty);
|
assert(q.empty);
|
||||||
|
|
||||||
j = 0;
|
j = 0;
|
||||||
q.insertBack(5);
|
q.enqueue(5);
|
||||||
q.insertBack(4);
|
q.enqueue(4);
|
||||||
q.insertBack(9);
|
q.enqueue(9);
|
||||||
foreach (e; q)
|
foreach (e; q)
|
||||||
{
|
{
|
||||||
assert(j != 2 || e == 9);
|
assert(j != 2 || e == 9);
|
||||||
@ -238,48 +256,26 @@ class Queue(T)
|
|||||||
}
|
}
|
||||||
assert(j == 3);
|
assert(j == 3);
|
||||||
assert(q.empty);
|
assert(q.empty);
|
||||||
|
|
||||||
dispose(theAllocator, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private SEntry!T* first;
|
||||||
* Queue entry.
|
private SEntry!T* rear;
|
||||||
*/
|
|
||||||
protected struct Entry
|
|
||||||
{
|
|
||||||
/// Queue item content.
|
|
||||||
T content;
|
|
||||||
|
|
||||||
/// Next list item.
|
mixin DefaultAllocator;
|
||||||
Entry* next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The first element of the list.
|
|
||||||
protected Entry first;
|
|
||||||
|
|
||||||
/// The last element of the list.
|
|
||||||
protected Entry* rear;
|
|
||||||
|
|
||||||
/// The allocator.
|
|
||||||
protected IAllocator allocator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto q = theAllocator.make!(Queue!int);
|
Queue!int q;
|
||||||
|
|
||||||
q.insertBack(5);
|
q.enqueue(5);
|
||||||
assert(!q.empty);
|
assert(!q.empty);
|
||||||
|
|
||||||
q.insertBack(4);
|
q.enqueue(4);
|
||||||
assert(q.front == 5);
|
q.enqueue(9);
|
||||||
|
|
||||||
q.insertBack(9);
|
assert(q.dequeue() == 5);
|
||||||
assert(q.front == 5);
|
|
||||||
|
|
||||||
q.popFront();
|
|
||||||
assert(q.front == 4);
|
|
||||||
|
|
||||||
foreach (i, ref e; q)
|
foreach (i, ref e; q)
|
||||||
{
|
{
|
||||||
@ -287,6 +283,4 @@ unittest
|
|||||||
assert(i != 1 || e == 9);
|
assert(i != 1 || e == 9);
|
||||||
}
|
}
|
||||||
assert(q.empty);
|
assert(q.empty);
|
||||||
|
|
||||||
theAllocator.dispose(q);
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,607 +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.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]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
IAllocator allocator = theAllocator)
|
|
||||||
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 = theAllocator.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);
|
|
||||||
|
|
||||||
theAllocator.dispose(input);
|
|
||||||
}
|
|
||||||
{ // PKCS#7
|
|
||||||
auto input = theAllocator.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
theAllocator.dispose(input);
|
|
||||||
}
|
|
||||||
{ // ANSI X.923
|
|
||||||
auto input = theAllocator.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
theAllocator.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,
|
|
||||||
IAllocator allocator = theAllocator)
|
|
||||||
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 = theAllocator.makeArray!ubyte(50);
|
|
||||||
auto inputDup = theAllocator.makeArray!ubyte(50);
|
|
||||||
|
|
||||||
pad(input, PaddingMode.zero, 64);
|
|
||||||
pad(inputDup, PaddingMode.zero, 64);
|
|
||||||
|
|
||||||
unpad(input, PaddingMode.zero, 64);
|
|
||||||
assert(input == inputDup);
|
|
||||||
|
|
||||||
theAllocator.dispose(input);
|
|
||||||
theAllocator.dispose(inputDup);
|
|
||||||
|
|
||||||
}
|
|
||||||
{ // PKCS#7
|
|
||||||
auto input = theAllocator.makeArray!ubyte(50);
|
|
||||||
auto inputDup = theAllocator.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);
|
|
||||||
|
|
||||||
theAllocator.dispose(input);
|
|
||||||
theAllocator.dispose(inputDup);
|
|
||||||
}
|
|
||||||
{ // ANSI X.923
|
|
||||||
auto input = theAllocator.makeArray!ubyte(50);
|
|
||||||
auto inputDup = theAllocator.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);
|
|
||||||
|
|
||||||
theAllocator.dispose(input);
|
|
||||||
theAllocator.dispose(inputDup);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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");
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,9 @@
|
|||||||
*/
|
*/
|
||||||
module tanya.math;
|
module tanya.math;
|
||||||
|
|
||||||
|
import std.traits;
|
||||||
public import tanya.math.mp;
|
public import tanya.math.mp;
|
||||||
|
public import tanya.math.random;
|
||||||
|
|
||||||
version (unittest)
|
version (unittest)
|
||||||
{
|
{
|
||||||
@ -20,26 +22,32 @@ version (unittest)
|
|||||||
/**
|
/**
|
||||||
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
|
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
|
||||||
*
|
*
|
||||||
|
* If $(D_PARAM I) is an $(D_PSYMBOL Integer), the allocator of $(D_PARAM x)
|
||||||
|
* is used to allocate the result.
|
||||||
|
*
|
||||||
* Params:
|
* Params:
|
||||||
|
* I = Base type.
|
||||||
|
* G = Exponent type.
|
||||||
|
* H = Divisor type:
|
||||||
* x = Base.
|
* x = Base.
|
||||||
* y = Exponent.
|
* y = Exponent.
|
||||||
* z = Divisor.
|
* z = Divisor.
|
||||||
*
|
*
|
||||||
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
|
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
|
||||||
* by $(D_PARAM z).
|
* by $(D_PARAM z).
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE z > 0)
|
||||||
*/
|
*/
|
||||||
ulong pow(ulong x, ulong y, ulong z) nothrow pure @safe @nogc
|
H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z)
|
||||||
|
if (isIntegral!I && isIntegral!G && isIntegral!H)
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(z > 0);
|
assert(z > 0, "Division by zero.");
|
||||||
}
|
|
||||||
out (result)
|
|
||||||
{
|
|
||||||
assert(result >= 0);
|
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
ulong mask = ulong.max / 2 + 1, result;
|
G mask = G.max / 2 + 1;
|
||||||
|
H result;
|
||||||
|
|
||||||
if (y == 0)
|
if (y == 0)
|
||||||
{
|
{
|
||||||
@ -51,7 +59,7 @@ body
|
|||||||
}
|
}
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
auto bit = y & mask;
|
immutable bit = y & mask;
|
||||||
if (!result && bit)
|
if (!result && bit)
|
||||||
{
|
{
|
||||||
result = x;
|
result = x;
|
||||||
@ -64,15 +72,53 @@ body
|
|||||||
result *= x;
|
result *= x;
|
||||||
}
|
}
|
||||||
result %= z;
|
result %= z;
|
||||||
|
|
||||||
}
|
}
|
||||||
while (mask >>= 1);
|
while (mask >>= 1);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z)
|
||||||
|
if (is(I == Integer))
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(z.length > 0, "Division by zero.");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
size_t i = y.length;
|
||||||
|
auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator);
|
||||||
|
Integer result = Integer(x.allocator);
|
||||||
|
|
||||||
|
if (x.length == 0 && i != 0)
|
||||||
|
{
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
while (i)
|
||||||
|
{
|
||||||
|
--i;
|
||||||
|
for (ubyte mask = 0x01; mask; mask <<= 1)
|
||||||
|
{
|
||||||
|
if (y.rep[i] & mask)
|
||||||
|
{
|
||||||
|
result *= tmp1;
|
||||||
|
result %= z;
|
||||||
|
}
|
||||||
|
tmp2 = tmp1;
|
||||||
|
tmp1 *= tmp2;
|
||||||
|
tmp1 %= z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
unittest
|
pure nothrow @safe @nogc unittest
|
||||||
{
|
{
|
||||||
assert(pow(3, 5, 7) == 5);
|
assert(pow(3, 5, 7) == 5);
|
||||||
assert(pow(2, 2, 1) == 0);
|
assert(pow(2, 2, 1) == 0);
|
||||||
@ -85,6 +131,20 @@ unittest
|
|||||||
assert(pow(0, 5, 5) == 0);
|
assert(pow(0, 5, 5) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert(cast(long) pow(Integer(3), Integer(5), Integer(7)) == 5);
|
||||||
|
assert(cast(long) pow(Integer(2), Integer(2), Integer(1)) == 0);
|
||||||
|
assert(cast(long) pow(Integer(3), Integer(3), Integer(3)) == 0);
|
||||||
|
assert(cast(long) pow(Integer(7), Integer(4), Integer(2)) == 1);
|
||||||
|
assert(cast(long) pow(Integer(53), Integer(0), Integer(2)) == 1);
|
||||||
|
assert(cast(long) pow(Integer(53), Integer(1), Integer(3)) == 2);
|
||||||
|
assert(cast(long) pow(Integer(53), Integer(2), Integer(5)) == 4);
|
||||||
|
assert(cast(long) pow(Integer(0), Integer(0), Integer(5)) == 1);
|
||||||
|
assert(cast(long) pow(Integer(0), Integer(5), Integer(5)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if $(D_PARAM x) is a prime.
|
* Checks if $(D_PARAM x) is a prime.
|
||||||
*
|
*
|
||||||
|
@ -3,16 +3,18 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Random number generator.
|
||||||
|
*
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.random;
|
module tanya.math.random;
|
||||||
|
|
||||||
import tanya.memory;
|
|
||||||
import std.digest.sha;
|
import std.digest.sha;
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
|
import tanya.memory;
|
||||||
|
|
||||||
/// Block size of entropy accumulator (SHA-512).
|
/// Block size of entropy accumulator (SHA-512).
|
||||||
enum blockSize = 64;
|
enum blockSize = 64;
|
||||||
@ -148,13 +150,13 @@ version (linux)
|
|||||||
/**
|
/**
|
||||||
* Pseudorandom number generator.
|
* Pseudorandom number generator.
|
||||||
* ---
|
* ---
|
||||||
* auto entropy = theAllocator.make!Entropy;
|
* auto entropy = defaultAllocator.make!Entropy();
|
||||||
*
|
*
|
||||||
* ubyte[blockSize] output;
|
* ubyte[blockSize] output;
|
||||||
*
|
*
|
||||||
* output = entropy.random;
|
* output = entropy.random;
|
||||||
*
|
*
|
||||||
* theAllocator.finalize(entropy);
|
* defaultAllocator.finalize(entropy);
|
||||||
* ---
|
* ---
|
||||||
*/
|
*/
|
||||||
class Entropy
|
class Entropy
|
||||||
@ -164,7 +166,7 @@ class Entropy
|
|||||||
|
|
||||||
private ubyte sourceCount_;
|
private ubyte sourceCount_;
|
||||||
|
|
||||||
private IAllocator allocator;
|
private shared Allocator allocator;
|
||||||
|
|
||||||
/// Entropy accumulator.
|
/// Entropy accumulator.
|
||||||
protected SHA!(maxGather * 8, 512) accumulator;
|
protected SHA!(maxGather * 8, 512) accumulator;
|
||||||
@ -175,7 +177,7 @@ class Entropy
|
|||||||
* allocator = Allocator to allocate entropy sources available on the
|
* allocator = Allocator to allocate entropy sources available on the
|
||||||
* system.
|
* system.
|
||||||
*/
|
*/
|
||||||
this(size_t maxSources = 20, IAllocator allocator = theAllocator)
|
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(maxSources > 0 && maxSources <= ubyte.max);
|
assert(maxSources > 0 && maxSources <= ubyte.max);
|
@ -3,180 +3,65 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.memory.allocator;
|
module tanya.memory.allocator;
|
||||||
|
|
||||||
import std.experimental.allocator;
|
|
||||||
import std.traits;
|
|
||||||
import std.typecons;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class implementing a basic allocator.
|
* Abstract class implementing a basic allocator.
|
||||||
*/
|
*/
|
||||||
abstract class Allocator : IAllocator
|
interface Allocator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Not supported.
|
* Returns: Alignment offered.
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD false).
|
|
||||||
*/
|
*/
|
||||||
bool deallocateAll() const @nogc @safe pure nothrow
|
@property uint alignment() const shared pure nothrow @safe @nogc;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not supported.
|
* Allocates $(D_PARAM size) bytes of memory.
|
||||||
*
|
*
|
||||||
* Returns $(D_PSYMBOL Ternary.unknown).
|
|
||||||
*/
|
|
||||||
Ternary empty() const @nogc @safe pure nothrow
|
|
||||||
{
|
|
||||||
return Ternary.unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* b = Memory block.
|
|
||||||
*
|
|
||||||
* Returns: $(D_PSYMBOL Ternary.unknown).
|
|
||||||
*/
|
|
||||||
Ternary owns(void[] b) const @nogc @safe pure nothrow
|
|
||||||
{
|
|
||||||
return Ternary.unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* p = Pointer to a memory block.
|
|
||||||
* result = Full block allocated.
|
|
||||||
*
|
|
||||||
* Returns: $(D_PSYMBOL Ternary.unknown).
|
|
||||||
*/
|
|
||||||
Ternary resolveInternalPointer(void* p, ref void[] result)
|
|
||||||
const @nogc @safe pure nothrow
|
|
||||||
{
|
|
||||||
return Ternary.unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
* Params:
|
||||||
* size = Amount of memory to allocate.
|
* size = Amount of memory to allocate.
|
||||||
*
|
*
|
||||||
* Returns: The good allocation size that guarantees zero internal
|
* Returns: Pointer to the new allocated memory.
|
||||||
* fragmentation.
|
|
||||||
*/
|
*/
|
||||||
size_t goodAllocSize(size_t s)
|
void[] allocate(in size_t size) shared nothrow @nogc;
|
||||||
{
|
|
||||||
auto rem = s % alignment;
|
|
||||||
return rem ? s + alignment - rem : s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not supported.
|
* Deallocates a memory block.
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD null).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void[] allocateAll() const @nogc @safe pure nothrow
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* b = Block to be expanded.
|
* p = A pointer to the memory block to be freed.
|
||||||
* s = New size.
|
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD false).
|
* Returns: Whether the deallocation was successful.
|
||||||
*/
|
*/
|
||||||
bool expand(ref void[] b, size_t s) const @nogc @safe pure nothrow
|
bool deallocate(void[] p) shared nothrow @nogc;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not supported.
|
* Increases or decreases the size of a memory block.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* n = Amount of memory to allocate.
|
* p = A pointer to the memory block.
|
||||||
* a = Alignment.
|
* size = Size of the reallocated block.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD null).
|
* Returns: Pointer to the allocated memory.
|
||||||
*/
|
*/
|
||||||
void[] alignedAllocate(size_t n, uint a) const @nogc @safe pure nothrow
|
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not supported.
|
* Reallocates a memory block in place if possible or returns
|
||||||
|
* $(D_KEYWORD false). This function cannot be used to allocate or
|
||||||
|
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
|
||||||
|
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* n = Amount of memory to allocate.
|
* p = A pointer to the memory block.
|
||||||
* a = Alignment.
|
* size = Size of the reallocated block.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD false).
|
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||||
*/
|
*/
|
||||||
bool alignedReallocate(ref void[] b, size_t size, uint alignment)
|
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||||
const @nogc @safe pure nothrow
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)(IAllocator 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;
|
|
||||||
|
|
||||||
theAllocator.resizeArray(p, 20);
|
|
||||||
assert(p.length == 20);
|
|
||||||
|
|
||||||
theAllocator.resizeArray(p, 30);
|
|
||||||
assert(p.length == 30);
|
|
||||||
|
|
||||||
theAllocator.resizeArray(p, 10);
|
|
||||||
assert(p.length == 10);
|
|
||||||
|
|
||||||
theAllocator.resizeArray(p, 0);
|
|
||||||
assert(p is null);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|
|
||||||
|| hasElaborateDestructor!T || isDynamicArray!T;
|
|
||||||
|
@ -3,16 +3,16 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.memory.mmappool;
|
module tanya.memory.mmappool;
|
||||||
|
|
||||||
|
import core.stdc.string;
|
||||||
|
import std.algorithm.comparison;
|
||||||
import tanya.memory.allocator;
|
import tanya.memory.allocator;
|
||||||
import core.atomic;
|
|
||||||
import core.exception;
|
|
||||||
|
|
||||||
version (Posix)
|
version (Posix)
|
||||||
{
|
{
|
||||||
@ -27,7 +27,7 @@ else version (Windows)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This allocator allocates memory in regions (multiple of 4 KB for example).
|
* This allocator allocates memory in regions (multiple of 64 KB for example).
|
||||||
* Each region is then splitted in blocks. So it doesn't request the memory
|
* 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
|
* from the operating system on each call, but only if there are no large
|
||||||
* enough free blocks in the available regions.
|
* enough free blocks in the available regions.
|
||||||
@ -36,7 +36,7 @@ else version (Windows)
|
|||||||
* block as free and only if all blocks in the region are free, the complete
|
* block as free and only if all blocks in the region are free, the complete
|
||||||
* region is deallocated.
|
* region is deallocated.
|
||||||
*
|
*
|
||||||
* ----------------------------------------------------------------------------
|
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
* | | | | | || | | |
|
* | | | | | || | | |
|
||||||
* | |prev <----------- | || | | |
|
* | |prev <----------- | || | | |
|
||||||
* | R | B | | B | || R | B | |
|
* | R | B | | B | || R | B | |
|
||||||
@ -46,32 +46,22 @@ else version (Windows)
|
|||||||
* | O | K | | K | prev O | K | |
|
* | O | K | | K | prev O | K | |
|
||||||
* | N | -----------> next| || N | | |
|
* | 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
|
final class MmapPool : Allocator
|
||||||
{
|
{
|
||||||
@disable this();
|
invariant
|
||||||
|
|
||||||
shared static this()
|
|
||||||
{
|
{
|
||||||
version (Posix)
|
for (auto r = &head; *r !is null; r = &((*r).next))
|
||||||
{
|
{
|
||||||
pageSize = sysconf(_SC_PAGE_SIZE);
|
auto block = cast(Block) (cast(void*) *r + RegionEntry.sizeof);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
assert(block.prev is null || block.prev.next is block);
|
||||||
|
assert(block.next is null || block.next.prev is block);
|
||||||
|
assert(block.region is *r);
|
||||||
}
|
}
|
||||||
else version (Windows)
|
while ((block = block.next) !is null);
|
||||||
{
|
|
||||||
SYSTEM_INFO si;
|
|
||||||
GetSystemInfo(&si);
|
|
||||||
pageSize = si.dwPageSize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +71,9 @@ class MmapPool : Allocator
|
|||||||
* Params:
|
* Params:
|
||||||
* size = Amount of memory to allocate.
|
* size = Amount of memory to allocate.
|
||||||
*
|
*
|
||||||
* Returns: The pointer to the new allocated memory.
|
* Returns: Pointer to the new allocated memory.
|
||||||
*/
|
*/
|
||||||
void[] allocate(size_t size, TypeInfo ti = null) @nogc @trusted nothrow
|
void[] allocate(in size_t size) shared nothrow @nogc
|
||||||
{
|
{
|
||||||
if (!size)
|
if (!size)
|
||||||
{
|
{
|
||||||
@ -101,7 +91,7 @@ class MmapPool : Allocator
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@nogc @safe nothrow unittest
|
nothrow unittest
|
||||||
{
|
{
|
||||||
auto p = MmapPool.instance.allocate(20);
|
auto p = MmapPool.instance.allocate(20);
|
||||||
|
|
||||||
@ -115,16 +105,16 @@ class MmapPool : Allocator
|
|||||||
* into two blocks if the block is too large.
|
* into two blocks if the block is too large.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* size = Minimum size the block should have.
|
* size = Minimum size the block should have (aligned).
|
||||||
*
|
*
|
||||||
* Returns: Data the block points to or $(D_KEYWORD null).
|
* Returns: Data the block points to or $(D_KEYWORD null).
|
||||||
*/
|
*/
|
||||||
private void* findBlock(size_t size) @nogc nothrow
|
private void* findBlock(in ref size_t size) shared nothrow @nogc
|
||||||
{
|
{
|
||||||
Block block1;
|
Block block1;
|
||||||
RegionLoop: for (auto r = head; r !is null; r = r.next)
|
RegionLoop: for (auto r = head; r !is null; r = r.next)
|
||||||
{
|
{
|
||||||
block1 = cast(Block) (cast(void*) r + regionEntrySize);
|
block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (block1.free && block1.size >= size)
|
if (block1.free && block1.size >= size)
|
||||||
@ -138,35 +128,37 @@ class MmapPool : Allocator
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else if (block1.size >= size + alignment + blockEntrySize)
|
else if (block1.size >= size + alignment_ + BlockEntry.sizeof)
|
||||||
{ // Split the block if needed
|
{ // Split the block if needed
|
||||||
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
|
Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size);
|
||||||
block2.prev = block1;
|
block2.prev = block1;
|
||||||
if (block1.next is null)
|
block2.next = block1.next;
|
||||||
|
block2.free = true;
|
||||||
|
block2.size = block1.size - BlockEntry.sizeof - size;
|
||||||
|
block2.region = block1.region;
|
||||||
|
|
||||||
|
if (block1.next !is null)
|
||||||
{
|
{
|
||||||
block2.next = null;
|
block1.next.prev = block2;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
block2.next = block1.next.next;
|
|
||||||
}
|
}
|
||||||
block1.next = block2;
|
block1.next = block2;
|
||||||
|
|
||||||
block1.free = false;
|
|
||||||
block2.free = true;
|
|
||||||
|
|
||||||
block2.size = block1.size - blockEntrySize - size;
|
|
||||||
block1.size = size;
|
block1.size = size;
|
||||||
|
|
||||||
block2.region = block1.region;
|
|
||||||
atomicOp!"+="(block1.region.blocks, 1);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
block1.free = false;
|
block1.free = false;
|
||||||
atomicOp!"+="(block1.region.blocks, 1);
|
block1.region.blocks = block1.region.blocks + 1;
|
||||||
|
|
||||||
|
return cast(void*) block1 + BlockEntry.sizeof;
|
||||||
}
|
}
|
||||||
return cast(void*) block1 + blockEntrySize;
|
|
||||||
|
// Merge block with the next one.
|
||||||
|
private void mergeNext(Block block) shared const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
block.size = block.size + BlockEntry.sizeof + block.next.size;
|
||||||
|
if (block.next.next !is null)
|
||||||
|
{
|
||||||
|
block.next.next.prev = block;
|
||||||
|
}
|
||||||
|
block.next = block.next.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -177,14 +169,14 @@ class MmapPool : Allocator
|
|||||||
*
|
*
|
||||||
* Returns: Whether the deallocation was successful.
|
* Returns: Whether the deallocation was successful.
|
||||||
*/
|
*/
|
||||||
bool deallocate(void[] p) @nogc @trusted nothrow
|
bool deallocate(void[] p) shared nothrow @nogc
|
||||||
{
|
{
|
||||||
if (p is null)
|
if (p.ptr is null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Block block = cast(Block) (p.ptr - blockEntrySize);
|
Block block = cast(Block) (p.ptr - BlockEntry.sizeof);
|
||||||
if (block.region.blocks <= 1)
|
if (block.region.blocks <= 1)
|
||||||
{
|
{
|
||||||
if (block.region.prev !is null)
|
if (block.region.prev !is null)
|
||||||
@ -208,22 +200,130 @@ class MmapPool : Allocator
|
|||||||
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
|
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Merge blocks if neigbours are free.
|
||||||
|
if (block.next !is null && block.next.free)
|
||||||
|
{
|
||||||
|
mergeNext(block);
|
||||||
|
}
|
||||||
|
if (block.prev !is null && block.prev.free)
|
||||||
|
{
|
||||||
|
block.prev.size = block.prev.size + BlockEntry.sizeof + block.size;
|
||||||
|
if (block.next !is null)
|
||||||
|
{
|
||||||
|
block.next.prev = block.prev;
|
||||||
|
}
|
||||||
|
block.prev.next = block.next;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
block.free = true;
|
block.free = true;
|
||||||
atomicOp!"-="(block.region.blocks, 1);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
block.region.blocks = block.region.blocks - 1;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@nogc @safe nothrow unittest
|
nothrow unittest
|
||||||
{
|
{
|
||||||
auto p = MmapPool.instance.allocate(20);
|
auto p = MmapPool.instance.allocate(20);
|
||||||
|
|
||||||
assert(MmapPool.instance.deallocate(p));
|
assert(MmapPool.instance.deallocate(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reallocates a memory block in place if possible or returns
|
||||||
|
* $(D_KEYWORD false). This function cannot be used to allocate or
|
||||||
|
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
|
||||||
|
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* p = A pointer to the memory block.
|
||||||
|
* size = Size of the reallocated block.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc
|
||||||
|
{
|
||||||
|
if (p is null || size == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (size <= p.length)
|
||||||
|
{
|
||||||
|
// Leave the block as is.
|
||||||
|
p = p.ptr[0 .. size];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof);
|
||||||
|
|
||||||
|
if (block1.size >= size)
|
||||||
|
{
|
||||||
|
// Enough space in the current block. Can happen because of the alignment.
|
||||||
|
p = p.ptr[0 .. size];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
immutable dataSize = addAlignment(size);
|
||||||
|
immutable delta = dataSize - addAlignment(p.length);
|
||||||
|
|
||||||
|
if (block1.next is null
|
||||||
|
|| !block1.next.free
|
||||||
|
|| block1.next.size + BlockEntry.sizeof < delta)
|
||||||
|
{
|
||||||
|
// It is the last block in the region or the next block is too small or not
|
||||||
|
// free.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (block1.next.size >= delta + alignment_)
|
||||||
|
{
|
||||||
|
// Move size from block2 to block1.
|
||||||
|
block1.next.size = block1.next.size - delta;
|
||||||
|
block1.size = block1.size + delta;
|
||||||
|
|
||||||
|
auto block2 = cast(Block) (p.ptr + dataSize);
|
||||||
|
if (block1.next.next !is null)
|
||||||
|
{
|
||||||
|
block1.next.next.prev = block2;
|
||||||
|
}
|
||||||
|
// block1.next and block2 can overlap.
|
||||||
|
memmove(cast(void*) block2, cast(void*) block1.next, BlockEntry.sizeof);
|
||||||
|
block1.next = block2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The next block has enough space, but is too small for further
|
||||||
|
// allocations. Merge it with the current block.
|
||||||
|
mergeNext(block1);
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p.ptr[0 .. size];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow unittest
|
||||||
|
{
|
||||||
|
void[] p;
|
||||||
|
assert(!MmapPool.instance.reallocateInPlace(p, 5));
|
||||||
|
assert(p is null);
|
||||||
|
|
||||||
|
p = MmapPool.instance.allocate(1);
|
||||||
|
auto orig = p.ptr;
|
||||||
|
|
||||||
|
assert(MmapPool.instance.reallocateInPlace(p, 2));
|
||||||
|
assert(p.length == 2);
|
||||||
|
assert(p.ptr == orig);
|
||||||
|
|
||||||
|
assert(MmapPool.instance.reallocateInPlace(p, 4));
|
||||||
|
assert(p.length == 4);
|
||||||
|
assert(p.ptr == orig);
|
||||||
|
|
||||||
|
assert(MmapPool.instance.reallocateInPlace(p, 2));
|
||||||
|
assert(p.length == 2);
|
||||||
|
assert(p.ptr == orig);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(p);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases or decreases the size of a memory block.
|
* Increases or decreases the size of a memory block.
|
||||||
*
|
*
|
||||||
@ -233,33 +333,31 @@ class MmapPool : Allocator
|
|||||||
*
|
*
|
||||||
* Returns: Whether the reallocation was successful.
|
* Returns: Whether the reallocation was successful.
|
||||||
*/
|
*/
|
||||||
bool reallocate(ref void[] p, size_t size) @nogc @trusted nothrow
|
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc
|
||||||
{
|
{
|
||||||
void[] reallocP;
|
if (size == 0)
|
||||||
|
{
|
||||||
if (size == p.length)
|
if (deallocate(p))
|
||||||
|
{
|
||||||
|
p = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (reallocateInPlace(p, size))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (size > 0)
|
// Can't reallocate in place, allocate a new block,
|
||||||
{
|
// copy and delete the previous one.
|
||||||
reallocP = allocate(size);
|
void[] reallocP = allocate(size);
|
||||||
if (reallocP is null)
|
if (reallocP is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (p !is null)
|
if (p !is null)
|
||||||
{
|
{
|
||||||
if (size > p.length)
|
memcpy(reallocP.ptr, p.ptr, min(p.length, size));
|
||||||
{
|
|
||||||
reallocP[0..p.length] = p[0..$];
|
|
||||||
}
|
|
||||||
else if (size > 0)
|
|
||||||
{
|
|
||||||
reallocP[0..size] = p[0..size];
|
|
||||||
}
|
|
||||||
deallocate(p);
|
deallocate(p);
|
||||||
}
|
}
|
||||||
p = reallocP;
|
p = reallocP;
|
||||||
@ -268,7 +366,7 @@ class MmapPool : Allocator
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@nogc nothrow unittest
|
nothrow unittest
|
||||||
{
|
{
|
||||||
void[] p;
|
void[] p;
|
||||||
MmapPool.instance.reallocate(p, 10 * int.sizeof);
|
MmapPool.instance.reallocate(p, 10 * int.sizeof);
|
||||||
@ -301,18 +399,34 @@ class MmapPool : Allocator
|
|||||||
*
|
*
|
||||||
* Returns: Global $(D_PSYMBOL MmapPool) instance.
|
* Returns: Global $(D_PSYMBOL MmapPool) instance.
|
||||||
*/
|
*/
|
||||||
static @property ref MmapPool instance() @nogc @trusted nothrow
|
static @property ref shared(MmapPool) instance() nothrow @nogc
|
||||||
{
|
{
|
||||||
if (instance_ is null)
|
if (instance_ is null)
|
||||||
{
|
{
|
||||||
|
// Get system dependend page size.
|
||||||
|
version (Posix)
|
||||||
|
{
|
||||||
|
pageSize = sysconf(_SC_PAGE_SIZE);
|
||||||
|
if (pageSize < 65536)
|
||||||
|
{
|
||||||
|
pageSize = pageSize * 65536 / pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else version (Windows)
|
||||||
|
{
|
||||||
|
SYSTEM_INFO si;
|
||||||
|
GetSystemInfo(&si);
|
||||||
|
pageSize = si.dwPageSize;
|
||||||
|
}
|
||||||
|
|
||||||
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
|
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
|
||||||
|
|
||||||
Region head; // Will become soon our region list head
|
Region head; // Will become soon our region list head
|
||||||
void* data = initializeRegion(instanceSize, head);
|
void* data = initializeRegion(instanceSize, head);
|
||||||
if (data !is null)
|
if (data !is null)
|
||||||
{
|
{
|
||||||
data[0..instanceSize] = typeid(MmapPool).initializer[];
|
memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize);
|
||||||
instance_ = cast(MmapPool) data;
|
instance_ = cast(shared MmapPool) data;
|
||||||
instance_.head = head;
|
instance_.head = head;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,12 +434,12 @@ class MmapPool : Allocator
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@nogc @safe nothrow unittest
|
nothrow unittest
|
||||||
{
|
{
|
||||||
assert(instance is instance);
|
assert(instance is instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Initializes a region for one element.
|
* Initializes a region for one element.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
@ -334,8 +448,8 @@ class MmapPool : Allocator
|
|||||||
*
|
*
|
||||||
* Returns: A pointer to the data.
|
* Returns: A pointer to the data.
|
||||||
*/
|
*/
|
||||||
private static void* initializeRegion(size_t size,
|
private static void* initializeRegion(size_t size, ref Region head)
|
||||||
ref Region head) @nogc nothrow
|
nothrow @nogc
|
||||||
{
|
{
|
||||||
immutable regionSize = calculateRegionSize(size);
|
immutable regionSize = calculateRegionSize(size);
|
||||||
|
|
||||||
@ -349,10 +463,6 @@ class MmapPool : Allocator
|
|||||||
0);
|
0);
|
||||||
if (p is MAP_FAILED)
|
if (p is MAP_FAILED)
|
||||||
{
|
{
|
||||||
if (errno == ENOMEM)
|
|
||||||
{
|
|
||||||
onOutOfMemoryError();
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -364,10 +474,6 @@ class MmapPool : Allocator
|
|||||||
PAGE_READWRITE);
|
PAGE_READWRITE);
|
||||||
if (p is null)
|
if (p is null)
|
||||||
{
|
{
|
||||||
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
|
|
||||||
{
|
|
||||||
onOutOfMemoryError();
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -386,13 +492,13 @@ class MmapPool : Allocator
|
|||||||
head = region;
|
head = region;
|
||||||
|
|
||||||
// Initialize the data block
|
// Initialize the data block
|
||||||
void* memoryPointer = p + regionEntrySize;
|
void* memoryPointer = p + RegionEntry.sizeof;
|
||||||
Block block1 = cast(Block) memoryPointer;
|
Block block1 = cast(Block) memoryPointer;
|
||||||
block1.size = size;
|
block1.size = size;
|
||||||
block1.free = false;
|
block1.free = false;
|
||||||
|
|
||||||
// It is what we want to return
|
// It is what we want to return
|
||||||
void* data = memoryPointer + blockEntrySize;
|
void* data = memoryPointer + BlockEntry.sizeof;
|
||||||
|
|
||||||
// Free block after data
|
// Free block after data
|
||||||
memoryPointer = data + size;
|
memoryPointer = data + size;
|
||||||
@ -400,28 +506,26 @@ class MmapPool : Allocator
|
|||||||
block1.prev = block2.next = null;
|
block1.prev = block2.next = null;
|
||||||
block1.next = block2;
|
block1.next = block2;
|
||||||
block2.prev = block1;
|
block2.prev = block1;
|
||||||
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
|
block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2;
|
||||||
block2.free = true;
|
block2.free = true;
|
||||||
block1.region = block2.region = region;
|
block1.region = block2.region = region;
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
private void* initializeRegion(size_t size) shared nothrow @nogc
|
||||||
private void* initializeRegion(size_t size) @nogc nothrow
|
|
||||||
{
|
{
|
||||||
return initializeRegion(size, head);
|
return initializeRegion(size, head);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Params:
|
* Params:
|
||||||
* x = Space to be aligned.
|
* x = Space to be aligned.
|
||||||
*
|
*
|
||||||
* Returns: Aligned size of $(D_PARAM x).
|
* Returns: Aligned size of $(D_PARAM x).
|
||||||
*/
|
*/
|
||||||
pragma(inline)
|
|
||||||
private static immutable(size_t) addAlignment(size_t x)
|
private static immutable(size_t) addAlignment(size_t x)
|
||||||
@nogc @safe pure nothrow
|
pure nothrow @safe @nogc
|
||||||
out (result)
|
out (result)
|
||||||
{
|
{
|
||||||
assert(result > 0);
|
assert(result > 0);
|
||||||
@ -431,34 +535,35 @@ class MmapPool : Allocator
|
|||||||
return (x - 1) / alignment_ * alignment_ + alignment_;
|
return (x - 1) / alignment_ * alignment_ + alignment_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Params:
|
* Params:
|
||||||
* x = Required space.
|
* x = Required space.
|
||||||
*
|
*
|
||||||
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
|
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
|
||||||
*/
|
*/
|
||||||
pragma(inline)
|
|
||||||
private static immutable(size_t) calculateRegionSize(size_t x)
|
private static immutable(size_t) calculateRegionSize(size_t x)
|
||||||
@nogc @safe pure nothrow
|
nothrow @safe @nogc
|
||||||
out (result)
|
out (result)
|
||||||
{
|
{
|
||||||
assert(result > 0);
|
assert(result > 0);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
x += regionEntrySize + blockEntrySize * 2;
|
x += RegionEntry.sizeof + BlockEntry.sizeof * 2;
|
||||||
return x / pageSize * pageSize + pageSize;
|
return x / pageSize * pageSize + pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property uint alignment() const @nogc @safe pure nothrow
|
/**
|
||||||
|
* Returns: Alignment offered.
|
||||||
|
*/
|
||||||
|
@property uint alignment() shared const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return alignment_;
|
return alignment_;
|
||||||
}
|
}
|
||||||
private enum alignment_ = 8;
|
private enum alignment_ = 8;
|
||||||
|
|
||||||
private static MmapPool instance_;
|
private shared static MmapPool instance_;
|
||||||
|
private shared static size_t pageSize;
|
||||||
private shared static immutable size_t pageSize;
|
|
||||||
|
|
||||||
private shared struct RegionEntry
|
private shared struct RegionEntry
|
||||||
{
|
{
|
||||||
@ -468,18 +573,73 @@ class MmapPool : Allocator
|
|||||||
size_t size;
|
size_t size;
|
||||||
}
|
}
|
||||||
private alias Region = shared RegionEntry*;
|
private alias Region = shared RegionEntry*;
|
||||||
private enum regionEntrySize = 32;
|
|
||||||
|
|
||||||
private shared Region head;
|
private shared Region head;
|
||||||
|
|
||||||
private shared struct BlockEntry
|
private shared struct BlockEntry
|
||||||
{
|
{
|
||||||
Block prev;
|
Block prev;
|
||||||
Block next;
|
Block next;
|
||||||
bool free;
|
|
||||||
size_t size;
|
|
||||||
Region region;
|
Region region;
|
||||||
|
size_t size;
|
||||||
|
bool free;
|
||||||
}
|
}
|
||||||
private alias Block = shared BlockEntry*;
|
private alias Block = shared BlockEntry*;
|
||||||
private enum blockEntrySize = 40;
|
}
|
||||||
|
|
||||||
|
// A lot of allocations/deallocations, but it is the minimum caused a
|
||||||
|
// segmentation fault because MmapPool reallocateInPlace moves a block wrong.
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto a = MmapPool.instance.allocate(16);
|
||||||
|
auto d = MmapPool.instance.allocate(16);
|
||||||
|
auto b = MmapPool.instance.allocate(16);
|
||||||
|
auto e = MmapPool.instance.allocate(16);
|
||||||
|
auto c = MmapPool.instance.allocate(16);
|
||||||
|
auto f = MmapPool.instance.allocate(16);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(a);
|
||||||
|
MmapPool.instance.deallocate(b);
|
||||||
|
MmapPool.instance.deallocate(c);
|
||||||
|
|
||||||
|
a = MmapPool.instance.allocate(50);
|
||||||
|
MmapPool.instance.reallocateInPlace(a, 64);
|
||||||
|
MmapPool.instance.deallocate(a);
|
||||||
|
|
||||||
|
a = MmapPool.instance.allocate(1);
|
||||||
|
auto tmp1 = MmapPool.instance.allocate(1);
|
||||||
|
auto h1 = MmapPool.instance.allocate(1);
|
||||||
|
auto tmp2 = cast(ubyte[]) MmapPool.instance.allocate(1);
|
||||||
|
|
||||||
|
auto h2 = MmapPool.instance.allocate(2);
|
||||||
|
tmp1 = MmapPool.instance.allocate(1);
|
||||||
|
MmapPool.instance.deallocate(h2);
|
||||||
|
MmapPool.instance.deallocate(h1);
|
||||||
|
|
||||||
|
h2 = MmapPool.instance.allocate(2);
|
||||||
|
h1 = MmapPool.instance.allocate(1);
|
||||||
|
MmapPool.instance.deallocate(h2);
|
||||||
|
|
||||||
|
auto rep = cast(void[]) tmp2;
|
||||||
|
MmapPool.instance.reallocate(rep, tmp1.length);
|
||||||
|
tmp2 = cast(ubyte[]) rep;
|
||||||
|
|
||||||
|
MmapPool.instance.reallocate(tmp1, 9);
|
||||||
|
|
||||||
|
rep = cast(void[]) tmp2;
|
||||||
|
MmapPool.instance.reallocate(rep, tmp1.length);
|
||||||
|
tmp2 = cast(ubyte[]) rep;
|
||||||
|
MmapPool.instance.reallocate(tmp1, 17);
|
||||||
|
|
||||||
|
tmp2[$ - 1] = 0;
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(tmp1);
|
||||||
|
|
||||||
|
b = MmapPool.instance.allocate(16);
|
||||||
|
|
||||||
|
MmapPool.instance.deallocate(h1);
|
||||||
|
MmapPool.instance.deallocate(a);
|
||||||
|
MmapPool.instance.deallocate(b);
|
||||||
|
MmapPool.instance.deallocate(d);
|
||||||
|
MmapPool.instance.deallocate(e);
|
||||||
|
MmapPool.instance.deallocate(f);
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,315 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.memory;
|
module tanya.memory;
|
||||||
|
|
||||||
|
import core.exception;
|
||||||
|
public import std.experimental.allocator : make, makeArray;
|
||||||
|
import std.traits;
|
||||||
public import tanya.memory.allocator;
|
public import tanya.memory.allocator;
|
||||||
public import std.experimental.allocator;
|
|
||||||
|
/**
|
||||||
|
* The mixin generates common methods for classes and structs using
|
||||||
|
* allocators. It provides a protected member, constructor and a read-only property,
|
||||||
|
* that checks if an allocator was already set and sets it to the default
|
||||||
|
* one, if not (useful for structs which don't have a default constructor).
|
||||||
|
*/
|
||||||
|
mixin template DefaultAllocator()
|
||||||
|
{
|
||||||
|
/// Allocator.
|
||||||
|
protected shared Allocator allocator_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* allocator = The allocator should be used.
|
||||||
|
*/
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator_ = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This property checks if the allocator was set in the constructor
|
||||||
|
* and sets it to the default one, if not.
|
||||||
|
*
|
||||||
|
* Returns: Used allocator.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE allocator_ !is null)
|
||||||
|
*/
|
||||||
|
protected @property shared(Allocator) allocator() nothrow @safe @nogc
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
if (allocator_ is null)
|
||||||
|
{
|
||||||
|
allocator_ = defaultAllocator;
|
||||||
|
}
|
||||||
|
return allocator_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
@property shared(Allocator) allocator() const nothrow @trusted @nogc
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
if (allocator_ is null)
|
||||||
|
{
|
||||||
|
return defaultAllocator;
|
||||||
|
}
|
||||||
|
return cast(shared Allocator) allocator_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From druntime
|
||||||
|
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc;
|
||||||
|
|
||||||
|
shared Allocator allocator;
|
||||||
|
|
||||||
|
shared static this() nothrow @trusted @nogc
|
||||||
|
{
|
||||||
|
import tanya.memory.mmappool;
|
||||||
|
allocator = MmapPool.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
.allocator = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size in bytes of the state that needs to be allocated to hold an
|
||||||
|
* object of type $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Object type.
|
||||||
|
*/
|
||||||
|
template stateSize(T)
|
||||||
|
{
|
||||||
|
static if (is(T == class) || is(T == interface))
|
||||||
|
{
|
||||||
|
enum stateSize = __traits(classInstanceSize, T);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enum stateSize = T.sizeof;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* size = Raw size.
|
||||||
|
* alignment = Alignment.
|
||||||
|
*
|
||||||
|
* Returns: Aligned size.
|
||||||
|
*/
|
||||||
|
size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return (size - 1) / alignment * alignment + alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function used to create, resize or destroy a dynamic array. It
|
||||||
|
* throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new
|
||||||
|
* allocated part of the array is initialized only if $(D_PARAM Init)
|
||||||
|
* is set. This function can be trusted only in the data structures that
|
||||||
|
* can ensure that the array is allocated/rellocated/deallocated with the
|
||||||
|
* same allocator.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Element type of the array being created.
|
||||||
|
* Init = If should be initialized.
|
||||||
|
* Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn.
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
package(tanya) bool resize(T,
|
||||||
|
bool Init = true,
|
||||||
|
bool Throws = true)
|
||||||
|
(shared Allocator allocator,
|
||||||
|
ref T[] array,
|
||||||
|
in size_t length) @trusted
|
||||||
|
{
|
||||||
|
void[] buf = array;
|
||||||
|
static if (Init)
|
||||||
|
{
|
||||||
|
immutable oldLength = array.length;
|
||||||
|
}
|
||||||
|
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||||
|
{
|
||||||
|
static if (Throws)
|
||||||
|
{
|
||||||
|
onOutOfMemoryError;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Casting from void[] is unsafe, but we know we cast to the original type.
|
||||||
|
array = cast(T[]) buf;
|
||||||
|
|
||||||
|
static if (Init)
|
||||||
|
{
|
||||||
|
if (oldLength < length)
|
||||||
|
{
|
||||||
|
array[oldLength .. $] = T.init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
package(tanya) alias resizeArray = resize;
|
||||||
|
|
||||||
|
///
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
|
||||||
|
* It is assumed the respective entities had been allocated with the same
|
||||||
|
* allocator.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of $(D_PARAM p).
|
||||||
|
* allocator = Allocator the $(D_PARAM p) was allocated with.
|
||||||
|
* p = Object or array to be destroyed.
|
||||||
|
*/
|
||||||
|
void dispose(T)(shared Allocator allocator, auto ref T* p)
|
||||||
|
{
|
||||||
|
static if (hasElaborateDestructor!T)
|
||||||
|
{
|
||||||
|
destroy(*p);
|
||||||
|
}
|
||||||
|
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
|
||||||
|
p = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
void dispose(T)(shared Allocator allocator, auto ref T p)
|
||||||
|
if (is(T == class) || is(T == interface))
|
||||||
|
{
|
||||||
|
if (p is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
static if (is(T == interface))
|
||||||
|
{
|
||||||
|
version(Windows)
|
||||||
|
{
|
||||||
|
import core.sys.windows.unknwn : IUnknown;
|
||||||
|
static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
|
||||||
|
~ __PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
auto ob = cast(Object) p;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alias ob = p;
|
||||||
|
}
|
||||||
|
auto ptr = cast(void *) ob;
|
||||||
|
|
||||||
|
auto support = ptr[0 .. typeid(ob).initializer.length];
|
||||||
|
scope (success)
|
||||||
|
{
|
||||||
|
() @trusted { allocator.deallocate(support); }();
|
||||||
|
p = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ppv = cast(void**) ptr;
|
||||||
|
if (!*ppv)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto pc = cast(ClassInfo*) *ppv;
|
||||||
|
scope (exit)
|
||||||
|
{
|
||||||
|
*ppv = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto c = *pc;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Assume the destructor is @nogc. Leave it nothrow since the destructor
|
||||||
|
// shouldn't throw and if it does, it is an error anyway.
|
||||||
|
if (c.destructor)
|
||||||
|
{
|
||||||
|
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ((c = c.base) !is null);
|
||||||
|
|
||||||
|
if (ppv[1]) // if monitor is not null
|
||||||
|
{
|
||||||
|
_d_monitordelete(cast(Object) ptr, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
void dispose(T)(shared Allocator allocator, auto ref T[] p)
|
||||||
|
{
|
||||||
|
static if (hasElaborateDestructor!(typeof(p[0])))
|
||||||
|
{
|
||||||
|
import std.algorithm.iteration;
|
||||||
|
p.each!(e => destroy(e));
|
||||||
|
}
|
||||||
|
() @trusted { allocator.deallocate(p); }();
|
||||||
|
p = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
struct S
|
||||||
|
{
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
|
||||||
|
|
||||||
|
defaultAllocator.dispose(p);
|
||||||
|
}
|
||||||
|
458
source/tanya/memory/types.d
Normal file
458
source/tanya/memory/types.d
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
/* 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.memory.types;
|
||||||
|
|
||||||
|
import core.exception;
|
||||||
|
import std.algorithm.comparison;
|
||||||
|
import std.algorithm.mutation;
|
||||||
|
import std.conv;
|
||||||
|
import std.traits;
|
||||||
|
import tanya.memory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference-counted object containing a $(D_PARAM T) value as payload.
|
||||||
|
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
|
||||||
|
* when the reference count goes down to zero, frees the underlying store.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of the reference-counted value.
|
||||||
|
*/
|
||||||
|
struct RefCounted(T)
|
||||||
|
{
|
||||||
|
static if (is(T == class) || is(T == interface))
|
||||||
|
{
|
||||||
|
private alias Payload = T;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
private alias Payload = T*;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Storage
|
||||||
|
{
|
||||||
|
private Payload payload;
|
||||||
|
private size_t counter = 1;
|
||||||
|
|
||||||
|
private final size_t opUnary(string op)() pure nothrow @safe @nogc
|
||||||
|
if (op == "--" || op == "++")
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(counter > 0);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
mixin("return " ~ op ~ "counter;");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int opCmp(size_t counter) const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
if (this.counter > counter)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (this.counter < counter)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int opEquals(size_t counter) const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return this.counter == counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class RefCountedStorage : Storage
|
||||||
|
{
|
||||||
|
private shared Allocator allocator;
|
||||||
|
|
||||||
|
this(shared Allocator allocator) pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this() nothrow @nogc
|
||||||
|
{
|
||||||
|
allocator.dispose(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Storage storage;
|
||||||
|
|
||||||
|
invariant
|
||||||
|
{
|
||||||
|
assert(storage is null || allocator_ !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes ownership over $(D_PARAM value), setting the counter to 1.
|
||||||
|
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* value = Value whose ownership is taken over.
|
||||||
|
* allocator = Allocator used to destroy the $(D_PARAM value) and to
|
||||||
|
* allocate/deallocate internal storage.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
this(Payload value, shared Allocator allocator = defaultAllocator)
|
||||||
|
{
|
||||||
|
this(allocator);
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
move(value, storage.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator_ = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the reference counter by one.
|
||||||
|
*/
|
||||||
|
this(this)
|
||||||
|
{
|
||||||
|
if (count != 0)
|
||||||
|
{
|
||||||
|
++storage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decreases the reference counter by one.
|
||||||
|
*
|
||||||
|
* If the counter reaches 0, destroys the owned object.
|
||||||
|
*/
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
if (storage !is null && !(storage.counter && --storage))
|
||||||
|
{
|
||||||
|
allocator_.dispose(storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes ownership over $(D_PARAM rhs). Initializes this
|
||||||
|
* $(D_PSYMBOL RefCounted) if needed.
|
||||||
|
*
|
||||||
|
* If it is the last reference of the previously owned object,
|
||||||
|
* it will be destroyed.
|
||||||
|
*
|
||||||
|
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
||||||
|
*
|
||||||
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
||||||
|
* be used. If you need a different allocator, create a new
|
||||||
|
* $(D_PSYMBOL RefCounted) and assign it.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* rhs = $(D_KEYWORD this).
|
||||||
|
*/
|
||||||
|
ref typeof(this) opAssign(Payload rhs)
|
||||||
|
{
|
||||||
|
if (storage is null)
|
||||||
|
{
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
}
|
||||||
|
else if (storage > 1)
|
||||||
|
{
|
||||||
|
--storage;
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
}
|
||||||
|
else if (cast(RefCountedStorage) storage is null)
|
||||||
|
{
|
||||||
|
// Created with refCounted. Always destroyed togethter with the pointer.
|
||||||
|
assert(storage.counter != 0);
|
||||||
|
allocator.dispose(storage);
|
||||||
|
storage = allocator.make!RefCountedStorage(allocator);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allocator.dispose(storage.payload);
|
||||||
|
}
|
||||||
|
move(rhs, storage.payload);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(null))
|
||||||
|
{
|
||||||
|
if (storage is null)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else if (storage > 1)
|
||||||
|
{
|
||||||
|
--storage;
|
||||||
|
storage = null;
|
||||||
|
}
|
||||||
|
else if (cast(RefCountedStorage) storage is null)
|
||||||
|
{
|
||||||
|
// Created with refCounted. Always destroyed togethter with the pointer.
|
||||||
|
assert(storage.counter != 0);
|
||||||
|
allocator.dispose(storage);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allocator.dispose(storage.payload);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(this) rhs)
|
||||||
|
{
|
||||||
|
swap(allocator_, rhs.allocator_);
|
||||||
|
swap(storage, rhs.storage);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Reference to the owned object.
|
||||||
|
*/
|
||||||
|
inout(Payload) get() inout pure nothrow @safe @nogc
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(count > 0, "Attempted to access an uninitialized reference.");
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return storage.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
static if (isPointer!Payload)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* op = Operation.
|
||||||
|
*
|
||||||
|
* Dereferences the pointer. It is defined only for pointers, not for
|
||||||
|
* reference types like classes, that can be accessed directly.
|
||||||
|
*
|
||||||
|
* Returns: Reference to the pointed value.
|
||||||
|
*/
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*")
|
||||||
|
{
|
||||||
|
return *storage.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
|
||||||
|
* storage.
|
||||||
|
*/
|
||||||
|
@property bool isInitialized() const
|
||||||
|
{
|
||||||
|
return storage !is null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: The number of $(D_PSYMBOL RefCounted) instances that share
|
||||||
|
* ownership over the same pointer (including $(D_KEYWORD this)).
|
||||||
|
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
|
||||||
|
*/
|
||||||
|
@property size_t count() const
|
||||||
|
{
|
||||||
|
return storage is null ? 0 : storage.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin DefaultAllocator;
|
||||||
|
alias get this;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
|
||||||
|
auto val = rc.get;
|
||||||
|
|
||||||
|
*val = 8;
|
||||||
|
assert(*rc.storage.payload == 8);
|
||||||
|
|
||||||
|
val = null;
|
||||||
|
assert(rc.storage.payload !is null);
|
||||||
|
assert(*rc.storage.payload == 8);
|
||||||
|
|
||||||
|
*rc = 9;
|
||||||
|
assert(*rc.storage.payload == 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
version (unittest)
|
||||||
|
{
|
||||||
|
private class A
|
||||||
|
{
|
||||||
|
uint *destroyed;
|
||||||
|
|
||||||
|
this(ref uint destroyed) @nogc
|
||||||
|
{
|
||||||
|
this.destroyed = &destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
++(*destroyed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct B
|
||||||
|
{
|
||||||
|
int prop;
|
||||||
|
@disable this();
|
||||||
|
this(int param1) @nogc
|
||||||
|
{
|
||||||
|
prop = param1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
uint destroyed;
|
||||||
|
auto a = defaultAllocator.make!A(destroyed);
|
||||||
|
|
||||||
|
assert(destroyed == 0);
|
||||||
|
{
|
||||||
|
auto rc = RefCounted!A(a, defaultAllocator);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
void func(RefCounted!A rc)
|
||||||
|
{
|
||||||
|
assert(rc.count == 2);
|
||||||
|
}
|
||||||
|
func(rc);
|
||||||
|
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
assert(destroyed == 1);
|
||||||
|
|
||||||
|
RefCounted!int rc;
|
||||||
|
assert(rc.count == 0);
|
||||||
|
rc = defaultAllocator.make!int(8);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
||||||
|
static assert(is(typeof(RefCounted!A.storage.payload) == A));
|
||||||
|
|
||||||
|
static assert(is(RefCounted!B));
|
||||||
|
static assert(is(RefCounted!A));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
||||||
|
* $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for
|
||||||
|
* the constructor of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* This function is more efficient than the using of $(D_PSYMBOL RefCounted)
|
||||||
|
* directly, since it allocates only ones (the internal storage and the
|
||||||
|
* object).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of the constructed object.
|
||||||
|
* A = Types of the arguments to the constructor of $(D_PARAM T).
|
||||||
|
* allocator = Allocator.
|
||||||
|
* args = Constructor arguments of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
|
||||||
|
*/
|
||||||
|
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
|
||||||
|
if (!is(T == interface) && !isAbstractClass!T
|
||||||
|
&& !isArray!T && !isAssociativeArray!T)
|
||||||
|
{
|
||||||
|
auto rc = typeof(return)(allocator);
|
||||||
|
|
||||||
|
immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
|
||||||
|
immutable size = alignedSize(stateSize!T + storageSize);
|
||||||
|
|
||||||
|
auto mem = (() @trusted => allocator.allocate(size))();
|
||||||
|
if (mem is null)
|
||||||
|
{
|
||||||
|
onOutOfMemoryError();
|
||||||
|
}
|
||||||
|
scope (failure)
|
||||||
|
{
|
||||||
|
() @trusted { allocator.deallocate(mem); }();
|
||||||
|
}
|
||||||
|
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
|
||||||
|
|
||||||
|
static if (is(T == class))
|
||||||
|
{
|
||||||
|
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
||||||
|
rc.storage.payload = emplace!T(ptr, args);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!int(5);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
void func(RefCounted!int param)
|
||||||
|
{
|
||||||
|
if (param.count == 2)
|
||||||
|
{
|
||||||
|
func(param);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(param.count == 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func(rc);
|
||||||
|
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @nogc unittest
|
||||||
|
{
|
||||||
|
struct E
|
||||||
|
{
|
||||||
|
}
|
||||||
|
auto b = defaultAllocator.refCounted!B(15);
|
||||||
|
static assert(is(typeof(b.storage.payload) == B*));
|
||||||
|
static assert(is(typeof(b.prop) == int));
|
||||||
|
static assert(!is(typeof(defaultAllocator.refCounted!B())));
|
||||||
|
|
||||||
|
static assert(is(typeof(defaultAllocator.refCounted!E())));
|
||||||
|
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!B(3);
|
||||||
|
assert(rc.get.prop == 3);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!E();
|
||||||
|
assert(rc.count);
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
* Copyright: Eugene Wissner 2016.
|
* Copyright: Eugene Wissner 2016.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
* Mozilla Public License, v. 2.0).
|
* Mozilla Public License, v. 2.0).
|
||||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
*/
|
*/
|
||||||
module tanya.network.socket;
|
module tanya.network.socket;
|
||||||
|
|
||||||
@ -15,9 +15,8 @@ import core.stdc.errno;
|
|||||||
import core.time;
|
import core.time;
|
||||||
import std.algorithm.comparison;
|
import std.algorithm.comparison;
|
||||||
import std.algorithm.searching;
|
import std.algorithm.searching;
|
||||||
public import std.socket : AddressException, socket_t, Linger, SocketOptionLevel,
|
public import std.socket : socket_t, Linger, SocketOptionLevel, SocketOption,
|
||||||
SocketType, AddressFamily, AddressInfo,
|
SocketType, AddressFamily, AddressInfo;
|
||||||
SocketOption;
|
|
||||||
import std.traits;
|
import std.traits;
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
|
|
||||||
@ -154,6 +153,23 @@ else version (Windows)
|
|||||||
DWORD lpFlags,
|
DWORD lpFlags,
|
||||||
LPOVERLAPPED lpOverlapped,
|
LPOVERLAPPED lpOverlapped,
|
||||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||||
|
int WSAIoctl(SOCKET s,
|
||||||
|
uint dwIoControlCode,
|
||||||
|
void* lpvInBuffer,
|
||||||
|
uint cbInBuffer,
|
||||||
|
void* lpvOutBuffer,
|
||||||
|
uint cbOutBuffer,
|
||||||
|
uint* lpcbBytesReturned,
|
||||||
|
LPWSAOVERLAPPED lpOverlapped,
|
||||||
|
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||||
|
alias LPFN_ACCEPTEX = BOOL function(SOCKET,
|
||||||
|
SOCKET,
|
||||||
|
PVOID,
|
||||||
|
DWORD,
|
||||||
|
DWORD,
|
||||||
|
DWORD,
|
||||||
|
LPDWORD,
|
||||||
|
LPOVERLAPPED);
|
||||||
}
|
}
|
||||||
alias WSASocket = WSASocketW;
|
alias WSASocket = WSASocketW;
|
||||||
|
|
||||||
@ -213,7 +229,7 @@ else version (Windows)
|
|||||||
* handle = Socket handle.
|
* handle = Socket handle.
|
||||||
* af = Address family.
|
* af = Address family.
|
||||||
*/
|
*/
|
||||||
this(socket_t handle, AddressFamily af)
|
this(socket_t handle, AddressFamily af) @nogc
|
||||||
{
|
{
|
||||||
super(handle, af);
|
super(handle, af);
|
||||||
}
|
}
|
||||||
@ -233,13 +249,13 @@ else version (Windows)
|
|||||||
*/
|
*/
|
||||||
bool beginReceive(ubyte[] buffer,
|
bool beginReceive(ubyte[] buffer,
|
||||||
SocketState overlapped,
|
SocketState overlapped,
|
||||||
Flags flags = Flags(Flag.none)) @trusted
|
Flags flags = Flags(Flag.none)) @nogc @trusted
|
||||||
{
|
{
|
||||||
auto receiveFlags = cast(DWORD) flags;
|
auto receiveFlags = cast(DWORD) flags;
|
||||||
|
|
||||||
overlapped.handle = cast(HANDLE) handle_;
|
overlapped.handle = cast(HANDLE) handle_;
|
||||||
overlapped.event = OverlappedSocketEvent.read;
|
overlapped.event = OverlappedSocketEvent.read;
|
||||||
overlapped.buffer.len = buffer.length;
|
overlapped.buffer.len = cast(ULONG) buffer.length;
|
||||||
overlapped.buffer.buf = cast(char*) buffer.ptr;
|
overlapped.buffer.buf = cast(char*) buffer.ptr;
|
||||||
|
|
||||||
auto result = WSARecv(handle_,
|
auto result = WSARecv(handle_,
|
||||||
@ -252,7 +268,7 @@ else version (Windows)
|
|||||||
|
|
||||||
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to receive");
|
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||||
}
|
}
|
||||||
return result == 0;
|
return result == 0;
|
||||||
}
|
}
|
||||||
@ -267,7 +283,7 @@ else version (Windows)
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||||
*/
|
*/
|
||||||
int endReceive(SocketState overlapped) @trusted
|
int endReceive(SocketState overlapped) @nogc @trusted
|
||||||
out (count)
|
out (count)
|
||||||
{
|
{
|
||||||
assert(count >= 0);
|
assert(count >= 0);
|
||||||
@ -282,7 +298,7 @@ else version (Windows)
|
|||||||
if (result == FALSE && !wouldHaveBlocked)
|
if (result == FALSE && !wouldHaveBlocked)
|
||||||
{
|
{
|
||||||
disconnected_ = true;
|
disconnected_ = true;
|
||||||
throw theAllocator.make!SocketException("Unable to receive");
|
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||||
}
|
}
|
||||||
if (lpNumber == 0)
|
if (lpNumber == 0)
|
||||||
{
|
{
|
||||||
@ -306,11 +322,11 @@ else version (Windows)
|
|||||||
*/
|
*/
|
||||||
bool beginSend(ubyte[] buffer,
|
bool beginSend(ubyte[] buffer,
|
||||||
SocketState overlapped,
|
SocketState overlapped,
|
||||||
Flags flags = Flags(Flag.none)) @trusted
|
Flags flags = Flags(Flag.none)) @nogc @trusted
|
||||||
{
|
{
|
||||||
overlapped.handle = cast(HANDLE) handle_;
|
overlapped.handle = cast(HANDLE) handle_;
|
||||||
overlapped.event = OverlappedSocketEvent.write;
|
overlapped.event = OverlappedSocketEvent.write;
|
||||||
overlapped.buffer.len = buffer.length;
|
overlapped.buffer.len = cast(ULONG) buffer.length;
|
||||||
overlapped.buffer.buf = cast(char*) buffer.ptr;
|
overlapped.buffer.buf = cast(char*) buffer.ptr;
|
||||||
|
|
||||||
auto result = WSASend(handle_,
|
auto result = WSASend(handle_,
|
||||||
@ -324,7 +340,7 @@ else version (Windows)
|
|||||||
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
||||||
{
|
{
|
||||||
disconnected_ = true;
|
disconnected_ = true;
|
||||||
throw theAllocator.make!SocketException("Unable to send");
|
throw defaultAllocator.make!SocketException("Unable to send");
|
||||||
}
|
}
|
||||||
return result == 0;
|
return result == 0;
|
||||||
}
|
}
|
||||||
@ -339,7 +355,7 @@ else version (Windows)
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||||
*/
|
*/
|
||||||
int endSend(SocketState overlapped) @trusted
|
int endSend(SocketState overlapped) @nogc @trusted
|
||||||
out (count)
|
out (count)
|
||||||
{
|
{
|
||||||
assert(count >= 0);
|
assert(count >= 0);
|
||||||
@ -354,7 +370,7 @@ else version (Windows)
|
|||||||
if (result == FALSE && !wouldHaveBlocked)
|
if (result == FALSE && !wouldHaveBlocked)
|
||||||
{
|
{
|
||||||
disconnected_ = true;
|
disconnected_ = true;
|
||||||
throw theAllocator.make!SocketException("Unable to receive");
|
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||||
}
|
}
|
||||||
return lpNumber;
|
return lpNumber;
|
||||||
}
|
}
|
||||||
@ -373,7 +389,7 @@ else version (Windows)
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) on errors.
|
* Throws: $(D_PSYMBOL SocketException) on errors.
|
||||||
*/
|
*/
|
||||||
this(AddressFamily af) @trusted
|
this(AddressFamily af) @nogc @trusted
|
||||||
{
|
{
|
||||||
super(af);
|
super(af);
|
||||||
scope (failure)
|
scope (failure)
|
||||||
@ -396,7 +412,8 @@ else version (Windows)
|
|||||||
NULL);
|
NULL);
|
||||||
if (!result == SOCKET_ERROR)
|
if (!result == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to retrieve an accept extension function pointer");
|
throw make!SocketException(defaultAllocator,
|
||||||
|
"Unable to retrieve an accept extension function pointer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,12 +428,12 @@ else version (Windows)
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) on accept errors.
|
* Throws: $(D_PSYMBOL SocketException) on accept errors.
|
||||||
*/
|
*/
|
||||||
bool beginAccept(SocketState overlapped) @trusted
|
bool beginAccept(SocketState overlapped) @nogc @trusted
|
||||||
{
|
{
|
||||||
auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
|
auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
|
||||||
if (socket == socket_t.init)
|
if (socket == socket_t.init)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to create socket");
|
throw defaultAllocator.make!SocketException("Unable to create socket");
|
||||||
}
|
}
|
||||||
scope (failure)
|
scope (failure)
|
||||||
{
|
{
|
||||||
@ -425,8 +442,10 @@ else version (Windows)
|
|||||||
DWORD dwBytes;
|
DWORD dwBytes;
|
||||||
overlapped.handle = cast(HANDLE) socket;
|
overlapped.handle = cast(HANDLE) socket;
|
||||||
overlapped.event = OverlappedSocketEvent.accept;
|
overlapped.event = OverlappedSocketEvent.accept;
|
||||||
overlapped.buffer.len = (sockaddr_in.sizeof + 16) * 2;
|
|
||||||
overlapped.buffer.buf = theAllocator.makeArray!char(overlapped.buffer.len).ptr;
|
immutable len = (sockaddr_in.sizeof + 16) * 2;
|
||||||
|
overlapped.buffer.len = len;
|
||||||
|
overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
|
||||||
|
|
||||||
// We don't want to get any data now, but only start to accept the connections
|
// We don't want to get any data now, but only start to accept the connections
|
||||||
BOOL result = acceptExtension(handle_,
|
BOOL result = acceptExtension(handle_,
|
||||||
@ -439,7 +458,7 @@ else version (Windows)
|
|||||||
&overlapped.overlapped);
|
&overlapped.overlapped);
|
||||||
if (result == FALSE && !wouldHaveBlocked)
|
if (result == FALSE && !wouldHaveBlocked)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to accept socket connection");
|
throw defaultAllocator.make!SocketException("Unable to accept socket connection");
|
||||||
}
|
}
|
||||||
return result == TRUE;
|
return result == TRUE;
|
||||||
}
|
}
|
||||||
@ -455,17 +474,18 @@ else version (Windows)
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
||||||
*/
|
*/
|
||||||
OverlappedConnectedSocket endAccept(SocketState overlapped) @trusted
|
OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
|
||||||
{
|
{
|
||||||
scope (exit)
|
scope (exit)
|
||||||
{
|
{
|
||||||
theAllocator.dispose(overlapped.buffer.buf[0..overlapped.buffer.len]);
|
defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
|
||||||
}
|
}
|
||||||
auto socket = theAllocator.make!OverlappedConnectedSocket(cast(socket_t) overlapped.handle,
|
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
|
||||||
|
cast(socket_t) overlapped.handle,
|
||||||
addressFamily);
|
addressFamily);
|
||||||
scope (failure)
|
scope (failure)
|
||||||
{
|
{
|
||||||
theAllocator.dispose(socket);
|
defaultAllocator.dispose(socket);
|
||||||
}
|
}
|
||||||
socket.setOption(SocketOptionLevel.SOCKET,
|
socket.setOption(SocketOptionLevel.SOCKET,
|
||||||
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
|
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
|
||||||
@ -663,7 +683,7 @@ abstract class Socket
|
|||||||
/// Address family.
|
/// Address family.
|
||||||
protected AddressFamily family;
|
protected AddressFamily family;
|
||||||
|
|
||||||
private @property void handle(socket_t handle)
|
private @property void handle(socket_t handle) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(handle != socket_t.init);
|
assert(handle != socket_t.init);
|
||||||
@ -693,7 +713,7 @@ abstract class Socket
|
|||||||
* handle = Socket.
|
* handle = Socket.
|
||||||
* af = Address family.
|
* af = Address family.
|
||||||
*/
|
*/
|
||||||
this(socket_t handle, AddressFamily af)
|
this(socket_t handle, AddressFamily af) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(handle != socket_t.init);
|
assert(handle != socket_t.init);
|
||||||
@ -728,7 +748,9 @@ abstract class Socket
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) on error.
|
* Throws: $(D_PSYMBOL SocketException) on error.
|
||||||
*/
|
*/
|
||||||
protected int getOption(SocketOptionLevel level, SocketOption option, void[] result) const @trusted
|
protected int getOption(SocketOptionLevel level,
|
||||||
|
SocketOption option,
|
||||||
|
void[] result) const @trusted @nogc
|
||||||
{
|
{
|
||||||
auto length = cast(socklen_t) result.length;
|
auto length = cast(socklen_t) result.length;
|
||||||
if (getsockopt(handle_,
|
if (getsockopt(handle_,
|
||||||
@ -737,25 +759,31 @@ abstract class Socket
|
|||||||
result.ptr,
|
result.ptr,
|
||||||
&length) == SOCKET_ERROR)
|
&length) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to get socket option");
|
throw defaultAllocator.make!SocketException("Unable to get socket option");
|
||||||
}
|
}
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
int getOption(SocketOptionLevel level, SocketOption option, out size_t result) const @trusted
|
int getOption(SocketOptionLevel level,
|
||||||
|
SocketOption option,
|
||||||
|
out size_t result) const @trusted @nogc
|
||||||
{
|
{
|
||||||
return getOption(level, option, (&result)[0..1]);
|
return getOption(level, option, (&result)[0..1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
int getOption(SocketOptionLevel level, SocketOption option, out Linger result) const @trusted
|
int getOption(SocketOptionLevel level,
|
||||||
|
SocketOption option,
|
||||||
|
out Linger result) const @trusted @nogc
|
||||||
{
|
{
|
||||||
return getOption(level, option, (&result.clinger)[0..1]);
|
return getOption(level, option, (&result.clinger)[0..1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
int getOption(SocketOptionLevel level, SocketOption option, out Duration result) const @trusted
|
int getOption(SocketOptionLevel level,
|
||||||
|
SocketOption option,
|
||||||
|
out Duration result) const @trusted @nogc
|
||||||
{
|
{
|
||||||
// WinSock returns the timeout values as a milliseconds DWORD,
|
// WinSock returns the timeout values as a milliseconds DWORD,
|
||||||
// while Linux and BSD return a timeval struct.
|
// while Linux and BSD return a timeval struct.
|
||||||
@ -784,12 +812,13 @@ abstract class Socket
|
|||||||
* Params:
|
* Params:
|
||||||
* level = Protocol level at that the option exists.
|
* level = Protocol level at that the option exists.
|
||||||
* option = Option.
|
* option = Option.
|
||||||
* result = Option value.
|
* value = Option value.
|
||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) on error.
|
* Throws: $(D_PSYMBOL SocketException) on error.
|
||||||
*/
|
*/
|
||||||
protected void setOption(SocketOptionLevel level, SocketOption option, void[] value)
|
protected void setOption(SocketOptionLevel level,
|
||||||
const @trusted
|
SocketOption option,
|
||||||
|
void[] value) const @trusted @nogc
|
||||||
{
|
{
|
||||||
if (setsockopt(handle_,
|
if (setsockopt(handle_,
|
||||||
cast(int)level,
|
cast(int)level,
|
||||||
@ -797,24 +826,27 @@ abstract class Socket
|
|||||||
value.ptr,
|
value.ptr,
|
||||||
cast(uint) value.length) == SOCKET_ERROR)
|
cast(uint) value.length) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to set socket option");
|
throw defaultAllocator.make!SocketException("Unable to set socket option");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
void setOption(SocketOptionLevel level, SocketOption option, size_t value) const @trusted
|
void setOption(SocketOptionLevel level, SocketOption option, size_t value)
|
||||||
|
const @trusted @nogc
|
||||||
{
|
{
|
||||||
setOption(level, option, (&value)[0..1]);
|
setOption(level, option, (&value)[0..1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
void setOption(SocketOptionLevel level, SocketOption option, Linger value) const @trusted
|
void setOption(SocketOptionLevel level, SocketOption option, Linger value)
|
||||||
|
const @trusted @nogc
|
||||||
{
|
{
|
||||||
setOption(level, option, (&value.clinger)[0..1]);
|
setOption(level, option, (&value.clinger)[0..1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
void setOption(SocketOptionLevel level, SocketOption option, Duration value) const @trusted
|
void setOption(SocketOptionLevel level, SocketOption option, Duration value)
|
||||||
|
const @trusted @nogc
|
||||||
{
|
{
|
||||||
version (Posix)
|
version (Posix)
|
||||||
{
|
{
|
||||||
@ -852,7 +884,7 @@ abstract class Socket
|
|||||||
* Params:
|
* Params:
|
||||||
* yes = Socket's blocking flag.
|
* yes = Socket's blocking flag.
|
||||||
*/
|
*/
|
||||||
@property void blocking(bool yes)
|
@property void blocking(bool yes) @nogc
|
||||||
{
|
{
|
||||||
version (Posix)
|
version (Posix)
|
||||||
{
|
{
|
||||||
@ -865,7 +897,8 @@ abstract class Socket
|
|||||||
}
|
}
|
||||||
if (fl == SOCKET_ERROR)
|
if (fl == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to set socket blocking");
|
throw make!SocketException(defaultAllocator,
|
||||||
|
"Unable to set socket blocking");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else version (Windows)
|
else version (Windows)
|
||||||
@ -873,7 +906,8 @@ abstract class Socket
|
|||||||
uint num = !yes;
|
uint num = !yes;
|
||||||
if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
|
if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to set socket blocking");
|
throw make!SocketException(defaultAllocator,
|
||||||
|
"Unable to set socket blocking");
|
||||||
}
|
}
|
||||||
blocking_ = yes;
|
blocking_ = yes;
|
||||||
}
|
}
|
||||||
@ -938,11 +972,11 @@ abstract class Socket
|
|||||||
* backlog = Request of how many pending incoming connections are
|
* backlog = Request of how many pending incoming connections are
|
||||||
* queued until $(D_PSYMBOL accept)ed.
|
* queued until $(D_PSYMBOL accept)ed.
|
||||||
*/
|
*/
|
||||||
void listen(int backlog) const @trusted
|
void listen(int backlog) const @trusted @nogc
|
||||||
{
|
{
|
||||||
if (.listen(handle_, backlog) == SOCKET_ERROR)
|
if (.listen(handle_, backlog) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to listen on socket");
|
throw defaultAllocator.make!SocketException("Unable to listen on socket");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -987,12 +1021,12 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
* Params:
|
* Params:
|
||||||
* af = Address family.
|
* af = Address family.
|
||||||
*/
|
*/
|
||||||
this(AddressFamily af) @trusted
|
this(AddressFamily af) @trusted @nogc
|
||||||
{
|
{
|
||||||
auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
|
auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
|
||||||
if (handle == socket_t.init)
|
if (handle == socket_t.init)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to create socket");
|
throw defaultAllocator.make!SocketException("Unable to create socket");
|
||||||
}
|
}
|
||||||
super(handle, af);
|
super(handle, af);
|
||||||
}
|
}
|
||||||
@ -1005,11 +1039,11 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to bind.
|
* Throws: $(D_PSYMBOL SocketException) if unable to bind.
|
||||||
*/
|
*/
|
||||||
void bind(Address address) @trusted const
|
void bind(Address address) const @trusted @nogc
|
||||||
{
|
{
|
||||||
if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
|
if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!SocketException("Unable to bind socket");
|
throw defaultAllocator.make!SocketException("Unable to bind socket");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1024,7 +1058,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
||||||
*/
|
*/
|
||||||
ConnectedSocket accept() @trusted
|
ConnectedSocket accept() @trusted @nogc
|
||||||
{
|
{
|
||||||
socket_t sock;
|
socket_t sock;
|
||||||
|
|
||||||
@ -1048,10 +1082,11 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw theAllocator.make!SocketException("Unable to accept socket connection");
|
throw make!SocketException(defaultAllocator,
|
||||||
|
"Unable to accept socket connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto newSocket = theAllocator.make!ConnectedSocket(sock, addressFamily);
|
auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);
|
||||||
|
|
||||||
version (linux)
|
version (linux)
|
||||||
{ // Blocking mode already set
|
{ // Blocking mode already set
|
||||||
@ -1066,7 +1101,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
theAllocator.dispose(newSocket);
|
defaultAllocator.dispose(newSocket);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1106,7 +1141,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
* handle = Socket.
|
* handle = Socket.
|
||||||
* af = Address family.
|
* af = Address family.
|
||||||
*/
|
*/
|
||||||
this(socket_t handle, AddressFamily af)
|
this(socket_t handle, AddressFamily af) @nogc
|
||||||
{
|
{
|
||||||
super(handle, af);
|
super(handle, af);
|
||||||
}
|
}
|
||||||
@ -1142,7 +1177,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||||
*/
|
*/
|
||||||
ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted
|
ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc
|
||||||
{
|
{
|
||||||
ptrdiff_t ret;
|
ptrdiff_t ret;
|
||||||
if (!buf.length)
|
if (!buf.length)
|
||||||
@ -1162,7 +1197,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
disconnected_ = true;
|
disconnected_ = true;
|
||||||
throw theAllocator.make!SocketException("Unable to receive");
|
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1180,7 +1215,8 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to send.
|
* Throws: $(D_PSYMBOL SocketException) if unable to send.
|
||||||
*/
|
*/
|
||||||
ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none) const @trusted
|
ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none)
|
||||||
|
const @trusted @nogc
|
||||||
{
|
{
|
||||||
int sendFlags = cast(int) flags;
|
int sendFlags = cast(int) flags;
|
||||||
ptrdiff_t sent;
|
ptrdiff_t sent;
|
||||||
@ -1199,7 +1235,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
throw theAllocator.make!SocketException("Unable to send");
|
throw defaultAllocator.make!SocketException("Unable to send");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1238,22 +1274,23 @@ class InternetAddress : Address
|
|||||||
anyPort = 0,
|
anyPort = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
this(in string host, ushort port = anyPort)
|
this(in string host, ushort port = anyPort) @nogc
|
||||||
{
|
{
|
||||||
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
|
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!AddressException("Address info lookup is not available on this system");
|
throw make!SocketException(defaultAllocator,
|
||||||
|
"Address info lookup is not available on this system");
|
||||||
}
|
}
|
||||||
addrinfo* ai_res;
|
addrinfo* ai_res;
|
||||||
port_ = port;
|
port_ = port;
|
||||||
|
|
||||||
// Make C-string from host.
|
// Make C-string from host.
|
||||||
char[] node = theAllocator.makeArray!char(host.length + 1);
|
auto node = cast(char[]) allocator.allocate(host.length + 1);
|
||||||
node[0.. $ - 1] = host;
|
node[0.. $ - 1] = host;
|
||||||
node[$ - 1] = '\0';
|
node[$ - 1] = '\0';
|
||||||
scope (exit)
|
scope (exit)
|
||||||
{
|
{
|
||||||
theAllocator.dispose(node);
|
allocator.deallocate(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert port to a C-string.
|
// Convert port to a C-string.
|
||||||
@ -1278,7 +1315,7 @@ class InternetAddress : Address
|
|||||||
auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
|
auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
|
||||||
if (ret)
|
if (ret)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!AddressException("Address info lookup failed");
|
throw defaultAllocator.make!SocketException("Address info lookup failed");
|
||||||
}
|
}
|
||||||
scope (exit)
|
scope (exit)
|
||||||
{
|
{
|
||||||
@ -1292,7 +1329,7 @@ class InternetAddress : Address
|
|||||||
}
|
}
|
||||||
if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
|
if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
|
||||||
{
|
{
|
||||||
throw theAllocator.make!AddressException("Wrong address family");
|
throw defaultAllocator.make!SocketException("Wrong address family");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1352,7 +1389,8 @@ bool wouldHaveBlocked() nothrow @trusted @nogc
|
|||||||
}
|
}
|
||||||
else version (Windows)
|
else version (Windows)
|
||||||
{
|
{
|
||||||
return WSAGetLastError() == ERROR_IO_PENDING || WSAGetLastError() == EWOULDBLOCK
|
return WSAGetLastError() == ERROR_IO_PENDING
|
||||||
|
|| WSAGetLastError() == EWOULDBLOCK
|
||||||
|| WSAGetLastError() == ERROR_IO_INCOMPLETE;
|
|| WSAGetLastError() == ERROR_IO_INCOMPLETE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -630,32 +630,31 @@ static this()
|
|||||||
/**
|
/**
|
||||||
* A Unique Resource Locator.
|
* A Unique Resource Locator.
|
||||||
*/
|
*/
|
||||||
struct URL(U = string)
|
struct URL
|
||||||
if (isSomeString!U)
|
|
||||||
{
|
{
|
||||||
/** The URL scheme. */
|
/** The URL scheme. */
|
||||||
U scheme;
|
const(char)[] scheme;
|
||||||
|
|
||||||
/** The username. */
|
/** The username. */
|
||||||
U user;
|
const(char)[] user;
|
||||||
|
|
||||||
/** The password. */
|
/** The password. */
|
||||||
U pass;
|
const(char)[] pass;
|
||||||
|
|
||||||
/** The hostname. */
|
/** The hostname. */
|
||||||
U host;
|
const(char)[] host;
|
||||||
|
|
||||||
/** The port number. */
|
/** The port number. */
|
||||||
ushort port;
|
ushort port;
|
||||||
|
|
||||||
/** The path. */
|
/** The path. */
|
||||||
U path;
|
const(char)[] path;
|
||||||
|
|
||||||
/** The query string. */
|
/** The query string. */
|
||||||
U query;
|
const(char)[] query;
|
||||||
|
|
||||||
/** The anchor. */
|
/** The anchor. */
|
||||||
U fragment;
|
const(char)[] fragment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to parse an URL from a string.
|
* Attempts to parse an URL from a string.
|
||||||
@ -666,7 +665,7 @@ struct URL(U = string)
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL URIException) if the URL is malformed.
|
* Throws: $(D_PSYMBOL URIException) if the URL is malformed.
|
||||||
*/
|
*/
|
||||||
this(U source)
|
this(in char[] source)
|
||||||
{
|
{
|
||||||
auto value = source;
|
auto value = source;
|
||||||
ptrdiff_t pos = -1, endPos = value.length, start;
|
ptrdiff_t pos = -1, endPos = value.length, start;
|
||||||
@ -702,7 +701,7 @@ struct URL(U = string)
|
|||||||
{
|
{
|
||||||
if (!parsePort(value[pos..$]))
|
if (!parsePort(value[pos..$]))
|
||||||
{
|
{
|
||||||
throw theAllocator.make!URIException("Failed to parse port");
|
throw defaultAllocator.make!URIException("Failed to parse port");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto ParsePath;
|
goto ParsePath;
|
||||||
@ -751,7 +750,7 @@ struct URL(U = string)
|
|||||||
else if (pos == 0 && parsePort(value[pos..$]))
|
else if (pos == 0 && parsePort(value[pos..$]))
|
||||||
{
|
{
|
||||||
// An URL shouldn't begin with a port number
|
// An URL shouldn't begin with a port number
|
||||||
throw theAllocator.make!URIException("URL begins with port");
|
throw defaultAllocator.make!URIException("URL begins with port");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -808,7 +807,7 @@ struct URL(U = string)
|
|||||||
{
|
{
|
||||||
pass = null;
|
pass = null;
|
||||||
}
|
}
|
||||||
throw make!URIException(theAllocator,
|
throw make!URIException(defaultAllocator,
|
||||||
"Restricted characters in user information");
|
"Restricted characters in user information");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -844,7 +843,7 @@ struct URL(U = string)
|
|||||||
{
|
{
|
||||||
pass = null;
|
pass = null;
|
||||||
}
|
}
|
||||||
throw theAllocator.make!URIException("Invalid port");
|
throw defaultAllocator.make!URIException("Invalid port");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -866,7 +865,7 @@ struct URL(U = string)
|
|||||||
{
|
{
|
||||||
pass = null;
|
pass = null;
|
||||||
}
|
}
|
||||||
throw theAllocator.make!URIException("Invalid host");
|
throw defaultAllocator.make!URIException("Invalid host");
|
||||||
}
|
}
|
||||||
|
|
||||||
host = value[start..pos];
|
host = value[start..pos];
|
||||||
@ -954,7 +953,7 @@ struct URL(U = string)
|
|||||||
*
|
*
|
||||||
* Returns: Whether the port could be found.
|
* Returns: Whether the port could be found.
|
||||||
*/
|
*/
|
||||||
private bool parsePort(U port) pure nothrow @safe @nogc
|
private bool parsePort(in char[] port) pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
ptrdiff_t i = 1;
|
ptrdiff_t i = 1;
|
||||||
float lPort = 0;
|
float lPort = 0;
|
||||||
@ -984,14 +983,14 @@ struct URL(U = string)
|
|||||||
///
|
///
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
auto u = URL!()("example.org");
|
auto u = URL("example.org");
|
||||||
assert(u.path == "example.org");
|
assert(u.path == "example.org");
|
||||||
|
|
||||||
u = URL!()("relative/path");
|
u = URL("relative/path");
|
||||||
assert(u.path == "relative/path");
|
assert(u.path == "relative/path");
|
||||||
|
|
||||||
// Host and scheme
|
// Host and scheme
|
||||||
u = URL!()("https://example.org");
|
u = URL("https://example.org");
|
||||||
assert(u.scheme == "https");
|
assert(u.scheme == "https");
|
||||||
assert(u.host == "example.org");
|
assert(u.host == "example.org");
|
||||||
assert(u.path is null);
|
assert(u.path is null);
|
||||||
@ -999,7 +998,7 @@ unittest
|
|||||||
assert(u.fragment is null);
|
assert(u.fragment is null);
|
||||||
|
|
||||||
// With user and port and path
|
// With user and port and path
|
||||||
u = URL!()("https://hilary:putnam@example.org:443/foo/bar");
|
u = URL("https://hilary:putnam@example.org:443/foo/bar");
|
||||||
assert(u.scheme == "https");
|
assert(u.scheme == "https");
|
||||||
assert(u.host == "example.org");
|
assert(u.host == "example.org");
|
||||||
assert(u.path == "/foo/bar");
|
assert(u.path == "/foo/bar");
|
||||||
@ -1009,7 +1008,7 @@ unittest
|
|||||||
assert(u.fragment is null);
|
assert(u.fragment is null);
|
||||||
|
|
||||||
// With query string
|
// With query string
|
||||||
u = URL!()("https://example.org/?login=true");
|
u = URL("https://example.org/?login=true");
|
||||||
assert(u.scheme == "https");
|
assert(u.scheme == "https");
|
||||||
assert(u.host == "example.org");
|
assert(u.host == "example.org");
|
||||||
assert(u.path == "/");
|
assert(u.path == "/");
|
||||||
@ -1017,14 +1016,14 @@ unittest
|
|||||||
assert(u.fragment is null);
|
assert(u.fragment is null);
|
||||||
|
|
||||||
// With query string and fragment
|
// With query string and fragment
|
||||||
u = URL!()("https://example.org/?login=false#label");
|
u = URL("https://example.org/?login=false#label");
|
||||||
assert(u.scheme == "https");
|
assert(u.scheme == "https");
|
||||||
assert(u.host == "example.org");
|
assert(u.host == "example.org");
|
||||||
assert(u.path == "/");
|
assert(u.path == "/");
|
||||||
assert(u.query == "login=false");
|
assert(u.query == "login=false");
|
||||||
assert(u.fragment == "label");
|
assert(u.fragment == "label");
|
||||||
|
|
||||||
u = URL!()("redis://root:password@localhost:2201/path?query=value#fragment");
|
u = URL("redis://root:password@localhost:2201/path?query=value#fragment");
|
||||||
assert(u.scheme == "redis");
|
assert(u.scheme == "redis");
|
||||||
assert(u.user == "root");
|
assert(u.user == "root");
|
||||||
assert(u.pass == "password");
|
assert(u.pass == "password");
|
||||||
@ -1043,7 +1042,7 @@ private unittest
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
URL!()(t[0]);
|
URL(t[0]);
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
catch (URIException e)
|
catch (URIException e)
|
||||||
@ -1053,7 +1052,7 @@ private unittest
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto u = URL!()(t[0]);
|
auto u = URL(t[0]);
|
||||||
assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null,
|
assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null,
|
||||||
t[0]);
|
t[0]);
|
||||||
assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]);
|
assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]);
|
||||||
@ -1100,31 +1099,30 @@ enum Component : string
|
|||||||
*
|
*
|
||||||
* Returns: Requested URL components.
|
* Returns: Requested URL components.
|
||||||
*/
|
*/
|
||||||
URL parseURL(U)(in U source)
|
URL parseURL(typeof(null) T)(in char[] source)
|
||||||
if (isSomeString!U)
|
|
||||||
{
|
{
|
||||||
return URL!U(source);
|
return URL(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ditto */
|
/// Ditto.
|
||||||
string parseURL(string T, U)(in U source)
|
const(char)[] parseURL(immutable(char)[] T)(in char[] source)
|
||||||
if ((T == "scheme"
|
if (T == "scheme"
|
||||||
|| T =="host"
|
|| T =="host"
|
||||||
|| T == "user"
|
|| T == "user"
|
||||||
|| T == "pass"
|
|| T == "pass"
|
||||||
|| T == "path"
|
|| T == "path"
|
||||||
|| T == "query"
|
|| T == "query"
|
||||||
|| T == "fragment") && isSomeString!U)
|
|| T == "fragment")
|
||||||
{
|
{
|
||||||
auto ret = URL!U(source);
|
auto ret = URL(source);
|
||||||
return mixin("ret." ~ T);
|
return mixin("ret." ~ T);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ditto */
|
/// Ditto.
|
||||||
ushort parseURL(string T, U)(in U source)
|
ushort parseURL(immutable(char)[] T)(in char[] source)
|
||||||
if (T == "port" && isSomeString!U)
|
if (T == "port")
|
||||||
{
|
{
|
||||||
auto ret = URL!U(source);
|
auto ret = URL(source);
|
||||||
return ret.port;
|
return ret.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1158,7 +1156,7 @@ private unittest
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ushort port = parseURL!(Component.port)(t[0]);
|
ushort port = parseURL!(Component.port)(t[0]);
|
||||||
string component = parseURL!(Component.scheme)(t[0]);
|
auto component = parseURL!(Component.scheme)(t[0]);
|
||||||
assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null,
|
assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null,
|
||||||
t[0]);
|
t[0]);
|
||||||
component = parseURL!(Component.user)(t[0]);
|
component = parseURL!(Component.user)(t[0]);
|
||||||
|
Reference in New Issue
Block a user