198 Commits

Author SHA1 Message Date
278e851414 Rename String.toString to String.get()
Last changed it only by the Range and forgotten about the string itself.
2017-06-08 07:59:16 +02:00
6f549df243 Update README description 2017-06-07 08:04:50 +02:00
4633bcc680 Set: Fix comparing with removed elements 2017-06-07 07:57:22 +02:00
dc39efd316 Add some unit tests for InternetAddress 2017-06-03 15:18:53 +02:00
260937e4fb Put socket overlapped I/O docs into a D_Ddoc block 2017-06-03 13:20:32 +02:00
e17fff2881 Update 2.074 compiler 2017-06-02 22:01:13 +02:00
bc32511254 Fix template parameters for Set 2017-06-01 22:36:38 +02:00
f30972f948 Add basic constructors and opAssign to Set 2017-06-01 06:26:06 +02:00
ea33ca62c8 Implement lookups in the Set 2017-05-31 10:29:07 +02:00
0f365758e1 Add optional fieldnames for Pair 2017-05-30 20:20:20 +02:00
2815b53a88 Implement Set Range 2017-05-30 15:52:18 +02:00
6c0588164a Rename String.toString back to get()
Since it is expected that the return type of toString() is
immutable(char)[] and not char[] or const(char)[].
2017-05-29 11:41:49 +02:00
8ee1d647ce Close issue 212
Introduce Range and constRange aliases for containers.
2017-05-29 11:26:39 +02:00
25791775e6 Add information about the Set to README 2017-05-29 10:58:37 +02:00
f013e2f1f4 Implement a Set container first 2017-05-29 10:50:01 +02:00
ac3935d71b Merge branch 'master' into horton-table 2017-05-28 10:15:02 +02:00
b1c217e272 Fix kqueue to work with SocketType 2017-05-25 22:21:45 +02:00
d007aaa310 Rename socket_t to SocketType 2017-05-25 21:59:40 +02:00
8aaf9e14be Add HashTable struct 2017-05-23 22:17:35 +02:00
ae3e6b46f6 Import std.algorithm.comparison for network.socket on Windows 2017-05-21 10:25:54 +02:00
8687df1fbb Define AddressFamily in network.socket 2017-05-21 10:20:57 +02:00
ba0aff6737 Add tanya.typecons.Pair 2017-05-19 21:15:56 +02:00
a648e2120a Fix parameter count for docs in container.string 2017-05-19 20:01:04 +02:00
bc61809050 Implement DList.insertBack 2017-05-16 13:16:18 +02:00
8c42cbfd63 Rename Vector to Array
For consistency with Phobos.
2017-05-16 12:12:57 +02:00
58664570f9 Add new branch, add DList to package description 2017-05-15 20:09:32 +02:00
decb82f437 Remove crypto.mac for now
It wasn't released yet and needs some work.
2017-05-15 19:57:36 +02:00
357c7e279d Add doubly-linked list
DList is an adjusted copy of SList. Missing:
* insertBack
* insertAfter
* removeBack
2017-05-15 19:50:20 +02:00
32e19c8b58 Rename String.get into toString. Add String.toStringz 2017-05-14 11:56:57 +02:00
f5c6c5b483 Add Payload template for memory.types 2017-05-13 08:43:49 +02:00
ba2d086fb8 Add memory.types.Scoped 2017-05-13 08:27:51 +02:00
7a0241b484 Fix unittest text for strings 2017-05-12 22:42:43 +02:00
36dad80e18 Use char ranges to avoid compilation errors on elder compilers 2017-05-12 21:46:48 +02:00
29d883150e Fix unittests on 2.072 and 2.071 2017-05-12 21:10:22 +02:00
e2bed0cfcb Replace tabs with spaces in mmappool and buffer 2017-05-12 21:02:24 +02:00
38afeac071 Insert String.insertFront and String.insertBack 2017-05-12 20:55:42 +02:00
001c7c3e33 Replace immutable with const in Vector 2017-05-12 20:35:16 +02:00
d4ab339feb Add String.remove 2017-05-12 20:23:16 +02:00
8477312769 Add editorconfig 2017-05-11 13:57:24 +02:00
67f90e137d Add codecov badge 2017-05-11 13:15:04 +02:00
f264fd5597 Generate unittest coverage information 2017-05-11 13:11:12 +02:00
9e75620f1b Fix appveyor branch badges 2017-05-11 07:05:13 +02:00
45825946c0 Appveyor (#10)
* Add appveyor.yml

* Try major VC version

* Switch to VC 2015

* Try new version

* Try enterprise

* Try another path

* Change VC template

* Set arch

* Set LINKCMD64

* Fix quotes

* Update LINKCMD64

* remove dir

* Update arch

* Fix syntax

* Set arch to x64

* Remove extra dub downloading

* Remove dub version

* Download dub for 2.071.2

* Use DVersion

* Fix nul in powershell

* Put quotes to commands

* Add badges
2017-05-11 06:26:59 +02:00
8afb552d59 mp.Integer: add two's complement constructor 2017-05-10 19:27:25 +02:00
e4091669f8 Add information about io branch 2017-05-10 13:18:58 +02:00
1cb9349226 math.mp.Integer.toVector return two's complement 2017-05-09 06:27:30 +02:00
06620dc5df math.mp.Integer: Return two's complement length 2017-05-08 21:09:52 +02:00
708d95db49 Remove utf8string branch 2017-05-06 11:55:20 +02:00
85d9361bfb Fix fill with char on older compilers 2017-05-05 07:03:16 +02:00
a6a6f496eb Implement string slice assignments 2017-05-04 23:17:50 +02:00
db12f03264 Merge branch 'master' into utf8string 2017-05-03 19:15:13 +02:00
231aedb8ad Add HMAC 2017-05-03 19:05:23 +02:00
c3b63ee40d Merge branch 'master' into utf8string 2017-05-02 10:59:00 +02:00
6f405c5e08 Make Vector's opSliceAssign accept only own ranges
Vector.opSliceAssign and Vector.opIndexAssign should accept only vector
ranges. For assigning other ranges, std.algorithm.mutation.copy and
std.algorithm.mutation.fill should be used.
2017-05-02 10:56:32 +02:00
16cf8478cf Add ByCodePoint 2017-05-01 20:17:37 +02:00
8915a0c7a7 Implement opCmp and opEquals for the String 2017-05-01 18:43:12 +02:00
e5c7edb72c Implement String opAssign 2017-05-01 12:58:37 +02:00
64e0d666ed Merge branch 'master' of github.com:caraus-ecms/tanya into utf8string 2017-05-01 09:59:29 +02:00
f2aac680c5 Fix container ctors and opAssign ref parameters
Container constructors and opAssign should accept any ref container and
not only const, otherwise the source container will be copied because
the constructor/opAssign without ref would be a better match.
2017-05-01 09:48:12 +02:00
65c3ca14ec Integer storage optimization 2017-04-30 16:07:44 +02:00
4fa47153ba Make Integer representation little endian 2017-04-25 19:50:06 +02:00
d629525a4b Make String to be a char Slice alias 2017-04-21 14:03:20 +02:00
33d321f0d7 Merge branch 'master' into utf8string 2017-04-20 17:32:59 +02:00
3d64d59ba9 Merge branch 'master' of github.com:caraus-ecms/tanya 2017-04-20 17:32:29 +02:00
4635835a99 Rename Vector range to Slice 2017-04-20 17:32:16 +02:00
8725ec5f20 Make Integer representation little endian 2017-04-19 13:49:44 +02:00
9a4c8cea06 Merge branch 'master' into utf8string 2017-04-16 20:52:40 +02:00
eb360bda38 Add unittest to check RefCounted calles struct destructors 2017-04-16 20:52:24 +02:00
4b1cd2cbfd Merge branch 'master' into utf8string 2017-04-16 20:15:11 +02:00
628153e2e8 Make RefCounted work with dynamic arrays 2017-04-16 20:14:04 +02:00
7aa9ac9f4a Add internal finalize method for finalizing an object without deallocating 2017-04-16 20:13:20 +02:00
cd944a61b7 Merge remote-tracking branch 'origin/master' into utf8string 2017-04-13 16:03:00 +02:00
8156d0fe3a Add support for dmd 2.074.0, remove 2.070.2 2017-04-13 16:02:18 +02:00
47ef787353 Add missing constructors to the String 2017-04-10 08:10:08 +02:00
6436ad49df Add ByteRange to the String 2017-04-08 17:44:08 +02:00
e1964e47a5 Merge branch 'master' into utf8string 2017-04-08 08:44:21 +02:00
6e2ce5d686 Remove opApply from containers
opApply requires additional overloads for the const containers (with a
const delegate). If using a templated opApply foreach cannot infer the
types for the variables. foreach with one argument still works
(opIndex() is used), for more complex cases slicing should be used.
2017-04-07 16:00:50 +02:00
ba6bf554fb Make SList range public 2017-04-07 15:17:14 +02:00
b1d2b9bd9e Fix Vector.insertAfter/Before an empty range 2017-04-04 15:11:14 +02:00
9b953198fa Fix network.inet release build 2017-04-04 08:36:42 +02:00
bc2a6d2703 Swap toHostOrder template parameters 2017-04-03 15:32:15 +02:00
b458250ad7 Make NetworkOrder work with 8-byte integers 2017-04-02 20:55:22 +02:00
b08d5e5d83 Add tanya.network.inet.toHostOrder
The function reverts NetworkOrder.
2017-04-02 11:16:08 +02:00
445b872e91 Add tanya.network.inet.NetworkOrder
NetworkOrder converts an integral type into a bidirectional range with
big-endian byte order.
2017-04-02 09:29:54 +02:00
5e16fe98d6 Add tanya.network package file 2017-04-01 09:53:59 +02:00
647cfe03c2 Update latest supported compiler 2017-03-29 17:23:10 +02:00
4cd6126d6b Fix SList documentation for insertFront and insertBefore 2017-03-29 17:22:25 +02:00
b870179a35 Move bitvector to another branch till it is finished 2017-03-29 11:17:03 +02:00
aabb4fb534 Add SList.opAssign 2017-03-29 10:35:45 +02:00
4d8b95812e Implement opAssign for the Vector 2017-03-28 20:42:42 +02:00
e921413249 Merge branch 'master' of github.com:caraus-ecms/tanya 2017-03-24 20:54:47 +01:00
49cae88645 Add insertBefore and remove to SList 2017-03-24 20:54:28 +01:00
402fdfae89 math.mp: Fix initialization issues after resizing 2017-03-23 15:36:17 +01:00
7892c1a930 Remove Init template parameter from memory.resize() 2017-03-22 08:51:00 +01:00
b90517580e Merge math.mp.Integer changes from the crypto branch 2017-03-21 19:25:12 +01:00
85380ac3fc Remove makeArray import 2017-03-19 06:54:59 +01:00
b90c56395c Remove resizeArray alias 2017-03-19 06:10:27 +01:00
d0ada39fa7 Add Mallocator as an alternative allocator 2017-03-18 08:07:01 +01:00
f4145abfd1 Add SList constructors 2017-03-09 06:07:23 +01:00
093d499729 Fix element order inserted from a range into list 2017-03-08 07:12:23 +01:00
f90a03501b Move BitVector from the crypto branch 2017-03-02 11:27:26 +01:00
c6a99b114e SList.insertFront for ranges 2017-03-01 19:23:54 +01:00
43319e4e3a Initialization from a UTF-16 string 2017-02-27 11:27:24 +01:00
33dbf042c2 Add dchar constructor 2017-02-26 22:40:27 +01:00
3c23aca6a6 Improve Vector module and reserve documentation 2017-02-20 12:03:49 +01:00
885fca9b5e Add String.reserve and shrink 2017-02-20 12:01:15 +01:00
074d027629 Merge branch 'master' into utf8string 2017-02-20 08:02:01 +01:00
70632d975d Add documentation link 2017-02-18 16:35:06 +01:00
d210a39249 Implement IOCPTransport.close and isClosing 2017-02-18 14:10:54 +01:00
e86ff63f91 Add DuplexTransport.close for the selector transport 2017-02-12 18:51:00 +01:00
3454a1965a Move all IOWatcher members to the transports 2017-02-11 19:47:57 +01:00
c41fa2e98f Adjust kqueue build 2017-02-10 23:01:33 +01:00
a012ca4003 Remove StreamTransport interface
Implement DuplexTransport and SocketTransport separately.
2017-02-10 22:30:12 +01:00
f4b90d8b51 Add string skeleton 2017-02-10 19:22:46 +01:00
b74e5aa4ee Make event loop implementations final 2017-02-10 19:19:37 +01:00
44ac15ab78 Move the bug tracker to Redmine 2017-02-10 17:28:55 +01:00
b1b652b943 Fix Kqueue buil with the new watcher-transport 2017-02-09 21:40:52 +01:00
530a482402 Inherit IOCPTransport from IOWatcher 2017-02-09 21:40:52 +01:00
f9023cf0ab Let Transport extend IOWatcher 2017-02-09 21:40:52 +01:00
0e91ea6786 Pass client socket directly to the IOWatcher 2017-02-09 21:40:52 +01:00
63c6226a2a Implement protocol property for IOCPTransport 2017-02-08 21:21:12 +01:00
48a49c2a2d Add protocol property to the transport
Transport should be protocol aware because it should be possible to
switch the protocol if the operation is supported, for example for upgrading
HTTP to web sockets or HTTP 1.1 to HTTP/2.
2017-02-08 20:04:05 +01:00
43c28b749d Rename async.loop.Loop.done_ to done
Since there is no property with a conflicting name.
2017-02-04 14:55:52 +01:00
241767df13 Move DefaultAllocator mixin to tanya.memory
Since it depends on defaultAllocator property defined in the module.
2017-02-03 13:07:40 +01:00
b2baba9237 SList: Add length and opEquals 2017-01-25 19:41:05 +01:00
3e36ec0984 Add support for dmd 2.070.2 2017-01-25 07:24:19 +01:00
5be89e4858 Add support for dmd 2.073.0 2017-01-24 16:15:14 +01:00
a48d9cb739 Add range support for SList 2017-01-24 08:20:07 +01:00
a7206cbd02 Fix #4 2017-01-22 10:48:34 +01:00
1450a6adfe Vector.insertBack: Accept by value and by ref 2017-01-20 05:40:28 +01:00
5fa9bd7b49 Rename Allocator.expand to reallocateInPlace
Rename and extend Allocator.expand to reallocateInPlace. The problem is
that realloc for example doesn't guarante that the shrinking of the
memory block won't cause address change. So not only expanding should
have "in place" version, but the shrinking as well.
2017-01-18 09:33:39 +01:00
c7eb233fc7 Make passed length parameter const 2017-01-16 10:56:45 +01:00
20c8b659d1 Remove mutation methods from vector range
in favor of std.algorithm.mutation.
2017-01-16 09:02:00 +01:00
4ea9c2b740 Vector: Reuse available methods 2017-01-15 08:38:19 +01:00
48205b2fc9 MmapPool: Add invariant
Add invariant to ensure blocks are linked correctly since this error
appeared several times.
2017-01-14 21:48:21 +01:00
f5fe7bec4a Queue optimization. Fix #5 2017-01-14 21:27:07 +01:00
c567b88d5d MmapPool: Fix expand block moving.
D dereferences the pointer wrong because of missing difference between .
and -> operators, if trying to write a block over another
block. So use memmove first to move the memory and then update the fields
that should be changed (only size).
2017-01-14 20:39:33 +01:00
fe884541fc Rename Vector.data to Vector.get 2017-01-13 15:23:42 +01:00
8973bdb2af Fix if EPOLLIN and EPOLLOUT come together 2017-01-13 10:20:11 +01:00
4c4e65b373 MmapPool: (p[] is null) != (p[].ptr is null) 2017-01-12 19:47:07 +01:00
7bed7f039f Remove default parameter value from Protocol.disconnected 2017-01-12 18:07:39 +01:00
8ddea0aa46 Loop.maxEvents is const, not inout const 2017-01-12 10:43:02 +01:00
cb6cc65113 async: Switch to the internal use of the vector instead of built-in arrays 2017-01-12 10:17:12 +01:00
4de42ca227 Use only one queue for the async events 2017-01-12 09:09:33 +01:00
ab930657b6 Queue: Leave only enqueue/dequeue/empty/opApply 2017-01-11 18:24:50 +01:00
291920b479 Vector constructors for initializing from a vector 2017-01-10 06:34:53 +01:00
999c9bdb0f Vector: remove core.stdc.string import 2017-01-09 19:52:39 +01:00
405b6d9f9f Accept only ranges for slicing assignment 2017-01-09 19:32:51 +01:00
87b74b2542 Fix reallocating the vector 2017-01-09 17:03:09 +01:00
d6514cb515 Fix Ddoc 2017-01-07 17:53:57 +01:00
976eb4bfbc Add downloads button 2017-01-07 15:25:05 +01:00
fb843e3473 Fix #3 2017-01-07 09:30:42 +01:00
f3d48234c0 MmapPool: add expand and empty methods. 2017-01-06 23:12:19 +01:00
254b881da6 Fix block size calculation 2017-01-06 11:56:54 +01:00
8e0b742748 MmapPool: Merge blocks on deallocation if possible 2017-01-05 14:25:54 +01:00
a35e04c049 Don't throw in the allocator, return null 2017-01-05 07:42:23 +01:00
4271c8583e Remove static constructor from the MmapPool 2017-01-05 07:35:29 +01:00
e27d0fe58c Fix Vector.remove not destroying from the end copied elements 2017-01-04 20:37:55 +01:00
67952dabdb Implement Vector.remove 2017-01-03 13:21:19 +01:00
b8d5d4c2bd Fix template condition for Vector.insertBack 2017-01-03 10:03:28 +01:00
b6413823cd Add opEquals for all combinations of vector ranges 2017-01-02 17:33:01 +01:00
48e355b87f Vector: allow insert multiple items in insertBack 2017-01-02 12:47:41 +01:00
b3f4ea572e Vector: Use opEquals if defined to compare items 2017-01-02 06:59:05 +01:00
c73e704421 Fix constness of Vector range, optimizing 2017-01-01 02:51:49 +01:00
0561e96f21 Fix build with 2.071.2 2016-12-28 07:57:36 +01:00
86d87430da Fix socket build on Windows 2016-12-25 12:54:04 +01:00
0156c5a883 Don't allocate watcher queue on the heap 2016-12-25 00:54:05 +01:00
c966b42ac3 Fix FreeBSD build 2016-12-24 22:25:34 +01:00
200fff3714 Fix #1 2016-12-22 22:05:48 +01:00
28755b4d01 Rename module traits into enums 2016-12-22 22:05:06 +01:00
8bd6a14988 Fix issue going out of the range with back() 2016-12-22 22:01:45 +01:00
b41dcc9f37 Fix compatibility issue with dmd 2.071 2016-12-22 22:01:11 +01:00
38addb7a5b Add support for pow for big integers 2016-12-22 21:51:16 +01:00
f7fb89fed0 Move random.d into math submodule 2016-12-22 21:50:33 +01:00
e32af2d09e Add scalar type template parameter for buffers 2016-12-19 21:24:28 +01:00
f1bc4dc2e2 Add length and opCmp to the Queue 2016-12-19 16:33:16 +01:00
40857e69b7 Add capacity capabilities to the vector 2016-12-18 18:48:25 +01:00
c1fb89af99 Implement insertion into the vector 2016-12-15 15:00:06 +01:00
061cd6264b Use auto ref for templated overloaded functions 2016-12-13 10:59:05 +01:00
f437dafa6b Fix dispose for structs 2016-12-13 10:58:11 +01:00
54d0597657 Use resizeArray instead of expand/shrinkArray 2016-12-13 10:57:12 +01:00
ab9f96e0c7 Replace class Queue with the struct Queue 2016-12-13 10:56:29 +01:00
711855474c Remove unused buffer interface 2016-12-13 10:54:27 +01:00
b20f367aa8 Array support for refCounted factory function 2016-12-11 11:42:09 +01:00
a2dadda511 Fix subtraction of numbers with different signs 2016-12-08 18:30:22 +01:00
77dca31261 Add license info 2016-12-08 15:07:58 +01:00
b87aed4395 Add travis to README 2016-12-08 15:00:09 +01:00
42bbb3b023 Add travis 2016-12-08 14:58:59 +01:00
4309a30dfe Add opBinary for the other math operations on Integer 2016-12-08 14:51:49 +01:00
9362287938 Fix error with assignin long numbers to Integer 2016-12-08 14:43:50 +01:00
78bd901339 Add short description of the packages to the README 2016-12-07 23:16:49 +01:00
c8e6d44f7b Implement own dispose 2016-12-07 11:01:51 +01:00
f75433e0e6 Implement operations on negative numbers 2016-12-06 23:22:12 +01:00
fa607141e4 Make allocator shared and fix some RefCounted bugs 2016-12-06 21:29:08 +01:00
b3fdd6fd4a Implement unary operation for multiple precision integers 2016-12-05 22:06:06 +01:00
86c08e7af6 Use RefCounted as math.mp.Integer internal storage 2016-12-04 22:51:21 +01:00
1c5796eb96 Add RefCounted 2016-12-04 14:05:53 +01:00
f7f92e7906 Switch to container.queue. Remove PendingQueue 2016-12-02 19:18:37 +01:00
43 changed files with 11655 additions and 5144 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

5
.gitignore vendored
View File

@ -4,3 +4,8 @@
# D # D
.dub .dub
__test__*__ __test__*__
__test__*__.core
/tanya-test-library
/docs/
/docs.json

23
.travis.yml Normal file
View File

@ -0,0 +1,23 @@
sudo: false
os:
- linux
- osx
language: d
d:
- dmd-2.074.1
- dmd-2.073.2
- dmd-2.072.2
- dmd-2.071.2
env:
matrix:
- ARCH=x86_64
script:
- dub test -b unittest-cov --arch=$ARCH
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -1,4 +1,68 @@
# tanya # Tanya
tanya's mission is to provide a GC-free, general purpose library for D [![Build status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya)
programming language for developing servers. [![Build status](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/master?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/master)
[![codecov](https://codecov.io/gh/caraus-ecms/tanya/branch/master/graph/badge.svg)](https://codecov.io/gh/caraus-ecms/tanya)
[![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](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/issues)
* [Documentation](https://docs.caraus.io/tanya)
## Overview
Tanya consists of the following packages:
* `async`: Event loop (epoll, kqueue and IOCP).
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
string, Hash set.
* `math`: Arbitrary precision integer and a set of functions.
* `memory`: Tools for manual memory management (allocators, smart pointers).
* `network`: URL-Parsing, sockets, utilities.
### Supported compilers
| dmd |
|:-------:|
| 2.074.1 |
| 2.073.2 |
| 2.072.2 |
| 2.071.2 |
### Current status
Following modules are under development:
| Feature | Branch | Build status |
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| BitArray | bitvector | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=bitvector)](https://travis-ci.org/caraus-ecms/tanya) [![bitvector](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/bitvector?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
| TLS | crypto | [![crypto](https://travis-ci.org/caraus-ecms/tanya.svg?branch=crypto)](https://travis-ci.org/caraus-ecms/tanya) [![crypto](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/crypto?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
| File IO | io | [![io](https://travis-ci.org/caraus-ecms/tanya.svg?branch=io)](https://travis-ci.org/caraus-ecms/tanya) [![io](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/io?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
### Further characteristics
* Tanya is a native D library without any external dependencies.
* 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 yet.
## Release management
3-week release cycle.
Deprecated features are removed after one release (in approximately 6 weeks after deprecating).
## Contributing
Feel free to contact me if you have any questions: info@caraus.de.

52
appveyor.yml Normal file
View File

@ -0,0 +1,52 @@
platform: x64
os: Visual Studio 2017
environment:
matrix:
- DC: dmd
DVersion: 2.074.1
arch: x86
- DC: dmd
DVersion: 2.073.2
arch: x86
- DC: dmd
DVersion: 2.072.2
arch: x86
- DC: dmd
DVersion: 2.071.2
arch: x86
skip_tags: true
install:
- ps: function SetUpDCompiler
{
$env:toolchain = "msvc";
$version = $env:DVersion;
Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z";
echo "finished.";
pushd c:\\;
7z x dmd.7z > $null;
popd;
}
- ps: SetUpDCompiler
- ps: if($env:DVersion -eq "2.071.2"){
Invoke-WebRequest "http://code.dlang.org/files/dub-1.2.1-windows-x86.zip" -OutFile "dub.zip";
7z x dub.zip -odub > $null;
Move-Item "dub/dub.exe" "C:\dmd2\windows\bin"
}
before_build:
- ps: $env:PATH += ";C:\dmd2\windows\bin;";
- call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=%arch%
build_script:
- echo dummy build script - dont remove me
test_script:
- echo %DC%
- echo %PATH%
- 'dub --version'
- '%DC% --version'
- dub test --arch=x86 --compiler=%DC%

View File

@ -1,6 +1,6 @@
{ {
"name": "tanya", "name": "tanya",
"description": "D library with event loop", "description": "General purpose, @nogc library. Containers, networking, memory management, utilities",
"license": "MPL-2.0", "license": "MPL-2.0",
"copyright": "(c) Eugene Wissner <info@caraus.de>", "copyright": "(c) Eugene Wissner <info@caraus.de>",
"authors": [ "authors": [

View File

@ -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.array;
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 Array!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 = Array!epoll_event(maxEvents, MmapPool.instance);
} }
/** /**
* Free loop internals. * Frees loop internals.
*/ */
~this() ~this() @nogc
{ {
MmapPool.instance.dispose(events);
close(fd); close(fd);
} }
@ -57,18 +64,15 @@ class EpollLoop : SelectorLoop
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* 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)
{ {

View File

@ -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,66 +26,146 @@ 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;
} }
/** /**
* Write some data to the transport. * Write some data to the transport.
* *
* Params: * Params:
* data = Data to send. * data = Data to send.
*/ */
void write(ubyte[] data) void write(ubyte[] data) @nogc
{ {
immutable empty = input.length == 0;
input ~= data; input ~= data;
if (empty) }
/**
* 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)
{ {
SocketState overlapped; immutable empty = input.length == 0;
try protocol.received(output[0 .. $]);
output.clear();
if (empty)
{ {
overlapped = MmapPool.instance.make!SocketState; SocketState overlapped;
socket.beginSend(input[], overlapped); try
} {
catch (SocketException e) overlapped = MmapPool.instance.make!SocketState;
{ socket.beginSend(input[], overlapped);
MmapPool.instance.dispose(overlapped); }
MmapPool.instance.dispose(e); catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
MmapPool.instance.dispose(e);
}
} }
} }
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");
} }
} }
@ -109,15 +190,15 @@ class IOCPLoop : Loop
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* 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:

View File

@ -3,106 +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: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 = MissingKevent;
}
else version (iOS)
{
version = MissingKevent;
}
else version (TVOS)
{
version = MissingKevent;
}
else version (WatchOS)
{
version = MissingKevent;
}
else version (OpenBSD)
{
version = MissingKevent;
}
else version (DragonFlyBSD)
{
version = MissingKevent;
}
version (MissingKevent)
{
extern (C):
nothrow:
@nogc:
import core.stdc.stdint; // intptr_t, uintptr_t
import core.sys.posix.time; // timespec
enum : short
{
EVFILT_READ = -1,
EVFILT_WRITE = -2,
EVFILT_AIO = -3, /* attached to aio requests */
EVFILT_VNODE = -4, /* attached to vnodes */
EVFILT_PROC = -5, /* attached to struct proc */
EVFILT_SIGNAL = -6, /* attached to struct proc */
EVFILT_TIMER = -7, /* timers */
EVFILT_MACHPORT = -8, /* Mach portsets */
EVFILT_FS = -9, /* filesystem events */
EVFILT_USER = -10, /* User events */
EVFILT_VM = -12, /* virtual memory events */
EVFILT_SYSCOUNT = 11
}
extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args)
{
*kevp = kevent_t(args);
}
struct kevent_t
{
uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */
ushort flags;
uint fflags;
intptr_t data;
void *udata; /* opaque user data identifier */
}
enum
{
/* actions */
EV_ADD = 0x0001, /* add event to kq (implies enable) */
EV_DELETE = 0x0002, /* delete event from kq */
EV_ENABLE = 0x0004, /* enable event */
EV_DISABLE = 0x0008, /* disable event (not reported) */
/* flags */
EV_ONESHOT = 0x0010, /* only report one occurrence */
EV_CLEAR = 0x0020, /* clear event state after reporting */
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
EV_DISPATCH = 0x0080, /* disable event after reporting */
EV_SYSFLAGS = 0xF000, /* reserved by system */
EV_FLAG1 = 0x2000, /* filter-specific flag */
/* returned values */
EV_EOF = 0x8000, /* EOF detected */
EV_ERROR = 0x4000, /* error, data contains errno */
}
int kqueue();
int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents,
const timespec *timeout);
}
version (OSX) version (OSX)
{ {
version = MacBSD; version = MacBSD;
@ -111,10 +18,17 @@ else version (iOS)
{ {
version = MacBSD; version = MacBSD;
} }
else version (TVOS)
{
version = MacBSD;
}
else version (WatchOS)
{
version = MacBSD;
}
else version (FreeBSD) else version (FreeBSD)
{ {
version = MacBSD; version = MacBSD;
public import core.sys.freebsd.sys.event;
} }
else version (OpenBSD) else version (OpenBSD)
{ {
@ -127,62 +41,121 @@ else version (DragonFlyBSD)
version (MacBSD): version (MacBSD):
import core.stdc.errno;
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.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.array;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool; import tanya.memory.mmappool;
import tanya.network.socket; 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 void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
{
*kevp = kevent_t(args);
}
enum : short
{
EVFILT_READ = -1,
EVFILT_WRITE = -2,
EVFILT_AIO = -3, /* attached to aio requests */
EVFILT_VNODE = -4, /* attached to vnodes */
EVFILT_PROC = -5, /* attached to struct proc */
EVFILT_SIGNAL = -6, /* attached to struct proc */
EVFILT_TIMER = -7, /* timers */
EVFILT_MACHPORT = -8, /* Mach portsets */
EVFILT_FS = -9, /* filesystem events */
EVFILT_USER = -10, /* User events */
EVFILT_VM = -12, /* virtual memory events */
EVFILT_SYSCOUNT = 11
}
struct kevent_t
{
uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */
ushort flags;
uint fflags;
intptr_t data;
void *udata; /* opaque user data identifier */
}
enum
{
/* actions */
EV_ADD = 0x0001, /* add event to kq (implies enable) */
EV_DELETE = 0x0002, /* delete event from kq */
EV_ENABLE = 0x0004, /* enable event */
EV_DISABLE = 0x0008, /* disable event (not reported) */
/* flags */
EV_ONESHOT = 0x0010, /* only report one occurrence */
EV_CLEAR = 0x0020, /* clear event state after reporting */
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
EV_DISPATCH = 0x0080, /* disable event after reporting */
EV_SYSFLAGS = 0xF000, /* reserved by system */
EV_FLAG1 = 0x2000, /* filter-specific flag */
/* returned values */
EV_EOF = 0x8000, /* EOF detected */
EV_ERROR = 0x4000, /* error, data contains errno */
}
extern(C) int kqueue() nothrow @nogc;
extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents, const timespec *timeout)
nothrow @nogc;
final class KqueueLoop : SelectorLoop
{ {
protected int fd; protected int fd;
private kevent_t[] events; private Array!kevent_t events;
private kevent_t[] changes; private Array!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 = Array!kevent_t(64, MmapPool.instance);
changes = MmapPool.instance.makeArray!kevent_t(64); changes = Array!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(SocketType 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,
@ -198,15 +171,15 @@ class KqueueLoop : SelectorLoop
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* 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");
} }
@ -326,14 +301,15 @@ class KqueueLoop : SelectorLoop
* be handled by the event loop. * be handled by the event loop.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* exception = Exception thrown on sending. * exception = Exception thrown on sending.
* *
* 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))
{ {

View File

@ -3,75 +3,172 @@
* 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.array;
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;
/** /**
* 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;
}
} }
/** /**
* Write some data to the transport. * Write some data to the transport.
* *
* 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 +208,60 @@ class SelectorStreamTransport : StreamTransport
abstract class SelectorLoop : Loop abstract class SelectorLoop : Loop
{ {
/// Pending connections. /// Pending connections.
protected ConnectionWatcher[] connections; protected Array!SocketWatcher connections;
this() this() @nogc
{ {
super(); super();
connections = MmapPool.instance.makeArray!ConnectionWatcher(maxEvents); connections = Array!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
// user and should be freed by himself. // created by the 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);
} }
/** /**
@ -140,14 +269,20 @@ abstract class SelectorLoop : Loop
* be handled by the event loop. * be handled by the event loop.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* exception = Exception thrown on sending. * exception = Exception thrown on sending.
* *
* 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 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 +306,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;
} }
@ -184,9 +320,9 @@ abstract class SelectorLoop : Loop
* Start watching. * Start watching.
* *
* 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 +331,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;
@ -206,9 +342,9 @@ abstract class SelectorLoop : Loop
* Accept incoming connections. * Accept incoming connections.
* *
* 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 +360,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 +368,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);
} }
} }
} }

View File

@ -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;

View File

@ -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,60 +147,63 @@ 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;
} }
/** /**
* Start watching. * Start watching.
* *
* 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));
} }
@ -203,9 +211,9 @@ abstract class Loop
* Stop watching. * Stop watching.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
*/ */
void stop(ConnectionWatcher watcher) void stop(ConnectionWatcher watcher) @nogc
{ {
if (!watcher.active) if (!watcher.active)
{ {
@ -220,34 +228,34 @@ abstract class Loop
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* 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;
} }
/** /**
* Sets the blocking time for IO watchers. * Sets the blocking time for IO watchers.
* *
* Params: * Params:
* 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,15 +280,14 @@ 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.
* line = The line number where the exception occurred. * line = The line number where the exception occurred.
* 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)
{ {
@ -339,9 +331,9 @@ class BadLoopException : Exception
* your implementation to this property. * your implementation to this property.
* *
* 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;
}

View File

@ -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;

View File

@ -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;
@ -20,26 +20,26 @@ 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.
* *
* 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.
* *
* Params: * Params:
* 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;
} }
/** /**

View File

@ -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;
/** /**
@ -35,9 +36,9 @@ interface WriteTransport : Transport
* Write some data to the transport. * Write some data to the 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; /**
} * Returns: Socket.
*/
/** @property Socket socket() pure nothrow @safe @nogc;
* Represents a connection-oriented socket transport.
*/
package interface StreamTransport : DuplexTransport, SocketTransport
{
} }

View File

@ -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_;
} }
}
/**
* Connection watcher.
*/
class ConnectionWatcher : SocketWatcher
{
/// Incoming connection queue.
Queue!DuplexTransport incoming;
private Protocol delegate() @nogc protocolFactory;
/** /**
* Returns: New protocol instance. * Params:
* socket = Socket.
*/ */
@property Protocol protocol() 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)
{ {
io.protocol.connected(cast(DuplexTransport) io.transport); transport.protocol = protocolFactory();
} transport.protocol.connected(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;
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
}

View File

@ -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
{ {
@ -119,43 +79,54 @@ class ReadBuffer : Buffer
* Creates a new read buffer. * Creates a new read buffer.
* *
* 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 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,30 +163,28 @@ 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);
} }
/** /**
* Appends some data to the buffer. * Appends some data to the buffer.
* *
* Params: * Params:
* length = Number of the bytes read. * length = Number of the bytes read.
* *
* 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;
@ -232,7 +203,7 @@ class ReadBuffer : Buffer
numberRead = fillBuffer(b[], b.free, 0, 10); numberRead = fillBuffer(b[], b.free, 0, 10);
b += numberRead; b += numberRead;
result = b[0..$]; result = b[0 .. $];
assert(result[0] == 0); assert(result[0] == 0);
assert(result[1] == 1); assert(result[1] == 1);
assert(result[9] == 9); assert(result[9] == 9);
@ -251,26 +222,16 @@ 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;
} }
/** /**
* Params: * Params:
* start = Start position. * start = Start position.
* end = End position. * end = End position.
* *
* 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,11 +243,11 @@ 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)
{ {
auto ret = buffer_[0..start]; auto ret = buffer_[0 .. start];
ring = 0; ring = 0;
return ret; return ret;
} }
@ -294,17 +255,22 @@ 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_ .. $];
} }
} }
/// ///
unittest unittest
{ {
auto b = theAllocator.make!ReadBuffer; ReadBuffer!ubyte b;
size_t numberRead; size_t numberRead;
ubyte[] result; ubyte[] result;
@ -313,86 +279,107 @@ class ReadBuffer : Buffer
b += numberRead; b += numberRead;
assert(b.length == 10); assert(b.length == 10);
result = b[0..$]; result = b[0 .. $];
assert(result[0] == 0); assert(result[0] == 0);
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;
@ -465,7 +445,7 @@ class WriteBuffer : Buffer
end = afterRing; end = afterRing;
} }
start = end - position; start = end - position;
buffer_[position..end] = buffer[0..start]; buffer_[position .. end] = buffer[0 .. start];
if (end == afterRing) if (end == afterRing)
{ {
position = this.start == 0 ? afterRing : 0; position = this.start == 0 ? afterRing : 0;
@ -485,7 +465,7 @@ class WriteBuffer : Buffer
end = this.start; end = this.start;
} }
auto areaEnd = end - position + start; auto areaEnd = end - position + start;
buffer_[position..end] = buffer[start..areaEnd]; buffer_[position .. end] = buffer[start .. areaEnd];
position = end == this.start ? ring + 1 : end - position; position = end == this.start ? ring + 1 : end - position;
start = areaEnd; start = areaEnd;
} }
@ -496,11 +476,14 @@ 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;
if (this.start == 0) if (this.start == 0)
{ {
@ -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,19 +566,21 @@ 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)
{ {
buffer_[start.. start + overflow] = buffer_[afterRing..position]; buffer_[start .. start + overflow] = buffer_[afterRing .. position];
position -= overflow; position -= overflow;
} }
else else
{ {
buffer_[start.. start + overflow] = buffer_[afterRing..position]; buffer_[start .. start + overflow] = buffer_[afterRing .. position];
position = overflow; position = overflow;
} }
start += length; start += 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,62 +622,67 @@ 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;
if (position > ring || position < start) // Buffer overflowed if (position > ring || position < start) // Buffer overflowed
{ {
return buffer_[this.start.. ring + 1 - length + end]; return buffer_[this.start .. ring + 1 - length + end];
} }
else else
{ {
return buffer_[this.start.. this.start + end]; return buffer_[this.start .. this.start + end];
} }
} }
/// ///
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;
assert(b[0..$] == buf[0..6]); assert(b[0 .. $] == buf[0 .. 6]);
b += 2; b += 2;
assert(b[0..$] == buf[2..6]); assert(b[0 .. $] == buf[2 .. 6]);
b ~= buf; b ~= buf;
assert(b[0..$] == buf[2..6]); assert(b[0 .. $] == buf[2 .. 6]);
b += b.length; b += b.length;
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))));
} }

View File

@ -0,0 +1,111 @@
/* 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/. */
/*
* 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/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.container.entry;
import std.traits;
import tanya.typecons;
package struct SEntry(T)
{
// Item content.
T content;
// Next item.
SEntry* next;
}
package struct DEntry(T)
{
// Item content.
T content;
// Previous and next item.
DEntry* next, prev;
}
package struct HashEntry(K, V)
{
this(ref K key, ref V value)
{
this.pair = Pair!(K, V)(key, value);
}
Pair!(K, V) pair;
HashEntry* next;
}
package enum BucketStatus : byte
{
deleted = -1,
empty = 0,
used = 1,
}
package struct Bucket(T)
{
this(ref T content)
{
this.content = content;
}
@property void content(ref T content)
{
this.content_ = content;
this.status = BucketStatus.used;
}
@property ref inout(T) content() inout
{
return this.content_;
}
bool opEquals(ref T content)
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
}
bool opEquals(ref T content) const
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
}
bool opEquals(ref typeof(this) that)
{
return this.content == that.content && this.status == that.status;
}
bool opEquals(ref typeof(this) that) const
{
return this.content == that.content && this.status == that.status;
}
void remove()
{
static if (hasElaborateDestructor!T)
{
destroy(this.content);
}
this.status = BucketStatus.deleted;
}
T content_;
BucketStatus status = BucketStatus.empty;
}

File diff suppressed because it is too large Load Diff

View File

@ -3,15 +3,40 @@
* 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. * Abstract data types whose instances are collections of other objects.
*
* 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.array;
public import tanya.container.buffer; public import tanya.container.buffer;
public import tanya.container.set;
public import tanya.container.list; public import tanya.container.list;
public import tanya.container.vector; public import tanya.container.string;
public import tanya.container.queue; public import tanya.container.queue;
/**
* Thrown if $(D_PSYMBOL Set) cannot insert a new element because the container
* is full.
*/
class HashContainerFullException : Exception
{
/**
* Params:
* msg = The message for the exception.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc @safe pure nothrow
{
super(msg, file, line, next);
}
}

View File

@ -3,290 +3,286 @@
* 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. * FIFO queue.
*
* 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). * Removes all elements from the queue.
* */
* Params: ~this()
* allocator = The allocator should be used for the element {
* allocations. while (!empty)
*/ {
this(IAllocator allocator = theAllocator) dequeue();
{ }
this.allocator = allocator; }
}
/** /**
* Removes all elements from the queue. * Returns how many elements are in the queue. It iterates through the queue
*/ * to count the elements.
~this() *
{ * Returns: How many elements are in the queue.
clear(); */
} size_t length() const
{
size_t len;
for (const(SEntry!T)* i = first; i !is null; i = i.next)
{
++len;
}
return len;
}
/** ///
* Removes all elements from the queue. unittest
*/ {
void clear() Queue!int q;
{
while (!empty)
{
popFront();
}
}
/// assert(q.length == 0);
unittest q.enqueue(5);
{ assert(q.length == 1);
auto q = theAllocator.make!(Queue!int); q.enqueue(4);
assert(q.length == 2);
q.enqueue(9);
assert(q.length == 3);
assert(q.empty); q.dequeue();
q.insertBack(8); assert(q.length == 2);
q.insertBack(9); q.dequeue();
q.clear(); assert(q.length == 1);
assert(q.empty); q.dequeue();
assert(q.length == 0);
}
theAllocator.dispose(q); private void enqueueEntry(ref SEntry!T* entry)
} {
if (empty)
{
first = rear = entry;
}
else
{
rear.next = entry;
rear = rear.next;
}
}
/** private SEntry!T* allocateEntry()
* Returns: First element. {
*/ auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
@property ref inout(T) front() inout if (temp is null)
in {
{ onOutOfMemoryError();
assert(!empty); }
} return temp;
body }
{
return first.next.content;
}
/** /**
* Inserts a new element. * Inserts a new element.
* *
* 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.content = x;
if (empty) *temp = SEntry!T.init;
{ temp.content = x;
first.next = rear = temp;
}
else
{
rear.next = temp;
rear = rear.next;
}
}
/// Ditto. enqueueEntry(temp);
alias insert = insertBack; }
/// /// Ditto.
unittest void enqueue(T x)
{ {
auto q = make!(Queue!int)(theAllocator); auto temp = allocateEntry();
assert(q.empty); moveEmplace(x, (*temp).content);
q.insertBack(8); (*temp).next = null;
assert(q.front == 8);
q.insertBack(9);
assert(q.front == 8);
dispose(theAllocator, q); enqueueEntry(temp);
} }
/** ///
* Returns: $(D_KEYWORD true) if the queue is empty. unittest
*/ {
@property bool empty() inout const Queue!int q;
{
return first.next is null;
}
/// assert(q.empty);
unittest q.enqueue(8);
{ q.enqueue(9);
auto q = make!(Queue!int)(theAllocator); assert(q.dequeue() == 8);
int value = 7; assert(q.dequeue() == 9);
}
assert(q.empty); /**
q.insertBack(value); * Returns: $(D_KEYWORD true) if the queue is empty.
assert(!q.empty); */
@property bool empty() const
{
return first is null;
}
dispose(theAllocator, q); ///
} unittest
{
Queue!int q;
int value = 7;
/** assert(q.empty);
* Move the position to the next element. q.enqueue(value);
*/ assert(!q.empty);
void popFront() }
in
{
assert(!empty);
}
body
{
auto n = first.next.next;
dispose(allocator, first.next); /**
first.next = n; * Move the position to the next element.
} *
* Returns: Dequeued element.
*/
T dequeue()
in
{
assert(!empty);
}
body
{
auto n = first.next;
T ret = move(first.content);
/// allocator.dispose(first);
unittest first = n;
{ return ret;
auto q = make!(Queue!int)(theAllocator); }
q.insertBack(8); ///
q.insertBack(9); unittest
assert(q.front == 8); {
q.popFront(); Queue!int q;
assert(q.front == 9);
dispose(theAllocator, q); q.enqueue(8);
} q.enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/** /**
* $(D_KEYWORD foreach) iteration. * $(D_KEYWORD foreach) iteration. The elements will be automatically
* * dequeued.
* Params: *
* dg = $(D_KEYWORD foreach) body. * Params:
*/ * dg = $(D_KEYWORD foreach) body.
int opApply(scope int delegate(ref size_t i, ref T) dg) *
{ * Returns: The value returned from $(D_PARAM dg).
int result; */
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
{
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;
} }
/// ///
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);
assert(i != 1 || e == 4); assert(i != 1 || e == 4);
assert(i != 0 || e == 5); assert(i != 0 || e == 5);
++j; ++j;
} }
assert(j == 3); assert(j == 3);
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);
assert(j != 1 || e == 4); assert(j != 1 || e == 4);
assert(j != 0 || e == 5); assert(j != 0 || e == 5);
++j; ++j;
} }
assert(j == 3); assert(j == 3);
assert(q.empty); assert(q.empty);
}
dispose(theAllocator, q); private SEntry!T* first;
} private SEntry!T* rear;
/** mixin DefaultAllocator;
* 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;
/// 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(); foreach (i, ref e; q)
assert(q.front == 4); {
assert(i != 0 || e == 4);
foreach (i, ref e; q) assert(i != 1 || e == 9);
{ }
assert(i != 0 || e == 4); assert(q.empty);
assert(i != 1 || e == 9);
}
assert(q.empty);
theAllocator.dispose(q);
} }

View File

@ -0,0 +1,710 @@
/* 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/. */
/**
* This module implements a $(D_PSYMBOL Set) container that stores unique
* values without any particular order.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.container.set;
import std.algorithm.mutation;
import std.traits;
import tanya.container;
import tanya.container.entry;
import tanya.memory;
/**
* Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values.
*
* Params:
* E = Element type.
*/
struct Range(E)
{
static if (isMutable!E)
{
private alias DataRange = Array!(Bucket!(Unqual!E)).Range;
}
else
{
private alias DataRange = Array!(Bucket!(Unqual!E)).ConstRange;
}
private DataRange dataRange;
@disable this();
private this(DataRange dataRange)
{
while (!dataRange.empty && dataRange.front.status != BucketStatus.used)
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used)
{
dataRange.popBack();
}
this.dataRange = dataRange;
}
@property Range save()
{
return this;
}
@property bool empty() const
{
return this.dataRange.empty();
}
@property void popFront()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
{
do
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.front.status != BucketStatus.used);
}
@property void popBack()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
{
do
{
dataRange.popBack();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(E) front() inout
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
body
{
return dataRange.front.content;
}
@property ref inout(E) back() inout
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
body
{
return dataRange.back.content;
}
Range opIndex()
{
return typeof(return)(this.dataRange[]);
}
Range!(const E) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
}
/**
* Set is a data structure that stores unique values without any particular
* order.
*
* This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions
* are resolved with linear probing.
*
* Currently works only with integral types.
*
* Params:
* T = Element type.
*/
struct Set(T)
if (isIntegral!T || is(Unqual!T == bool))
{
/// The range types for $(D_PSYMBOL Set).
alias Range = .Range!T;
/// Ditto.
alias ConstRange = .Range!(const T);
invariant
{
assert(this.lengthIndex < primes.length);
assert(this.data.length == 0
|| this.data.length == primes[this.lengthIndex]);
}
/**
* Constructor.
*
* Params:
* n = Minimum number of buckets.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(const size_t n, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
body
{
this(allocator);
rehash(n);
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(allocator);
}
///
unittest
{
{
auto set = Set!int(defaultAllocator);
assert(set.capacity == 0);
}
{
auto set = Set!int(8);
assert(set.capacity == 13);
}
}
/**
* Initializes this $(D_PARAM Set) from another one.
*
* If $(D_PARAM init) is passed by reference, it will be copied.
* If $(D_PARAM init) is passed by value, it will be moved.
*
* Params:
* S = Source set type.
* init = Source set.
* allocator = Allocator.
*/
this(S)(ref S init, shared Allocator allocator = defaultAllocator)
if (is(Unqual!S == Set))
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(init.data, allocator);
}
/// Ditto.
this(S)(S init, shared Allocator allocator = defaultAllocator)
if (is(S == Set))
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(move(init.data), allocator);
this.lengthIndex = init.lengthIndex;
init.lengthIndex = 0;
}
/**
* Assigns another set.
*
* If $(D_PARAM that) is passed by reference, it will be copied.
* If $(D_PARAM that) is passed by value, it will be moved.
*
* Params:
* S = Content type.
* that = The value should be assigned.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(S)(ref S that)
if (is(Unqual!S == Set))
{
this.data = that.data;
this.lengthIndex = that.lengthIndex;
return this;
}
/// Ditto.
ref typeof(this) opAssign(S)(S that) @trusted
if (is(S == Set))
{
swap(this.data, that.data);
swap(this.lengthIndex, that.lengthIndex);
return this;
}
/**
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
@property shared(Allocator) allocator() const
out (allocator)
{
assert(allocator !is null);
}
body
{
return cast(shared Allocator) this.data.allocator;
}
/**
* Maximum amount of elements this $(D_PSYMBOL Set) can hold without
* resizing and rehashing. Note that it doesn't mean that the
* $(D_PSYMBOL Set) will hold $(I exactly) $(D_PSYMBOL capacity) elements.
* $(D_PSYMBOL capacity) tells the size of the container under a best-case
* distribution of elements.
*
* Returns: $(D_PSYMBOL Set) capacity.
*/
@property size_t capacity() const
{
return this.data.length;
}
///
unittest
{
Set!int set;
assert(set.capacity == 0);
set.insert(8);
assert(set.capacity == 3);
}
/**
* Iterates over the $(D_PSYMBOL Set) and counts the elements.
*
* Returns: Count of elements within the $(D_PSYMBOL Set).
*/
@property size_t length() const
{
size_t count;
foreach (ref e; this.data[])
{
if (e.status == BucketStatus.used)
{
++count;
}
}
return count;
}
///
unittest
{
Set!int set;
assert(set.length == 0);
set.insert(8);
assert(set.length == 1);
}
private static const size_t[41] primes = [
3, 7, 13, 23, 29, 37, 53, 71, 97, 131, 163, 193, 239, 293, 389, 521,
769, 919, 1103, 1327, 1543, 2333, 3079, 4861, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 139022417, 282312799, 573292817, 1164186217,
];
/// The maximum number of buckets the container can have.
enum size_t maxBucketCount = primes[$ - 1];
static private size_t calculateHash(ref T value)
{
static if (isIntegral!T || isSomeChar!T || is(T == bool))
{
return (cast(size_t) value);
}
else
{
static assert(false);
}
}
static private size_t locateBucket(ref const DataType buckets, size_t hash)
{
return hash % buckets.length;
}
private enum InsertStatus : byte
{
found = -1,
failed = 0,
added = 1,
}
/*
* Inserts the value in an empty or deleted bucket. If the value is
* already in there, does nothing and returns InsertStatus.found. If the
* hash array is full returns InsertStatus.failed.
*/
private InsertStatus insertInUnusedBucket(ref T value)
{
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Already in the set.
{
return InsertStatus.found;
}
else if (e.status != BucketStatus.used) // Insert the value.
{
e.content = value;
return InsertStatus.added;
}
}
return InsertStatus.failed;
}
/**
* Inserts a new element.
*
* Params:
* value = Element value.
*
* Returns: Amount of new elements inserted.
*
* Throws: $(D_PSYMBOL HashContainerFullException) if the insertion failed.
*/
size_t insert(T value)
{
if (this.data.length == 0)
{
this.data = DataType(primes[0], allocator);
}
InsertStatus status = insertInUnusedBucket(value);
for (; !status; status = insertInUnusedBucket(value))
{
if ((this.primes.length - 1) == this.lengthIndex)
{
throw make!HashContainerFullException(defaultAllocator,
"Set is full");
}
rehashToSize(this.lengthIndex + 1);
}
return status == InsertStatus.added;
}
///
unittest
{
Set!int set;
assert(8 !in set);
assert(set.insert(8) == 1);
assert(set.length == 1);
assert(8 in set);
assert(set.insert(8) == 0);
assert(set.length == 1);
assert(8 in set);
assert(set.remove(8));
assert(set.insert(8) == 1);
}
/**
* Removes an element.
*
* Params:
* value = Element value.
*
* Returns: Number of elements removed, which is in the container with
* unique values `1` if an element existed, and `0` otherwise.
*/
size_t remove(T value)
{
if (this.data.length == 0)
{
return 0;
}
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
e.remove();
return 1;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return 0;
}
///
unittest
{
Set!int set;
assert(8 !in set);
set.insert(8);
assert(8 in set);
assert(set.remove(8) == 1);
assert(set.remove(8) == 0);
assert(8 !in set);
}
/**
* $(D_KEYWORD in) operator.
*
* Params:
* value = Element to be searched for.
*
* Returns: $(D_KEYWORD true) if the given element exists in the container,
* $(D_KEYWORD false) otherwise.
*/
bool opBinaryRight(string op : "in")(auto ref T value)
{
if (this.data.length == 0)
{
return 0;
}
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
/// Ditto.
bool opBinaryRight(string op : "in")(auto ref const T value) const
{
if (this.data.length == 0)
{
return 0;
}
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e.status == BucketStatus.used && e.content == value) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
///
unittest
{
Set!int set;
assert(5 !in set);
set.insert(5);
assert(5 in set);
}
/**
* Sets the number of buckets in the container to at least $(D_PARAM n)
* and rearranges all the elements according to their hash values.
*
* If $(D_PARAM n) is greater than the current $(D_PSYMBOL capacity)
* and lower than or equal to $(D_PSYMBOL maxBucketCount), a rehash is
* forced.
*
* If $(D_PARAM n) is greater than $(D_PSYMBOL maxBucketCount),
* $(D_PSYMBOL maxBucketCount) is used instead as a new number of buckets.
*
* If $(D_PARAM n) is equal to the current $(D_PSYMBOL capacity), rehashing
* is forced without resizing the container.
*
* If $(D_PARAM n) is lower than the current $(D_PSYMBOL capacity), the
* function may have no effect.
*
* Rehashing is automatically performed whenever the container needs space
* to insert new elements.
*
* Params:
* n = Minimum number of buckets.
*/
void rehash(const size_t n)
{
size_t lengthIndex;
for (; lengthIndex < primes.length; ++lengthIndex)
{
if (primes[lengthIndex] >= n)
{
break;
}
}
rehashToSize(lengthIndex);
}
// Takes an index in the primes array.
private void rehashToSize(const size_t n)
{
auto storage = DataType(primes[n], allocator);
DataLoop: foreach (ref e1; this.data[])
{
if (e1.status == BucketStatus.used)
{
auto bucketPosition = locateBucket(storage,
calculateHash(e1.content));
foreach (ref e2; storage[bucketPosition .. $])
{
if (e2.status != BucketStatus.used) // Insert the value.
{
e2.content = e1.content;
continue DataLoop;
}
}
return; // Rehashing failed.
}
}
move(storage, this.data);
this.lengthIndex = n;
}
/**
* Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s
* elements.
*/
Range opIndex()
{
return typeof(return)(this.data[]);
}
/// Ditto.
ConstRange opIndex() const
{
return typeof(return)(this.data[]);
}
///
unittest
{
Set!int set;
assert(set[].empty);
set.insert(8);
assert(!set[].empty);
assert(set[].front == 8);
assert(set[].back == 8);
set.remove(8);
assert(set[].empty);
}
private alias DataType = Array!(Bucket!T);
private DataType data;
private size_t lengthIndex;
}
// Basic insertion logic.
private unittest
{
Set!int set;
assert(set.insert(5) == 1);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(5) == 0);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(9) == 1);
assert(set.data[0].content == 9 && set.data[0].status == BucketStatus.used);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(7) == 1);
assert(set.insert(8) == 1);
assert(set.data[0].content == 7);
assert(set.data[1].content == 8);
assert(set.data[2].content == 9);
assert(set.data[3].status == BucketStatus.empty);
assert(set.data[5].content == 5);
assert(set.data.length == 7);
assert(set.insert(16) == 1);
assert(set.data[2].content == 9);
assert(set.data[3].content == 16);
assert(set.data[4].status == BucketStatus.empty);
}
// Static checks.
private unittest
{
import std.range.primitives;
static assert(isBidirectionalRange!(Set!int.ConstRange));
static assert(isBidirectionalRange!(Set!int.Range));
static assert(!isInfinite!(Set!int.Range));
static assert(!hasLength!(Set!int.Range));
static assert(is(Set!uint));
static assert(is(Set!long));
static assert(is(Set!ulong));
static assert(is(Set!short));
static assert(is(Set!ushort));
static assert(is(Set!bool));
}

