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