1 ///
2 module modbus.backend.specrules;
3 
4 version (modbus_verbose)
5     import std.experimental.logger;
6 
7 ///
8 interface SpecRules
9 {
10 pure @nogc:
11     ///
12     @property size_t deviceTypeSize();
13 
14     ///
15     const(void)[] packDF(ulong dev, ubyte fnc);
16     ///
17     int unpackDF(const(void)[] buf, ref ulong dev, ref ubyte fnc);
18 
19     ///
20     const(void)[] pack(const(void)[]);
21     ///
22     final const(void)[] packT(T)(T value)
23     { return pack((cast(void*)&value)[0..T.sizeof]); }
24 
25     ///
26     const(void)[] unpack(const(void)[] data);
27     ///
28     final T unpackT(T)(const(void)[] data)
29     { return (cast(T[])unpack(data))[0]; }
30 }
31 
32 ///
33 class BasicSpecRules : SpecRules
34 {
35     import std.bitmanip : write;
36     import std.bitmanip : read;
37 
38 @nogc:
39 
40     protected ubyte[16] buffer;
41 
42     private const(void)[] typedPack(T)(T val)
43     {
44         static if (T.sizeof <= ushort.sizeof)
45             buffer[].write(val, 0);
46         else
47         {
48             size_t i;
49             auto data = cast(ushort[])((cast(void*)&val)[0..T.sizeof]);
50             foreach (part; data) buffer[].write(part, &i);
51         }
52 
53         return buffer[0..T.sizeof];
54     }
55 
56     private const(void)[] typedUnpack(T)(const(void)[] data)
57     {
58         import std.range : chunks, enumerate;
59 
60         static if (T.sizeof == ubyte.sizeof) return data;
61         else
62         {
63             enum us = ushort.sizeof;
64             foreach (i, s; (cast(const(ubyte)[])data).chunks(us).enumerate)
65             {
66                 auto tmp = s.read!ushort;
67                 buffer[i*us..(i+1)*us] = (cast(ubyte*)&tmp)[0..us];
68             }
69             return buffer[0..T.sizeof];
70         }
71     }
72 
73     import std.meta : AliasSeq;
74     alias Types = AliasSeq!(byte,short,int,long);
75 
76 public pure override:
77     @property size_t deviceTypeSize() { return 1; }
78 
79     const(void)[] packDF(ulong dev, ubyte fnc) 
80     {
81         assert(dev <= 255, "device number can't be more 255");
82         buffer[].write(cast(ubyte)dev, 0);
83         buffer[].write(fnc, 1);
84         return buffer[0..deviceTypeSize+1];
85     }
86 
87     int unpackDF(const(void)[] vbuf, ref ulong dev, ref ubyte fnc)
88     {
89         import std.bitmanip : peek;
90         auto buf = cast(const(ubyte)[])vbuf;
91         if (buf.length >= 1) dev = buf.peek!ubyte(0);
92         else return 2;
93         if (buf.length >= 2) fnc = buf.peek!ubyte(1);
94         else return 1;
95         return 0;
96     }
97 
98     const(void)[] pack(const(void)[] data)
99     {
100         final switch (data.length) foreach (T; Types)
101             case T.sizeof: return typedPack((cast(T[])data)[0]);
102     }
103 
104     const(void)[] unpack(const(void)[] data)
105     {
106         final switch (data.length) foreach (T; Types)
107             case T.sizeof: return typedUnpack!T(data);
108     }
109 }
110 
111 version(unittest)
112 {
113     void[] tb(T)(T value) { return cast(void[])[value]; }
114     T bt(T)(const(void)[] data) { return (cast(T[])data)[0]; }
115 }
116 
117 unittest
118 {
119     auto bsp = new BasicSpecRules;
120     assert(cast(ubyte[])bsp.packT(cast(ubyte)(0xAB)) == [0xAB]);
121     assert(cast(ubyte[])bsp.packT(cast(ushort)(0xA1B2)) == [0xA1, 0xB2]);
122     assert(cast(ubyte[])bsp.packT(cast(int)(0xA1B2C3D4)) ==
123             [0xC3, 0xD4, 0xA1, 0xB2]);
124     assert(cast(ubyte[])bsp.pack(tb(0xA1B2C3D4E5F6A7B8)) ==
125             [0xA7, 0xB8, 0xE5, 0xF6, 0xC3, 0xD4, 0xA1, 0xB2]);
126 
127     assert(bt!ulong(bsp.unpack(cast(ubyte[])[0xA7,0xB8,0xE5,0xF6,0xC3,0xD4,0xA1,0xB2])) ==
128             0xA1B2C3D4E5F6A7B8);
129 
130     import std.random;
131     void test(T)()
132     {
133         auto val = cast(T)uniform(0, T.max);
134         assert(bsp.unpackT!T(bsp.packT(val)) == val);
135     }
136     foreach (ubyte i; 0 .. 256)
137         assert(bsp.unpackT!ubyte(bsp.packT(i)) == i);
138 
139     foreach (i; 0 .. 10_000) test!ushort;
140     foreach (i; 0 .. 10_000) test!uint;
141     foreach (i; 0 .. 10_000) test!ulong;
142 }