File diff suppressed because it is too large Load Diff

View File

@ -3,779 +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. * Single-dimensioned array.
*
* 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)
*/ */
deprecated("Use tanya.container.array instead.")
module tanya.container.vector; module tanya.container.vector;
import std.algorithm.comparison; public import tanya.container.array;
import std.traits;
import tanya.memory;
/**
* One dimensional array.
*
* Params:
* T = Content type.
*/
class Vector(T)
{
/**
* Defines the container's primary range.
*/
struct Range(V)
{
private V[1] data;
private @property ref inout(V) outer() inout
{
return data[0];
}
private size_t start, end;
invariant
{
assert(start <= end);
}
private alias ElementType = typeof(data[0].vector[0]);
protected this(V data, in size_t a, in size_t b)
{
this.data = data;
start = a;
end = b;
}
@property Range save()
{
return this;
}
@property bool empty() inout const
{
return start >= end;
}
@property size_t length() inout const
{
return end - start;
}
alias opDollar = length;
@property ref inout(ElementType) front() inout
in
{
assert(!empty);
}
body
{
return outer[start];
}
@property ref inout(ElementType) back() inout
in
{
assert(!empty);
}
body
{
return outer[end - 1];
}
void popFront()
in
{
assert(!empty);
}
body
{
++start;
}
void popBack()
in
{
assert(!empty);
}
body
{
--end;
}
ref inout(ElementType) opIndex(in size_t i) inout
in
{
assert(start + i < end);
}
body
{
return outer[start + i];
}
Range opIndex()
{
return typeof(return)(outer, start, end);
}
Range opSlice(in size_t i, in size_t j)
in
{
assert(i <= j);
assert(start + j <= end);
}
body
{
return typeof(return)(outer, start + i, start + j);
}
Range!(const(V)) opIndex() const
{
return typeof(return)(outer, start, end);
}
Range!(const(V)) opSlice(in size_t i, in size_t j) const
in
{
assert(i <= j);
assert(start + j <= end);
}
body
{
return typeof(return)(outer, start + i, start + j);
}
static if (isMutable!V)
{
Range opIndexAssign(in ElementType value)
in
{
assert(end <= outer.length);
}
body
{
return outer[start .. end] = value;
}
Range opSliceAssign(in ElementType value, in size_t i, in size_t j)
in
{
assert(start + j <= end);
}
body
{
return outer[start + i .. start + j] = value;
}
Range opSliceAssign(in Range!Vector value, in size_t i, in size_t j)
in
{
assert(length == value.length);
}
body
{
return outer[start + i .. start + j] = value;
}
Range opSliceAssign(in T[] value, in size_t i, in size_t j)
in
{
assert(j - i == value.length);
}
body
{
return outer[start + i .. start + j] = value;
}
}
}
/**
* Creates an empty $(D_PSYMBOL Vector).
*
* Params:
* allocator = The allocator should be used for the element
* allocations.
*/
this(IAllocator allocator = theAllocator)
{
this.allocator = allocator;
}
/**
* Creates a new $(D_PSYMBOL Vector).
*
* Params:
* U = Variadic template for the constructor parameters.
* params = Values to initialize the array with. The last parameter can
* be an allocator, if not, $(D_PSYMBOL theAllocator) is used.
*/
this(U...)(U params)
{
static if (isImplicitlyConvertible!(typeof(params[$ - 1]), IAllocator))
{
allocator = params[$ - 1];
auto values = params[0 .. $ - 1];
}
else
{
allocator = theAllocator;
alias values = params;
}
resizeArray!T(allocator, vector, values.length);
foreach (i, v; values)
{
vector[i] = v;
}
}
/**
* Destroys this $(D_PSYMBOL Vector).
*/
~this()
{
dispose(allocator, vector);
}
/**
* Removes all elements.
*/
void clear()
{
resizeArray!T(allocator, vector, 0);
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(18, 20, 15);
v.clear();
assert(v.length == 0);
}
/**
* Returns: Vector length.
*/
@property size_t length() inout const
{
return vector.length;
}
/// Ditto.
size_t opDollar() inout const
{
return length;
}
/**
* Expands/shrinks the vector.
*
* Params:
* length = New length.
*/
@property void length(in size_t length)
{
resizeArray!T(allocator, vector, length);
}
///
unittest
{
auto v = theAllocator.make!(Vector!int);
v.length = 5;
assert(v.length == 5);
v.length = 7;
assert(v.length == 7);
v.length = 0;
assert(v.length == 0);
}
/**
* Returns: $(D_KEYWORD true) if the vector is empty.
*/
@property bool empty() inout const
{
return vector.length == 0;
}
/**
* Removes $(D_PARAM howMany) elements from the vector.
*
* This method doesn't fail if it could not remove $(D_PARAM howMany)
* elements. Instead, if $(D_PARAM howMany) is greater than the vector
* length, all elements are removed.
*
* Params:
* howMany = How many elements should be removed.
*
* Returns: The number of elements removed
*/
size_t removeBack(in size_t howMany)
{
immutable toRemove = min(howMany, length);
static if (hasElaborateDestructor!T)
{
foreach (ref e; vector[$ - toRemove ..$])
{
allocator.dispose(e);
}
}
length = length - toRemove;
return toRemove;
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(5, 18, 17);
assert(v.removeBack(0) == 0);
assert(v.removeBack(2) == 2);
assert(v.removeBack(3) == 1);
assert(v.removeBack(3) == 0);
theAllocator.dispose(v);
}
/**
* Assigns a value to the element with the index $(D_PARAM pos).
*
* Params:
* value = Value.
*
* Returns: Assigned value.
*
* Precondition: $(D_INLINECODE length > pos)
*/
T opIndexAssign(in T value, in size_t pos)
in
{
assert(length > pos);
}
body
{
return vector[pos] = value;
}
/// Ditto.
Range!Vector opIndexAssign(in T value)
{
vector[0..$] = value;
return opIndex();
}
///
unittest
{
auto v1 = theAllocator.make!(Vector!int)(12, 1, 7);
v1[] = 3;
assert(v1[0] == 3);
assert(v1[1] == 3);
assert(v1[2] == 3);
theAllocator.dispose(v1);
}
/**
* Returns: The value on index $(D_PARAM pos).
*
* Precondition: $(D_INLINECODE length > pos)
*/
ref inout(T) opIndex(in size_t pos) inout
in
{
assert(length > pos);
}
body
{
return vector[pos];
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(6, 123, 34, 5);
assert(v[0] == 6);
assert(v[1] == 123);
assert(v[2] == 34);
assert(v[3] == 5);
theAllocator.dispose(v);
}
/**
* Comparison for equality.
*
* Params:
* o = The vector to compare with.
*
* Returns: $(D_KEYWORD true) if the vectors are equal, $(D_KEYWORD false)
* otherwise.
*/
override bool opEquals(Object o)
{
auto v = cast(Vector) o;
return v is null ? super.opEquals(o) : vector == v.vector;
}
///
unittest
{
auto v1 = theAllocator.make!(Vector!int);
auto v2 = theAllocator.make!(Vector!int);
assert(v1 == v2);
v1.length = 1;
v2.length = 2;
assert(v1 != v2);
v1.length = 2;
v1[0] = v2[0] = 2;
v1[1] = 3;
v2[1] = 4;
assert(v1 != v2);
v2[1] = 3;
assert(v1 == v2);
theAllocator.dispose(v1);
theAllocator.dispose(v2);
}
/**
* $(D_KEYWORD foreach) iteration.
*
* Params:
* dg = $(D_KEYWORD foreach) body.
*/
int opApply(scope int delegate(ref T) dg)
{
int result;
foreach (e; vector)
{
if ((result = dg(e)) != 0)
{
return result;
}
}
return result;
}
/// Ditto.
int opApply(scope int delegate(ref size_t i, ref T) dg)
{
int result;
foreach (i, e; vector)
{
if ((result = dg(i, e)) != 0)
{
return result;
}
}
return result;
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(5, 15, 8);
size_t i;
foreach (j, ref e; v)
{
i = j;
}
assert(i == 2);
foreach (j, e; v)
{
assert(j != 0 || e == 5);
assert(j != 1 || e == 15);
assert(j != 2 || e == 8);
}
theAllocator.dispose(v);
}
/**
* $(D_KEYWORD foreach) iteration.
*
* Params:
* dg = $(D_KEYWORD foreach) body.
*/
int opApplyReverse(scope int delegate(ref T) dg)
{
int result;
foreach_reverse (e; vector)
{
if ((result = dg(e)) != 0)
{
return result;
}
}
return result;
}
/// Ditto.
int opApplyReverse(scope int delegate(ref size_t i, ref T) dg)
{
int result;
foreach_reverse (i, e; vector)
{
if ((result = dg(i, e)) != 0)
{
return result;
}
}
return result;
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(5, 15, 8);
size_t i;
foreach_reverse (j, ref e; v)
{
i = j;
}
assert(i == 0);
foreach_reverse (j, e; v)
{
assert(j != 2 || e == 8);
assert(j != 1 || e == 15);
assert(j != 0 || e == 5);
}
theAllocator.dispose(v);
}
/**
* Returns: The first element.
*
* Precondition: $(D_INLINECODE length > 0)
*/
@property ref inout(T) front() inout
in
{
assert(vector.length > 0);
}
body
{
return vector[0];
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(5);
assert(v.front == 5);
v.length = 2;
v[1] = 15;
assert(v.front == 5);
theAllocator.dispose(v);
}
/**
* Returns: The last element.
*
* Precondition: $(D_INLINECODE length > 0)
*/
@property ref inout(T) back() inout
in
{
assert(vector.length > 0);
}
body
{
return vector[$ - 1];
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(5);
assert(v.back == 5);
v.length = 2;
v[1] = 15;
assert(v.back == 15);
theAllocator.dispose(v);
}
/**
* Returns: A range that iterates over elements of the container, in
* forward order.
*/
Range!Vector opIndex()
{
return typeof(return)(this, 0, length);
}
/// Ditto.
Range!(const Vector) opIndex() const
{
return typeof(return)(this, 0, length);
}
/// Ditto.
Range!(immutable Vector) opIndex() immutable
{
return typeof(return)(this, 0, length);
}
/**
* Params:
* i = Slice start.
* j = Slice end.
*
* Returns: A range that iterates over elements of the container from
* index $(D_PARAM i) up to (excluding) index $(D_PARAM j).
*
* Precondition: $(D_INLINECODE i <= j && j <= length)
*/
Range!Vector opSlice(in size_t i, in size_t j)
in
{
assert(i <= j);
assert(j <= length);
}
body
{
return typeof(return)(this, i, j);
}
/// Ditto.
Range!(const Vector) opSlice(in size_t i, in size_t j) const
in
{
assert(i <= j);
assert(j <= length);
}
body
{
return typeof(return)(this, i, j);
}
/// Ditto.
Range!(immutable Vector) opSlice(in size_t i, in size_t j) immutable
in
{
assert(i <= j);
assert(j <= length);
}
body
{
return typeof(return)(this, i, j);
}
/**
* Slicing assignment.
*
* Params:
* value = New value.
* i = Slice start.
* j = Slice end.
*
* Returns: Assigned value.
*
* Precondition: $(D_INLINECODE i <= j && j <= length);
* The lenghts of the ranges and slices match.
*/
Range!Vector opSliceAssign(in T value, in size_t i, in size_t j)
in
{
assert(i <= j);
assert(j <= length);
}
body
{
vector[i .. j] = value;
return opSlice(i, j);
}
/// Ditto.
Range!Vector opSliceAssign(in Range!Vector value, in size_t i, in size_t j)
in
{
assert(j - i == value.length);
}
body
{
vector[i .. j] = value.outer.vector[value.start .. value.end];
return opSlice(i, j);
}
/// Ditto.
Range!Vector opSliceAssign(in T[] value, in size_t i, in size_t j)
in
{
assert(j - i == value.length);
}
body
{
vector[i .. j] = value;
return opSlice(i, j);
}
///
unittest
{
auto v1 = theAllocator.make!(Vector!int)(3, 3, 3);
auto v2 = theAllocator.make!(Vector!int)(1, 2);
v1[0..2] = 286;
assert(v1[0] == 286);
assert(v1[1] == 286);
assert(v1[2] == 3);
v2[0..$] = v1[1..3];
assert(v2[0] == 286);
assert(v2[1] == 3);
theAllocator.dispose(v2);
theAllocator.dispose(v1);
}
/// Internal representation.
protected T[] vector;
/// The allocator.
protected IAllocator allocator;
}
///
unittest
{
auto v = theAllocator.make!(Vector!int)(5, 15, 8);
assert(v.front == 5);
assert(v[1] == 15);
assert(v.back == 8);
theAllocator.dispose(v);
}

