1 /**
2  * Communication channels
3  *
4  *	Copyright: © 2016-2026 Orfeo Da Vià.
5  *	License: Boost Software License - Version 1.0 - August 17th, 2003
6  *	Authors: Orfeo Da Vià
7  */
8 module dfins.channel;
9 
10 import std.experimental.logger;
11 import core.time : dur;
12 import std.socket;
13 
14 /**
15  * Timeout exception
16  */
17 class ChannelTimeoutException : Exception {
18    this(string msg, string ip, const(ubyte[]) messageSent, string file = null, size_t line = 0) @trusted {
19       _ip = ip;
20       _messageSent = messageSent;
21 
22       import std.string : format;
23 
24       super("%s -- PLC ip %s".format(msg, ip));
25    }
26 
27    private string _ip;
28    string ip() {
29       return _ip;
30    }
31 
32    private const(ubyte[]) _messageSent;
33    const(ubyte[]) messageSent() {
34       return _messageSent;
35    }
36 }
37 
38 unittest {
39    ChannelTimeoutException ex = new ChannelTimeoutException("message", "192.168.221.1", [10, 11]);
40    assert(ex.ip == "192.168.221.1");
41    assert(ex.messageSent == [10, 11]);
42    assert(ex.msg == "message -- PLC ip 192.168.221.1");
43 }
44 
45 
46 interface IChannel {
47    ubyte[] send(const(ubyte[]) msg);
48 }
49 
50 /**
51  * UDP channel.
52  */
53 class UdpChannel : IChannel {
54    private Socket socket;
55    private Address address;
56    this(Socket socket, Address address) {
57       assert(socket !is null);
58       this.socket = socket;
59 
60       assert(address !is null);
61       this.address = address;
62    }
63 
64    /**
65     * Send a message and wait for a reply
66     */
67    ubyte[] send(const(ubyte[]) msg) {
68       int attempt;
69       while (true) {
70          try {
71             return sendSingle(msg);
72          } catch (Exception e) {
73             ++attempt;
74             tracef("attempt %d failed", attempt);
75             if (attempt > 2) {
76                // tre tentativi
77                throw e;
78             }
79          }
80       }
81    }
82 
83    private ubyte[] sendSingle(const(ubyte[]) msg) {
84       socket.sendTo(msg, address);
85 
86       ubyte[1024] reply;
87       version (Win32) {
88          uint len = socket.receiveFrom(reply, address);
89       } else {
90          long len = socket.receiveFrom(reply, address);
91       }
92 
93       if (len > 0) {
94          return reply[0 .. len].dup;
95       } else {
96          throw new ChannelTimeoutException("Channel send error", address.toAddrString, msg);
97       }
98    }
99 }
100 
101 /**
102  * Convenience functions that create an `IChannel` object
103  *
104  * Params:
105  *  ip = IP address
106  *  timeout = Send and recieve timeout in ms
107  *  port = Port number (defaul 9600)
108  */
109 IChannel createUdpChannel(string ip, long timeout, ushort port = 9600)
110 in {
111    assert(ip.length);
112    assert(timeout >= 0);
113 }
114 do {
115    auto sock = new UdpSocket();
116    sock.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"msecs"(timeout));
117 
118    Address addr = parseAddress(ip, port);
119    return new UdpChannel(sock, addr);
120 }