0
|
1 /* |
|
2 * connection.h -- value wrapper for connecting to irccd |
|
3 * |
|
4 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> |
|
5 * |
|
6 * Permission to use, copy, modify, and/or distribute this software for any |
|
7 * purpose with or without fee is hereby granted, provided that the above |
|
8 * copyright notice and this permission notice appear in all copies. |
|
9 * |
|
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
17 */ |
|
18 |
|
19 #ifndef _IRCCD_CONNECTION_H_ |
|
20 #define _IRCCD_CONNECTION_H_ |
|
21 |
|
22 #include <cassert> |
|
23 #include <stdexcept> |
|
24 |
|
25 #include <elapsed-timer.h> |
|
26 #include <json.h> |
|
27 #include <sockets.h> |
|
28 #include <system.h> |
|
29 #include <util.h> |
|
30 |
|
31 namespace irccd { |
|
32 |
|
33 /** |
|
34 * @class Connection |
|
35 * @brief Abstract class for connecting to irccd from Ip or Local addresses. |
|
36 */ |
|
37 class Connection { |
|
38 protected: |
|
39 ElapsedTimer m_timer; |
|
40 |
|
41 /** |
|
42 * Clamp the time to wait to be sure that it will be never less than 0. |
|
43 */ |
|
44 inline int clamp(int timeout) |
|
45 { |
|
46 return timeout < 0 ? -1 : (timeout - (int)m_timer.elapsed() < 0) ? 0 : (timeout - m_timer.elapsed()); |
|
47 } |
|
48 |
|
49 public: |
|
50 /** |
|
51 * Wait for the next requested response. |
|
52 * |
|
53 * @param name the response name |
|
54 * @param timeout the optional timeout |
|
55 * @return the object |
|
56 * @throw net::Error on errors or on timeout |
|
57 */ |
|
58 json::Value next(const std::string &name, int timeout = 30000); |
|
59 |
|
60 /** |
|
61 * Just wait if the operation succeeded. |
|
62 * |
|
63 * @param name the response name |
|
64 * @param timeout the timeout |
|
65 */ |
|
66 void verify(const std::string &name, int timeout = 30000); |
|
67 |
|
68 /** |
|
69 * Check if the socket is still connected. |
|
70 * |
|
71 * @return true if connected |
|
72 */ |
|
73 virtual bool isConnected() const noexcept = 0; |
|
74 |
|
75 /** |
|
76 * Try to connect to the host. |
|
77 * |
|
78 * @param timeout the maximum time in milliseconds |
|
79 * @throw net::Error on errors or timeout |
|
80 */ |
|
81 virtual void connect(int timeout = 30000) = 0; |
|
82 |
|
83 /** |
|
84 * Try to send the message in 30 seconds. The message must not end with \r\n\r\n, it is added automatically. |
|
85 * |
|
86 * @pre msg must not be empty |
|
87 * @param msg the message to send |
|
88 * @param timeout the maximum time in milliseconds |
|
89 * @throw net::Error on errors |
|
90 */ |
|
91 virtual void send(std::string msg, int timeout = 30000) = 0; |
|
92 |
|
93 /** |
|
94 * Get the next event from irccd. |
|
95 * |
|
96 * This functions throws if the connection is lost. |
|
97 * |
|
98 * @param timeout the maximum time in milliseconds |
|
99 * @return the next event |
|
100 * @throw net::Error on errors or disconnection |
|
101 */ |
|
102 virtual json::Value next(int timeout = 30000) = 0; |
|
103 }; |
|
104 |
|
105 /** |
|
106 * @class ConnectionBase |
|
107 * @brief Implementation for Ip or Local. |
|
108 */ |
|
109 template <typename Address> |
|
110 class ConnectionBase : public Connection { |
|
111 private: |
|
112 net::SocketTcp<Address> m_socket; |
|
113 net::Listener<> m_listener; |
|
114 Address m_address; |
|
115 |
|
116 /* Input buffer */ |
|
117 std::string m_input; |
|
118 |
|
119 public: |
|
120 /** |
|
121 * Construct the socket but do not connect immediately. |
|
122 * |
|
123 * @param address the address |
|
124 */ |
|
125 ConnectionBase(Address address) |
|
126 : m_address(std::move(address)) |
|
127 { |
|
128 m_socket.set(net::option::SockBlockMode{false}); |
|
129 m_listener.set(m_socket.handle(), net::Condition::Readable); |
|
130 } |
|
131 |
|
132 /** |
|
133 * @copydoc Connection::isConnected |
|
134 */ |
|
135 bool isConnected() const noexcept override |
|
136 { |
|
137 return m_socket.state() == net::State::Connected; |
|
138 } |
|
139 |
|
140 /** |
|
141 * @copydoc Connection::connect |
|
142 */ |
|
143 void connect(int timeout) override; |
|
144 |
|
145 /** |
|
146 * @copydoc Connection::send |
|
147 */ |
|
148 void send(std::string msg, int timeout) override; |
|
149 |
|
150 /** |
|
151 * @copydoc Connection::next |
|
152 */ |
|
153 json::Value next(int timeout) override; |
|
154 }; |
|
155 |
|
156 template <typename Address> |
|
157 void ConnectionBase<Address>::connect(int timeout) |
|
158 { |
|
159 m_socket.connect(m_address); |
|
160 |
|
161 if (m_socket.state() == net::State::Connecting) { |
|
162 m_listener.set(m_socket.handle(), net::Condition::Writable); |
|
163 m_listener.wait(timeout); |
|
164 m_socket.connect(); |
|
165 m_listener.unset(m_socket.handle(), net::Condition::Writable); |
|
166 } |
|
167 } |
|
168 |
|
169 template <typename Address> |
|
170 void ConnectionBase<Address>::send(std::string msg, int timeout) |
|
171 { |
|
172 assert(!msg.empty()); |
|
173 |
|
174 /* Add termination */ |
|
175 msg += "\r\n\r\n"; |
|
176 |
|
177 m_listener.remove(m_socket.handle()); |
|
178 m_listener.set(m_socket.handle(), net::Condition::Writable); |
|
179 m_timer.reset(); |
|
180 |
|
181 while (!msg.empty()) { |
|
182 /* Do not wait the time that is already passed */ |
|
183 m_listener.wait(clamp(timeout)); |
|
184 |
|
185 /* Try to send at most as possible */ |
|
186 msg.erase(0, m_socket.send(msg)); |
|
187 } |
|
188 |
|
189 /* Timeout? */ |
|
190 if (!msg.empty()) |
|
191 throw std::runtime_error("operation timed out while sending to irccd"); |
|
192 } |
|
193 |
|
194 template <typename Address> |
|
195 json::Value ConnectionBase<Address>::next(int timeout) |
|
196 { |
|
197 /* Maybe there is already something */ |
|
198 std::string buffer = util::nextNetwork(m_input); |
|
199 |
|
200 m_listener.remove(m_socket.handle()); |
|
201 m_listener.set(m_socket.handle(), net::Condition::Readable); |
|
202 m_timer.reset(); |
|
203 |
|
204 /* Read if there is nothing */ |
|
205 while (buffer.empty() && isConnected()) { |
|
206 /* Wait and read */ |
|
207 m_listener.wait(clamp(timeout)); |
|
208 m_input += m_socket.recv(512); |
|
209 |
|
210 /* Finally try */ |
|
211 buffer = util::nextNetwork(m_input); |
|
212 } |
|
213 |
|
214 if (!isConnected()) |
|
215 throw std::runtime_error("connection lost"); |
|
216 |
|
217 json::Value value(json::Buffer{buffer}); |
|
218 |
|
219 if (!value.isObject()) |
|
220 throw std::invalid_argument("invalid message received"); |
|
221 |
|
222 return value; |
|
223 } |
|
224 |
|
225 } // !irccd |
|
226 |
|
227 #endif // !_IRCCD_CONNECTION_H_ |