View File

@ -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]);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -3,55 +3,63 @@
* 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.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)
{ {
import std.algorithm.iteration; import std.algorithm.iteration;
} }
/** /**
* 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:
* x = Base. * I = Base type.
* y = Exponent. * G = Exponent type.
* z = Divisor. * H = Divisor type:
* x = Base.
* y = Exponent.
* 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)
{ {
return 1 % z; return 1 % z;
} }
else if (y == 1) else if (y == 1)
{ {
return x % z; return x % z;
} }
do do
{ {
auto bit = y & mask; immutable bit = y & mask;
if (!result && bit) if (!result && bit)
{ {
result = x; result = x;
@ -64,15 +72,54 @@ body
result *= x; result *= x;
} }
result %= z; result %= z;
} }
while (mask >>= 1); while (mask >>= 1);
return result; return result;
}
/// Ditto.
I pow(I)(const auto ref I x, const auto ref I y, const auto ref I z)
if (is(I == Integer))
in
{
assert(z.length > 0, "Division by zero.");
}
body
{
size_t i;
auto tmp1 = Integer(x, x.allocator);
auto result = Integer(x.allocator);
bool firstBit;
if (x.size == 0 && y.size != 0)
{
i = y.size;
}
else
{
result = 1;
}
while (i < y.size)
{
for (uint mask = 0x01; mask != 0x10000000; mask <<= 1)
{
if (y.rep[i] & mask)
{
result *= tmp1;
result %= z;
}
auto tmp2 = tmp1;
tmp1 *= tmp2;
tmp1 %= z;
}
++i;
}
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,28 +132,70 @@ unittest
assert(pow(0, 5, 5) == 0); assert(pow(0, 5, 5) == 0);
} }
///
unittest
{
assert(pow(Integer(3), Integer(5), Integer(7)) == 5);
assert(pow(Integer(2), Integer(2), Integer(1)) == 0);
assert(pow(Integer(3), Integer(3), Integer(3)) == 0);
assert(pow(Integer(7), Integer(4), Integer(2)) == 1);
assert(pow(Integer(53), Integer(0), Integer(2)) == 1);
assert(pow(Integer(53), Integer(1), Integer(3)) == 2);
assert(pow(Integer(53), Integer(2), Integer(5)) == 4);
assert(pow(Integer(0), Integer(0), Integer(5)) == 1);
assert(pow(Integer(0), Integer(5), Integer(5)) == 0);
}
/** /**
* Checks if $(D_PARAM x) is a prime. * Checks if $(D_PARAM x) is a prime.
* *
* Params: * Params:
* x = The number should be checked. * x = The number should be checked.
* *
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number, * Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
* $(D_KEYWORD false) otherwise. * $(D_KEYWORD false) otherwise.
*/ */
bool isPseudoprime(ulong x) nothrow pure @safe @nogc bool isPseudoprime(ulong x) nothrow pure @safe @nogc
{ {
return pow(2, x - 1, x) == 1; return pow(2, x - 1, x) == 1;
} }
/// ///
unittest unittest
{ {
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821, 74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
74827, 9973, 104729, 15485867, 49979693, 104395303, 74827, 9973, 104729, 15485867, 49979693, 104395303,
593441861, 104729, 15485867, 49979693, 104395303, 593441861, 104729, 15485867, 49979693, 104395303,
593441861, 899809363, 982451653]; 593441861, 899809363, 982451653];
known.each!((ref x) => assert(isPseudoprime(x))); known.each!((ref x) => assert(isPseudoprime(x)));
}
/**
* Params:
* I = Value type.
* x = Value.
*
* Returns: The absolute value of a number.
*/
I abs(I : Integer)(const auto ref I x)
{
auto result = Integer(x, x.allocator);
result.sign = Sign.positive;
return result;
}
/// Ditto.
I abs(I : Integer)(I x)
{
x.sign = Sign.positive;
return x;
}
/// Ditto.
I abs(I)(const I x)
if (isIntegral!I)
{
return x >= 0 ? x : -x;
} }

