1 ///
2 module modbus.backend.base;
3 
4 import std.traits : Unqual, isNumeric, isArray;
5 import std.range : ElementType;
6 
7 import modbus.types;
8 public import modbus.exception;
9 public import modbus.backend.specrules;
10 
11 /// Message builder and parser
12 interface Backend
13 {
14 @nogc:
15     /++ Full build message for sending
16 
17         work on preallocated buffer
18 
19         Params:
20             buf = preallocated buffer
21             dev = modbus device number
22             fnc = function number
23             args = message data
24 
25         Returns:
26             slice of preallocated buffer with message
27      +/
28     void[] buildMessage(Args...)(void[] buf, ulong dev, ubyte fnc, ulong stamp, Args args)
29     {
30         size_t idx;
31         startMessage(buf, idx, dev, fnc, stamp);
32         recursiveAppend(buf, idx, args);
33         finalizeMessage(buf, idx);
34         return buf[0..idx];
35     }
36 
37     ///
38     void recursiveAppend(Args...)(void[] buf, ref size_t idx, Args args)
39         if (Args.length >= 1)
40     {
41         static if (Args.length == 1)
42         {
43             alias T = Args[0];
44             auto val = args[0];
45             static if (isArray!T)
46             {
47                 static if (is(Unqual!(ElementType!T) == void))
48                     appendBytes(buf, idx, val);
49                 else foreach (e; val) recursiveAppend(buf, idx, e);
50             }
51             else
52             {
53                 static if (is(T == struct))
54                     foreach (v; val.tupleof) recursiveAppend(buf, idx, v);
55                 else static if (isNumeric!T) append(buf, idx, val);
56                 else static assert(0, "unsupported type " ~ T.stringof);
57             }
58         }
59         else static foreach (arg; args) recursiveAppend(buf, idx, arg);
60     }
61 
62     ///
63     enum ParseResult
64     {
65         success, ///
66         incomplete, ///
67         checkFails ///
68     }
69 
70     /++ Read data to temp message buffer
71 
72         Params:
73             data = parsing data buffer, CRC and etc
74             result = reference to result message
75      +/
76     ParseResult parseMessage(const(void)[] data, ref Message result);
77 
78     ///
79     size_t aduLength(size_t dataBytes=0);
80 
81     const(void)[] packT(T)(T value) { return sr.packT(value); }
82     T unpackT(T)(const(void)[] data) { return sr.unpackT!T(data); }
83     T unpackTT(T)(ref const T value) { return sr.unpackT!T((cast(void*)&value)[0..T.sizeof]); }
84 
85 protected:
86 
87     SpecRules sr() @property;
88 
89     /// start building message
90     void startMessage(void[] buf, ref size_t idx, ulong dev, ubyte fnc, ulong stamp);
91 
92     /// append data to message buffer
93     void append(T)(void[] buf, ref size_t idx, T val)
94         if (isNumeric!T && !is(T == real))
95     {
96         union cst { T value; void[T.sizeof] data; }
97         appendBytes(buf, idx, sr.pack(cst(val).data[]));
98     }
99     /// ditto
100     void appendBytes(void[] buf, ref size_t idx, const(void)[]);
101     ///
102     void finalizeMessage(void[] buf, ref size_t idx);
103 }
104 
105 /++ Basic functionality of Backend
106  +/
107 abstract class BaseBackend : Backend
108 {
109 protected:
110     enum functionTypeSize = 1;
111     SpecRules specRules;
112 
113     immutable size_t devOffset;
114     immutable size_t serviceData;
115 
116     override SpecRules sr() @nogc @property { return specRules; }
117 
118 public:
119 
120     /++
121         Params:
122             s = rules for pack N-byte data to sending package
123             serviceData = size of CRC for RTU, protocol id for TCP etc
124             deviceOffset = offset of device number (address) in message
125      +/
126     this(SpecRules s, size_t serviceData, size_t deviceOffset)
127     {
128         import std.exception : enforce;
129         this.specRules = s !is null ? s : new BasicSpecRules;
130         this.serviceData = serviceData;
131         this.devOffset = deviceOffset;
132     }
133 
134 @nogc:
135 
136     override
137     {
138         ParseResult parseMessage(const(void)[] data, ref Message msg)
139         {
140             if (data.length < aduLength)
141                 return ParseResult.incomplete;
142             if (auto err = sr.unpackDF(data[devOffset..$], msg.dev, msg.fnc))
143                 return ParseResult.incomplete;
144             if (!check(data))
145                 return ParseResult.checkFails;
146 
147             msg.stamp = getStamp(data);
148 
149             msg.data = data[startDataSplit..$-endDataSplit];
150             return ParseResult.success;
151         }
152 
153         size_t aduLength(size_t dataBytes=0)
154         {
155             return serviceData +
156                    sr.deviceTypeSize +
157                    functionTypeSize +
158                    dataBytes; 
159         }
160     }
161 
162 protected:
163 
164     override
165     {
166         void appendBytes(void[] buf, ref size_t idx, const(void)[] v)
167         {
168             auto inc = v.length;
169             if (idx + inc + serviceData >= buf.length)
170                 throwModbusException("many args");
171             buf[idx..idx+inc] = v[];
172             idx += inc;
173             version (modbus_verbose) debug
174                 .trace("append msg buffer data: ", buf[0..idx]);
175         }
176     }
177 
178     abstract
179     {
180         void startMessage(void[] buf, ref size_t idx, ulong dev, ubyte func, ulong stamp);
181         void finalizeMessage(void[] buf, ref size_t idx);
182 
183         bool check(const(void)[] data);
184         ulong getStamp(const(void)[] data);
185         size_t endDataSplit() @property;
186     }
187 
188     size_t startDataSplit() @property
189     { return devOffset + sr.deviceTypeSize + functionTypeSize; }
190 
191     void appendDF(void[] buf, ref size_t idx, ulong dev, ubyte fnc)
192     { appendBytes(buf, idx, sr.packDF(dev, fnc)); }
193 }