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