320
source/tanya/math/random.d Normal file
View File

@ -0,0 +1,320 @@
/* 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/. */
/**
* Random number generator.
*
* Copyright: Eugene Wissner 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.math.random;
import std.digest.sha;
import std.typecons;
import tanya.memory;
/// Block size of entropy accumulator (SHA-512).
enum blockSize = 64;
/// Maximum amount gathered from the entropy sources.
enum maxGather = 128;
/**
* Exception thrown if random number generating fails.
*/
class EntropyException : Exception
{
/**
* Params:
* msg = Message to output.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) pure @safe nothrow const @nogc
{
super(msg, file, line, next);
}
}
/**
* Interface for implementing entropy sources.
*/
abstract class EntropySource
{
/// Amount of already generated entropy.
protected ushort size_;
/**
* Returns: Minimum bytes required from the entropy source.
*/
@property immutable(ubyte) threshold() const @safe pure nothrow;
/**
* Returns: Whether this entropy source is strong.
*/
@property immutable(bool) strong() const @safe pure nothrow;
/**
* Returns: Amount of already generated entropy.
*/
@property ushort size() const @safe pure nothrow
{
return size_;
}
/**
* Params:
* size = Amount of already generated entropy. Cannot be smaller than the
* already set value.
*/
@property void size(ushort size) @safe pure nothrow
{
size_ = size;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
Nullable!ubyte poll(out ubyte[maxGather] output);
}
version (linux)
{
extern (C) long syscall(long number, ...) nothrow;
/**
* Uses getrandom system call.
*/
class PlatformEntropySource : EntropySource
{
/**
* Returns: Minimum bytes required from the entropy source.
*/
override @property immutable(ubyte) threshold() const @safe pure nothrow
{
return 32;
}
/**
* Returns: Whether this entropy source is strong.
*/
override @property immutable(bool) strong() const @safe pure nothrow
{
return true;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
out (length)
{
assert(length <= maxGather);
}
body
{
// int getrandom(void *buf, size_t buflen, unsigned int flags);
auto length = syscall(318, output.ptr, output.length, 0);
Nullable!ubyte ret;
if (length >= 0)
{
ret = cast(ubyte) length;
}
return ret;
}
}
}
/**
* Pseudorandom number generator.
* ---
* auto entropy = defaultAllocator.make!Entropy();
*
* ubyte[blockSize] output;
*
* output = entropy.random;
*
* defaultAllocator.dispose(entropy);
* ---
*/
class Entropy
{
/// Entropy sources.
protected EntropySource[] sources;
private ubyte sourceCount_;
private shared Allocator allocator;
/// Entropy accumulator.
protected SHA!(maxGather * 8, 512) accumulator;
/**
* Params:
* maxSources = Maximum amount of entropy sources can be set.
* allocator = Allocator to allocate entropy sources available on the
* system.
*/
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
in
{
assert(maxSources > 0 && maxSources <= ubyte.max);
assert(allocator !is null);
}
body
{
allocator.resize(sources, maxSources);
version (linux)
{
this ~= allocator.make!PlatformEntropySource;
}
}
/**
* Returns: Amount of the registered entropy sources.
*/
@property ubyte sourceCount() const @safe pure nothrow
{
return sourceCount_;
}
/**
* Add an entropy source.
*
* Params:
* source = Entropy source.
*
* Returns: $(D_PSYMBOL this).
*
* See_Also:
* $(D_PSYMBOL EntropySource)
*/
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
if (Op == "~")
in
{
assert(sourceCount_ <= sources.length);
}
body
{
sources[sourceCount_++] = source;
return this;
}
/**
* Returns: Generated random sequence.
*
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
* registered or it failed.
*/
@property ubyte[blockSize] random()
in
{
assert(sourceCount_ > 0, "No entropy sources defined.");
}
body
{
bool haveStrong;
ushort done;
ubyte[blockSize] output;
do
{
ubyte[maxGather] buffer;
// Run through our entropy sources
for (ubyte i; i < sourceCount; ++i)
{
auto outputLength = sources[i].poll(buffer);
if (!outputLength.isNull)
{
if (outputLength > 0)
{
update(i, buffer, outputLength);
sources[i].size = cast(ushort) (sources[i].size + outputLength);
}
if (sources[i].size < sources[i].threshold)
{
continue;
}
else if (sources[i].strong)
{
haveStrong = true;
}
}
done = 257;
}
}
while (++done < 256);
if (!haveStrong)
{
throw allocator.make!EntropyException("No strong entropy source defined.");
}
output = accumulator.finish();
// Reset accumulator and counters and recycle existing entropy
accumulator.start();
// Perform second SHA-512 on entropy
output = sha512Of(output);
for (ubyte i = 0; i < sourceCount; ++i)
{
sources[i].size = 0;
}
return output;
}
/**
* Update entropy accumulator.
*
* Params:
* sourceId = Entropy source index in $(D_PSYMBOL sources).
* data = Data got from the entropy source.
* length = Length of the received data.
*/
protected void update(in ubyte sourceId,
ref ubyte[maxGather] data,
ubyte length) @safe pure nothrow
{
ubyte[2] header;
if (length > blockSize)
{
data[0..64] = sha512Of(data);
length = blockSize;
}
header[0] = sourceId;
header[1] = length;
accumulator.put(header);
accumulator.put(data[0..length]);
}
}

View File

@ -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: * Params:
* b = Memory block. * size = Amount of memory to allocate.
* *
* Returns: $(D_PSYMBOL Ternary.unknown). * Returns: Pointer to the new allocated memory.
*/ */
Ternary owns(void[] b) const @nogc @safe pure nothrow void[] allocate(in size_t size) shared nothrow @nogc;
{
return Ternary.unknown;
}
/** /**
* Not supported. * Deallocates a memory block.
* *
* Params: * Params:
* p = Pointer to a memory block. * p = A pointer to the memory block to be freed.
* result = Full block allocated.
* *
* Returns: $(D_PSYMBOL Ternary.unknown). * Returns: Whether the deallocation was successful.
*/ */
Ternary resolveInternalPointer(void* p, ref void[] result) bool deallocate(void[] p) shared nothrow @nogc;
const @nogc @safe pure nothrow
{
return Ternary.unknown;
}
/** /**
* Params: * Increases or decreases the size of a memory block.
* size = Amount of memory to allocate.
*
* Returns: The good allocation size that guarantees zero internal
* fragmentation.
*/
size_t goodAllocSize(size_t s)
{
auto rem = s % alignment;
return rem ? s + alignment - rem : s;
}
/**
* Not supported.
*
* Returns: $(D_KEYWORD null).
*
*/
void[] allocateAll() const @nogc @safe pure nothrow
{
return null;
}
/**
* Not supported.
*
* Params:
* b = Block to be expanded.
* s = New size.
*
* Returns: $(D_KEYWORD false).
*/
bool expand(ref void[] b, size_t s) const @nogc @safe pure nothrow
{
return false;
}
/**
* Not supported.
* *
* 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;

View File

@ -0,0 +1,185 @@
/* 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 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.memory.mallocator;
import core.stdc.stdlib;
import std.algorithm.comparison;
import tanya.memory.allocator;
/**
* Wrapper for malloc/realloc/free from the C standard library.
*/
final class Mallocator : Allocator
{
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(in size_t size) shared nothrow @nogc
{
if (!size)
{
return null;
}
auto p = malloc(size + psize);
return p is null ? null : p[psize .. psize + size];
}
///
@nogc nothrow unittest
{
auto p = Mallocator.instance.allocate(20);
assert(p.length == 20);
Mallocator.instance.deallocate(p);
}
/**
* Deallocates a memory block.
*
* Params:
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared nothrow @nogc
{
if (p !is null)
{
free(p.ptr - psize);
}
return true;
}
///
@nogc nothrow unittest
{
void[] p;
assert(Mallocator.instance.deallocate(p));
p = Mallocator.instance.allocate(10);
assert(Mallocator.instance.deallocate(p));
}
/**
* Reallocating in place isn't supported.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD false).
*/
bool reallocateInPlace(ref void[] p, const size_t size) shared nothrow @nogc
{
return false;
}
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, const size_t size) shared nothrow @nogc
{
if (size == 0)
{
if (deallocate(p))
{
p = null;
return true;
}
}
else if (p is null)
{
p = allocate(size);
return p is null ? false : true;
}
else
{
auto r = realloc(p.ptr - psize, size + psize);
if (r !is null)
{
p = r[psize .. psize + size];
return true;
}
}
return false;
}
///
@nogc nothrow unittest
{
void[] p;
assert(Mallocator.instance.reallocate(p, 20));
assert(p.length == 20);
assert(Mallocator.instance.reallocate(p, 30));
assert(p.length == 30);
assert(Mallocator.instance.reallocate(p, 10));
assert(p.length == 10);
assert(Mallocator.instance.reallocate(p, 0));
assert(p is null);
}
/**
* Returns: The alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc
{
return cast(uint) max(double.alignof, real.alignof);
}
/**
* Static allocator instance and initializer.
*
* Returns: The global $(D_PSYMBOL Allocator) instance.
*/
static @property ref shared(Mallocator) instance() @nogc nothrow
{
if (instance_ is null)
{
immutable size = __traits(classInstanceSize, Mallocator) + psize;
void* p = malloc(size);
if (p !is null)
{
p[psize .. size] = typeid(Mallocator).initializer[];
instance_ = cast(shared Mallocator) p[psize .. size].ptr;
}
}
return instance_;
}
///
@nogc nothrow unittest
{
assert(instance is instance);
}
private enum psize = 8;
private shared static Mallocator instance_;
}

View File

@ -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
else version (Windows) {
{ assert(block.prev is null || block.prev.next is block);
SYSTEM_INFO si; assert(block.next is null || block.next.prev is block);
GetSystemInfo(&si); assert(block.region is *r);
pageSize = si.dwPageSize; }
while ((block = block.next) !is null);
} }
} }
@ -79,11 +69,11 @@ class MmapPool : Allocator
* Allocates $(D_PARAM size) bytes of memory. * Allocates $(D_PARAM size) bytes of memory.
* *
* 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)
{ {
@ -97,11 +87,11 @@ class MmapPool : Allocator
data = initializeRegion(dataSize); data = initializeRegion(dataSize);
} }
return data is null ? null : data[0..size]; return data is null ? null : data[0 .. size];
} }
/// ///
@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,53 +128,55 @@ 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;
}
block1.free = false;
block1.region.blocks = block1.region.blocks + 1;
block2.region = block1.region; return cast(void*) block1 + BlockEntry.sizeof;
atomicOp!"+="(block1.region.blocks, 1); }
}
else // 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)
{ {
block1.free = false; block.next.next.prev = block;
atomicOp!"+="(block1.region.blocks, 1);
} }
return cast(void*) block1 + blockEntrySize; block.next = block.next.next;
} }
/** /**
* Deallocates a memory block. * Deallocates a memory block.
* *
* Params: * Params:
* p = A pointer to the memory block to be freed. * p = A pointer to the memory block to be freed.
* *
* 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,58 +200,164 @@ 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.
* *
* Params: * Params:
* p = A pointer to the memory block. * p = A pointer to the memory block.
* size = Size of the reallocated block. * size = Size of the reallocated block.
* *
* 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.
void[] reallocP = allocate(size);
if (reallocP is null)
{ {
reallocP = allocate(size); return false;
if (reallocP is null)
{
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,25 +434,25 @@ 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:
* size = Aligned size of the first data block in the region. * size = Aligned size of the first data block in the region.
* head = Region list head. * head = Region list head.
* *
* 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);
version (Posix) version (Posix)
{ {
void* p = mmap(null, void* p = mmap(null,
@ -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);
} }

View File

@ -3,12 +3,307 @@
* 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;
import std.algorithm.iteration;
public import std.experimental.allocator : make;
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.
*
* Precondition: $(D_INLINECODE allocator_ !is null)
*/
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(const size_t size, const 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
* may throw $(D_PSYMBOL OutOfMemoryError). The new
* allocated part of the array isn't initialized. 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.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
*
* Returns: $(D_PARAM array).
*/
package(tanya) T[] resize(T)(shared Allocator allocator,
auto ref T[] array,
const size_t length) @trusted
{
if (length == 0)
{
if (allocator.deallocate(array))
{
return null;
}
else
{
onOutOfMemoryErrorNoGC();
}
}
void[] buf = array;
if (!allocator.reallocate(buf, length * T.sizeof))
{
onOutOfMemoryErrorNoGC();
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
return array;
}
private unittest
{
int[] p;
p = defaultAllocator.resize(p, 20);
assert(p.length == 20);
p = defaultAllocator.resize(p, 30);
assert(p.length == 30);
p = defaultAllocator.resize(p, 10);
assert(p.length == 10);
p = defaultAllocator.resize(p, 0);
assert(p is null);
}
/*
* Destroys the object.
* Returns the memory should be freed.
*/
package(tanya) void[] finalize(T)(ref T* p)
{
static if (hasElaborateDestructor!T)
{
destroy(*p);
}
return (cast(void*) p)[0 .. T.sizeof];
}
package(tanya) void[] finalize(T)(ref T p)
if (is(T == class) || is(T == interface))
{
if (p is null)
{
return null;
}
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];
auto ppv = cast(void**) ptr;
if (!*ppv)
{
return null;
}
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);
}
return support;
}
package(tanya) void[] finalize(T)(ref T[] p)
{
static if (hasElaborateDestructor!(typeof(p[0])))
{
p.each!((ref e) => destroy(e));
}
return p;
}
/**
* 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)
{
() @trusted { allocator.deallocate(finalize(p)); }();
p = null;
}
private unittest
{
struct S
{
~this()
{
}
}
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
defaultAllocator.dispose(p);
}

812
source/tanya/memory/types.d Normal file
View File

@ -0,0 +1,812 @@
/* 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-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.memory.types;
import core.exception;
import std.algorithm.comparison;
import std.algorithm.mutation;
import std.conv;
import std.range;
import std.traits;
import tanya.memory;
private template Payload(T)
{
static if (is(T == class) || is(T == interface) || isArray!T)
{
alias Payload = T;
}
else
{
alias Payload = T*;
}
}
final class RefCountedStore(T)
{
T payload;
size_t counter = 1;
size_t opUnary(string op)()
if (op == "--" || op == "++")
in
{
assert(counter > 0);
}
body
{
mixin("return " ~ op ~ "counter;");
}
int opCmp(const size_t counter)
{
if (this.counter > counter)
{
return 1;
}
else if (this.counter < counter)
{
return -1;
}
else
{
return 0;
}
}
int opEquals(const size_t counter)
{
return this.counter == counter;
}
}
private void separateDeleter(T)(RefCountedStore!T storage,
shared Allocator allocator)
{
allocator.dispose(storage.payload);
allocator.dispose(storage);
}
private void unifiedDeleter(T)(RefCountedStore!T storage,
shared Allocator allocator)
{
auto ptr1 = finalize(storage);
auto ptr2 = finalize(storage.payload);
allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]);
}
/**
* 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)
{
private alias Storage = RefCountedStore!(Payload!T);
private Storage storage;
private void function(Storage storage,
shared Allocator allocator) @nogc deleter;
invariant
{
assert(storage is null || allocator_ !is null);
assert(storage is null || deleter !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()(auto ref Payload!T value,
shared Allocator allocator = defaultAllocator)
{
this(allocator);
this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!(Payload!T);
move(value, this.storage.payload);
static if (__traits(isRef, value))
{
value = null;
}
}
/// 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)
{
++this.storage;
}
}
/**
* Decreases the reference counter by one.
*
* If the counter reaches 0, destroys the owned object.
*/
~this()
{
if (this.storage !is null && !(this.storage.counter && --this.storage))
{
deleter(this.storage, allocator);
}
}
/**
* 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 $(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 = New object.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign()(auto ref Payload!T rhs)
{
if (this.storage is null)
{
this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!(Payload!T);
}
else if (this.storage > 1)
{
--this.storage;
this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!(Payload!T);
}
else
{
finalize(this.storage.payload);
this.storage.payload = Payload!T.init;
}
move(rhs, this.storage.payload);
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(null))
{
if (this.storage is null)
{
return this;
}
else if (this.storage > 1)
{
--this.storage;
}
else
{
deleter(this.storage, allocator);
}
this.storage = null;
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(this.allocator_, rhs.allocator_);
swap(this.storage, rhs.storage);
swap(this.deleter, rhs.deleter);
return this;
}
/**
* Returns: Reference to the owned object.
*
* Precondition: $(D_INLINECODE cound > 0).
*/
Payload!T get() pure nothrow @safe @nogc
in
{
assert(count > 0, "Attempted to access an uninitialized reference.");
}
body
{
return storage.payload;
}
version (D_Ddoc)
{
/**
* 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 == "*");
}
else static if (isPointer!(Payload!T))
{
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
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int rc)
{
assert(rc.count == 2);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
assert(rc.count == 1);
func(rc);
assert(rc.count == 1);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
private unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(*rc == 5);
void func(RefCounted!int rc)
{
assert(rc.count == 2);
rc = defaultAllocator.refCounted!int(4);
assert(*rc == 4);
assert(rc.count == 1);
}
func(rc);
assert(*rc == 5);
}
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).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
body
{
auto rc = typeof(return)(allocator);
const storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
const 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);
}
rc.deleter = &unifiedDeleter!(Payload!T);
return rc;
}
/**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL RefCounted).
*
* Params:
* T = Array type.
* size = Array size.
* allocator = Allocator.
*
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
*
* Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof)
*/
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
body
{
auto payload = allocator.resize!(ElementType!T)(null, size);
return RefCounted!T(payload, allocator);
}
///
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);
}
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!(int[])(5);
assert(rc.length == 5);
}
private @nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
destroyed = true;
}
}
{
auto rc = defaultAllocator.refCounted!F();
}
assert(destroyed);
}
/**
* $(D_PSYMBOL Scoped) stores an object that gets destroyed at the end of its scope.
*
* Params:
* T = Value type.
*/
struct Scoped(T)
{
private Payload!T payload;
invariant
{
assert(payload 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()(auto ref Payload!T value,
shared Allocator allocator = defaultAllocator)
{
this(allocator);
move(value, this.payload);
static if (__traits(isRef, value))
{
value = null;
}
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator_ = allocator;
}
/**
* $(D_PSYMBOL Scoped) is noncopyable.
*/
@disable this(this);
/**
* Destroys the owned object.
*/
~this()
{
if (this.payload !is null)
{
allocator.dispose(this.payload);
}
}
/**
* Initialized this $(D_PARAM Scoped) and takes ownership over
* $(D_PARAM rhs).
*
* To reset $(D_PSYMBOL Scoped) 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 Scoped) and assign it.
*
* Params:
* rhs = New object.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign()(auto ref Payload!T rhs)
{
allocator.dispose(this.payload);
move(rhs, this.payload);
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(null))
{
allocator.dispose(this.payload);
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(this.allocator_, rhs.allocator_);
swap(this.payload, rhs.payload);
return this;
}
/**
* Returns: Reference to the owned object.
*/
Payload!T get() pure nothrow @safe @nogc
{
return payload;
}
version (D_Ddoc)
{
/**
* 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 == "*");
}
else static if (isPointer!(Payload!T))
{
ref T opUnary(string op)()
if (op == "*")
{
return *payload;
}
}
mixin DefaultAllocator;
alias get this;
}
///
@nogc unittest
{
auto p = defaultAllocator.make!int(5);
auto s = Scoped!int(p, defaultAllocator);
assert(p is null);
assert(*s == 5);
}
///
@nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
destroyed = true;
}
}
{
auto s = Scoped!F(defaultAllocator.make!F(), defaultAllocator);
}
assert(destroyed);
}
/**
* Constructs a new object of type $(D_PARAM T) and wraps it in a
* $(D_PSYMBOL Scoped) using $(D_PARAM args) as the parameter list for
* the constructor of $(D_PARAM T).
*
* 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 Scoped!T).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
Scoped!T scoped(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
body
{
auto payload = allocator.make!(T, shared Allocator, A)(args);
return Scoped!T(payload, allocator);
}
/**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL Scoped).
*
* Params:
* T = Array type.
* size = Array size.
* allocator = Allocator.
*
* Returns: Newly created $(D_PSYMBOL Scoped!T).
*
* Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof)
*/
Scoped!T scoped(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
body
{
auto payload = allocator.resize!(ElementType!T)(null, size);
return Scoped!T(payload, allocator);
}
private unittest
{
static assert(is(typeof(defaultAllocator.scoped!B(5))));
static assert(is(typeof(defaultAllocator.scoped!(int[])(5))));
}
private unittest
{
auto s = defaultAllocator.scoped!int(5);
assert(*s == 5);
s = null;
assert(s is null);
}
private unittest
{
auto s = defaultAllocator.scoped!int(5);
assert(*s == 5);
s = defaultAllocator.scoped!int(4);
assert(*s == 4);
}

356
source/tanya/network/inet.d Normal file
View File

@ -0,0 +1,356 @@
/* 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/. */
/**
* Internet utilities.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.network.inet;
import std.math;
import std.range.primitives;
import std.traits;
version (unittest)
{
version (Windows)
{
import core.sys.windows.winsock2;
version = PlattformUnittest;
}
else version (Posix)
{
import core.sys.posix.arpa.inet;
version = PlattformUnittest;
}
}
/**
* Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
*
* The range is bidirectional. The byte order is always big-endian.
*
* It can accept any unsigned integral type but the value should fit
* in $(D_PARAM L) bytes.
*
* Params:
* L = Desired range length.
*/
struct NetworkOrder(uint L)
if (L > ubyte.sizeof && L <= ulong.sizeof)
{
static if (L > uint.sizeof)
{
private alias StorageType = ulong;
}
else static if (L > ushort.sizeof)
{
private alias StorageType = uint;
}
else static if (L > ubyte.sizeof)
{
private alias StorageType = ushort;
}
else
{
private alias StorageType = ubyte;
}
private StorageType value;
private size_t size = L;
const pure nothrow @safe @nogc invariant
{
assert(this.size <= L);
}
/**
* Constructs a new range.
*
* $(D_PARAM T) can be any unsigned type but $(D_PARAM value) cannot be
* larger than the maximum can be stored in $(D_PARAM L) bytes. Otherwise
* an assertion failure will be caused.
*
* Params:
* T = Value type.
* value = The value should be represented by this range.
*
* Precondition: $(D_INLINECODE value <= 2 ^^ (length * 8) - 1).
*/
this(T)(const T value)
if (isUnsigned!T)
in
{
assert(value <= pow(2, L * 8) - 1);
}
body
{
this.value = value & StorageType.max;
}
/**
* Returns: LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte back() const
in
{
assert(this.length > 0);
}
body
{
return this.value & 0xff;
}
/**
* Returns: MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte front() const
in
{
assert(this.length > 0);
}
body
{
return (this.value >> ((this.length - 1) * 8)) & 0xff;
}
/**
* Eliminates the LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popBack()
in
{
assert(this.length > 0);
}
body
{
this.value >>= 8;
--this.size;
}
/**
* Eliminates the MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popFront()
in
{
assert(this.length > 0);
}
body
{
this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
--this.size;
}
/**
* Returns: Copy of this range.
*/
typeof(this) save() const
{
return this;
}
/**
* Returns: Whether the range is empty.
*/
@property bool empty() const
{
return this.length == 0;
}
/**
* Returns: Byte length.
*/
@property size_t length() const
{
return this.size;
}
}
///
pure nothrow @safe @nogc unittest
{
auto networkOrder = NetworkOrder!3(0xae34e2u);
assert(!networkOrder.empty);
assert(networkOrder.front == 0xae);
networkOrder.popFront();
assert(networkOrder.length == 2);
assert(networkOrder.front == 0x34);
assert(networkOrder.back == 0xe2);
networkOrder.popBack();
assert(networkOrder.length == 1);
assert(networkOrder.front == 0x34);
assert(networkOrder.front == 0x34);
networkOrder.popFront();
assert(networkOrder.empty);
}
// Static.
private unittest
{
static assert(isBidirectionalRange!(NetworkOrder!4));
static assert(isBidirectionalRange!(NetworkOrder!8));
static assert(!is(NetworkOrder!9));
static assert(!is(NetworkOrder!1));
}
// Tests against the system's htonl, htons.
version (PlattformUnittest)
{
private unittest
{
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
{
const value = pow(2, counter) - 1;
const inNetworkOrder = htonl(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.length == 4);
assert(!networkOrder.empty);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 3));
networkOrder.popBack();
assert(networkOrder.length == 3);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 2));
networkOrder.popFront();
assert(networkOrder.length == 2);
assert(networkOrder.front == *(p + 1));
assert(networkOrder.back == *(p + 2));
networkOrder.popFront();
assert(networkOrder.length == 1);
assert(networkOrder.front == *(p + 2));
assert(networkOrder.back == *(p + 2));
networkOrder.popBack();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
}
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
{
const value = cast(ushort) (pow(2, counter) - 1);
const inNetworkOrder = htons(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!2(value);
assert(networkOrder.length == 2);
assert(!networkOrder.empty);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 1));
networkOrder.popBack();
assert(networkOrder.length == 1);
assert(networkOrder.front == *p);
assert(networkOrder.back == *p);
networkOrder.popBack();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
networkOrder = NetworkOrder!2(value);
networkOrder.popFront();
assert(networkOrder.length == 1);
assert(networkOrder.front == *(p + 1));
assert(networkOrder.back == *(p + 1));
networkOrder.popFront();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
}
}
}
/**
* Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
* $(D_PARAM T).
*
* The byte order of $(D_PARAM r) is assumed to be big-endian. The length
* cannot be larger than $(D_INLINECODE T.sizeof). Otherwise an assertion
* failure will be caused.
*
* Params:
* T = Desired return type.
* R = Range type.
* range = Input range.
*
* Returns: Integral representation of $(D_PARAM range) with the host byte
* order.
*/
T toHostOrder(T = size_t, R)(R range)
if (isInputRange!R
&& !isInfinite!R
&& is(Unqual!(ElementType!R) == ubyte)
&& isUnsigned!T)
{
T ret;
ushort pos = T.sizeof * 8;
for (; !range.empty && range.front == 0; pos -= 8, range.popFront())
{
}
for (; !range.empty; range.popFront())
{
assert(pos != 0);
pos -= 8;
ret |= (cast(T) range.front) << pos;
}
return ret >> pos;
}
///
pure nothrow @safe @nogc unittest
{
const value = 0xae34e2u;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.toHostOrder() == value);
}
// Tests against the system's htonl, htons.
version (PlattformUnittest)
{
private unittest
{
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
{
const value = pow(2, counter) - 1;
const inNetworkOrder = htonl(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!4(value);
assert(p[0 .. uint.sizeof].toHostOrder() == value);
}
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
{
const value = cast(ushort) (pow(2, counter) - 1);
const inNetworkOrder = htons(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!2(value);
assert(p[0 .. ushort.sizeof].toHostOrder() == value);
}
}
}

View File

@ -3,16 +3,15 @@
* 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. * Network programming.
*
* 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.network;
public public import tanya.network.inet;
{ public import tanya.network.socket;
import tanya.crypto.des; public import tanya.network.url;
import tanya.crypto.mode;
import tanya.crypto.symmetric;
}

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,12 @@
* 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. * URL parser.
*
* 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.network.url; module tanya.network.url;
@ -630,32 +632,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 +667,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 +703,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 +752,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,8 +809,8 @@ 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");
} }
} }
if (user is null) if (user is null)
@ -844,7 +845,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 +867,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];
@ -948,13 +949,13 @@ struct URL(U = string)
* Attempts to parse and set the port. * Attempts to parse and set the port.
* *
* Params: * Params:
* port = String beginning with a colon followed by the port number and * port = String beginning with a colon followed by the port number and
* an optional path (query string and/or fragment), like: * an optional path (query string and/or fragment), like:
* `:12345/some_path` or `:12345`. * `:12345/some_path` or `:12345`.
* *
* 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 +985,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 +1000,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 +1010,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 +1018,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 +1044,7 @@ private unittest
{ {
try try
{ {
URL!()(t[0]); URL(t[0]);
assert(0); assert(0);
} }
catch (URIException e) catch (URIException e)
@ -1053,7 +1054,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]);
@ -1094,37 +1095,36 @@ enum Component : string
* Attempts to parse an URL from a string. * Attempts to parse an URL from a string.
* *
* Params: * Params:
* T = $(D_SYMBOL Component) member or $(D_KEYWORD null) for a * T = $(D_SYMBOL Component) member or $(D_KEYWORD null) for a
* struct with all components. * struct with all components.
* source = The string containing the URL. * source = The string containing the URL.
* *
* 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 +1158,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]);

View File

@ -1,318 +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.random;
import tanya.memory;
import std.digest.sha;
import std.typecons;
/// Block size of entropy accumulator (SHA-512).
enum blockSize = 64;
/// Maximum amount gathered from the entropy sources.
enum maxGather = 128;
/**
* Exception thrown if random number generating fails.
*/
class EntropyException : Exception
{
/**
* Params:
* msg = Message to output.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) pure @safe nothrow const @nogc
{
super(msg, file, line, next);
}
}
/**
* Interface for implementing entropy sources.
*/
abstract class EntropySource
{
/// Amount of already generated entropy.
protected ushort size_;
/**
* Returns: Minimum bytes required from the entropy source.
*/
@property immutable(ubyte) threshold() const @safe pure nothrow;
/**
* Returns: Whether this entropy source is strong.
*/
@property immutable(bool) strong() const @safe pure nothrow;
/**
* Returns: Amount of already generated entropy.
*/
@property ushort size() const @safe pure nothrow
{
return size_;
}
/**
* Params:
* size = Amount of already generated entropy. Cannot be smaller than the
* already set value.
*/
@property void size(ushort size) @safe pure nothrow
{
size_ = size;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
Nullable!ubyte poll(out ubyte[maxGather] output);
}
version (linux)
{
extern (C) long syscall(long number, ...) nothrow;
/**
* Uses getrandom system call.
*/
class PlatformEntropySource : EntropySource
{
/**
* Returns: Minimum bytes required from the entropy source.
*/
override @property immutable(ubyte) threshold() const @safe pure nothrow
{
return 32;
}
/**
* Returns: Whether this entropy source is strong.
*/
override @property immutable(bool) strong() const @safe pure nothrow
{
return true;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
out (length)
{
assert(length <= maxGather);
}
body
{
// int getrandom(void *buf, size_t buflen, unsigned int flags);
auto length = syscall(318, output.ptr, output.length, 0);
Nullable!ubyte ret;
if (length >= 0)
{
ret = cast(ubyte) length;
}
return ret;
}
}
}
/**
* Pseudorandom number generator.
* ---
* auto entropy = theAllocator.make!Entropy;
*
* ubyte[blockSize] output;
*
* output = entropy.random;
*
* theAllocator.finalize(entropy);
* ---
*/
class Entropy
{
/// Entropy sources.
protected EntropySource[] sources;
private ubyte sourceCount_;
private IAllocator allocator;
/// Entropy accumulator.
protected SHA!(maxGather * 8, 512) accumulator;
/**
* Params:
* maxSources = Maximum amount of entropy sources can be set.
* allocator = Allocator to allocate entropy sources available on the
* system.
*/
this(size_t maxSources = 20, IAllocator allocator = theAllocator)
in
{
assert(maxSources > 0 && maxSources <= ubyte.max);
assert(allocator !is null);
}
body
{
allocator.resizeArray(sources, maxSources);
version (linux)
{
this ~= allocator.make!PlatformEntropySource;
}
}
/**
* Returns: Amount of the registered entropy sources.
*/
@property ubyte sourceCount() const @safe pure nothrow
{
return sourceCount_;
}
/**
* Add an entropy source.
*
* Params:
* source = Entropy source.
*
* Returns: $(D_PSYMBOL this).
*
* See_Also:
* $(D_PSYMBOL EntropySource)
*/
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
if (Op == "~")
in
{
assert(sourceCount_ <= sources.length);
}
body
{
sources[sourceCount_++] = source;
return this;
}
/**
* Returns: Generated random sequence.
*
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
* registered or it failed.
*/
@property ubyte[blockSize] random()
in
{
assert(sourceCount_ > 0, "No entropy sources defined.");
}
body
{
bool haveStrong;
ushort done;
ubyte[blockSize] output;
do
{
ubyte[maxGather] buffer;
// Run through our entropy sources
for (ubyte i; i < sourceCount; ++i)
{
auto outputLength = sources[i].poll(buffer);
if (!outputLength.isNull)
{
if (outputLength > 0)
{
update(i, buffer, outputLength);
sources[i].size = cast(ushort) (sources[i].size + outputLength);
}
if (sources[i].size < sources[i].threshold)
{
continue;
}
else if (sources[i].strong)
{
haveStrong = true;
}
}
done = 257;
}
}
while (++done < 256);
if (!haveStrong)
{
throw allocator.make!EntropyException("No strong entropy source defined.");
}
output = accumulator.finish();
// Reset accumulator and counters and recycle existing entropy
accumulator.start();
// Perform second SHA-512 on entropy
output = sha512Of(output);
for (ubyte i = 0; i < sourceCount; ++i)
{
sources[i].size = 0;
}
return output;
}
/**
* Update entropy accumulator.
*
* Params:
* sourceId = Entropy source index in $(D_PSYMBOL sources).
* data = Data got from the entropy source.
* length = Length of the received data.
*/
protected void update(in ubyte sourceId,
ref ubyte[maxGather] data,
ubyte length) @safe pure nothrow
{
ubyte[2] header;
if (length > blockSize)
{
data[0..64] = sha512Of(data);
length = blockSize;
}
header[0] = sourceId;
header[1] = length;
accumulator.put(header);
accumulator.put(data[0..length]);
}
}

107
source/tanya/typecons.d Normal file
View File

@ -0,0 +1,107 @@
/* 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/. */
/**
* Type constructors.
*
* This module contains templates that allow to build new types from the
* available ones.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.typecons;
import std.meta;
/**
* $(D_PSYMBOL Pair) can store two heterogeneous objects.
*
* The objects can by accessed by index as $(D_INLINECODE obj[0]) and
* $(D_INLINECODE obj[1]) or by optional names (e.g.
* $(D_INLINECODE obj.first)).
*
* $(D_PARAM Specs) contains a list of object types and names. First
* comes the object type, then an optional string containing the name.
* If you want the object be accessible only by its index (`0` or `1`),
* just skip the name.
*
* Params:
* Specs = Field types and names.
*/
template Pair(Specs...)
{
template parseSpecs(int fieldCount, Specs...)
{
static if (Specs.length == 0)
{
alias parseSpecs = AliasSeq!();
}
else static if (is(Specs[0]) && fieldCount < 2)
{
static if (is(typeof(Specs[1]) == string))
{
alias parseSpecs
= AliasSeq!(Specs[0],
parseSpecs!(fieldCount + 1, Specs[2 .. $]));
}
else
{
alias parseSpecs
= AliasSeq!(Specs[0],
parseSpecs!(fieldCount + 1, Specs[1 .. $]));
}
}
else
{
static assert(false, "Invalid argument: " ~ Specs[0].stringof);
}
}
struct Pair
{
/// Field types.
alias Types = parseSpecs!(0, Specs);
static assert(Types.length == 2, "Invalid argument count.");
// Create field aliases.
static if (is(typeof(Specs[1]) == string))
{
mixin("alias " ~ Specs[1] ~ " = expand[0];");
}
static if (is(typeof(Specs[2]) == string))
{
mixin("alias " ~ Specs[2] ~ " = expand[1];");
}
else static if (is(typeof(Specs[3]) == string))
{
mixin("alias " ~ Specs[3] ~ " = expand[1];");
}
/// Represents the values of the $(D_PSYMBOL Pair) as a list of values.
Types expand;
alias expand this;
}
}
///
unittest
{
static assert(is(Pair!(int, int)));
static assert(!is(Pair!(int, 5)));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, "first", int, "second")));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, int, "second")));
static assert(!is(Pair!("first", int, "second", int)));
static assert(!is(Pair!(int, int, int)));
static assert(!is(Pair!(int, "first")));
}