Commit | Line | Data |
---|---|---|
19facd85 TJ |
1 | /* |
2 | Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy | |
3 | ||
4 | The software in this package is distributed under the GNU General | |
5 | Public License version 2 (with a special exception described below). | |
6 | ||
7 | A copy of GNU General Public License (GPL) is included in this distribution, | |
8 | in the file COPYING.GPL. | |
9 | ||
10 | As a special exception, if other files instantiate templates or use macros | |
11 | or inline functions from this file, or you compile this file and link it | |
12 | with other works to produce a work based on this file, this file | |
13 | does not by itself cause the resulting work to be covered | |
14 | by the GNU General Public License. | |
15 | ||
16 | However the source code for this file must still be made available | |
17 | in accordance with section (3) of the GNU General Public License. | |
18 | ||
19 | This exception does not invalidate any other reasons why a work based | |
20 | on this file might be covered by the GNU General Public License. | |
21 | */ | |
7087e187 GE |
22 | |
23 | #include <string> | |
24 | #include <sstream> | |
25 | ||
26 | #include <boost/archive/binary_oarchive.hpp> | |
27 | #include <boost/archive/binary_iarchive.hpp> | |
28 | #include <boost/archive/xml_oarchive.hpp> | |
29 | #include <boost/archive/xml_iarchive.hpp> | |
30 | #include <boost/serialization/serialization.hpp> | |
7087e187 | 31 | |
8104c8f7 GE |
32 | #include <boost/bind.hpp> |
33 | ||
7087e187 GE |
34 | #include "command_client.hxx" |
35 | ||
8104c8f7 GE |
36 | #ifdef HAVE_CONFIG_H |
37 | #include <config.h> | |
38 | #endif | |
39 | ||
7087e187 GE |
40 | using namespace std; |
41 | ||
42 | namespace libt2n | |
43 | { | |
44 | ||
696c95c2 TJ |
45 | /** |
46 | * Constructor | |
47 | * @param _c connection for this command. Ownership of the pointer is outside. | |
48 | * @param _command_timeout_usec timeout until the command has to be completed | |
49 | * @param _hello_timeout_usec timeout until hello has to be received | |
50 | */ | |
fb3345ad | 51 | command_client::command_client(client_connection* _c, long long _command_timeout_usec, long long _hello_timeout_usec) |
8104c8f7 | 52 | : c(_c) |
696c95c2 | 53 | , constructorException(NULL) |
8104c8f7 | 54 | { |
45a2ebc9 GE |
55 | command_timeout_usec=_command_timeout_usec; |
56 | hello_timeout_usec=_hello_timeout_usec; | |
57 | ||
8104c8f7 | 58 | // for reconnects |
fb3345ad | 59 | c->add_callback(new_connection,bind(&command_client::read_hello, boost::ref(*this))); |
8104c8f7 | 60 | |
fb3345ad | 61 | // don't expect hello from an always closed connection (like dummy_client_connection) |
b5922184 GE |
62 | if (!is_connection_closed()) |
63 | { | |
64 | try | |
65 | { | |
66 | read_hello(); | |
67 | } | |
68 | catch (t2n_communication_error &e) | |
69 | { | |
70 | c->close(); | |
71 | ||
72 | // store a copy of the exception that you can find out details about the error later | |
696c95c2 | 73 | constructorException = e.clone(); |
b5922184 GE |
74 | } |
75 | catch (...) | |
76 | { | |
77 | throw; | |
78 | } | |
79 | } | |
8104c8f7 GE |
80 | } |
81 | ||
696c95c2 TJ |
82 | /** |
83 | * Destructor | |
84 | */ | |
85 | command_client::~command_client() | |
86 | { | |
87 | if (constructorException) | |
88 | { | |
89 | delete constructorException; | |
90 | constructorException = NULL; | |
91 | } | |
92 | } | |
93 | ||
af84dfb5 | 94 | /** @brief replace the connection currently in use with a new one |
fb3345ad | 95 | @param _c pointer to the new connection |
af84dfb5 GE |
96 | |
97 | @note the old connection must still be valid when this method is called, | |
98 | it can safely be deleted after this method returned | |
99 | ||
100 | @note all callbacks registered on the old connection will be copied over | |
101 | to the new one | |
102 | */ | |
fb3345ad | 103 | void command_client::replace_connection(client_connection* _c) |
af84dfb5 GE |
104 | { |
105 | // copy all callbacks registered on the old connection | |
106 | for(callback_event_type e=static_cast<callback_event_type>(0); | |
107 | e < __events_end; | |
108 | e=static_cast<callback_event_type>(static_cast<int>(e)+1)) | |
109 | { | |
fb3345ad | 110 | list<boost::function<void ()> > evcb=c->get_callback_list(e); |
af84dfb5 GE |
111 | |
112 | for (list<boost::function<void ()> >::iterator i=evcb.begin(); i != evcb.end(); i++) | |
fb3345ad | 113 | _c->add_callback(e,*i); |
af84dfb5 GE |
114 | } |
115 | ||
116 | // replace the connection | |
117 | c=_c; | |
118 | ||
119 | read_hello(); | |
120 | } | |
121 | ||
487afb79 GE |
122 | /** @brief return a complete packet |
123 | @param usec_timeout maximum microseconds to wait until the packet is complete | |
124 | @retval packet data as std::string | |
125 | ||
126 | @note throws a t2n_transfer_error if the timeout is exceeded | |
127 | */ | |
45a2ebc9 | 128 | std::string command_client::read_packet(const long long &usec_timeout) |
8104c8f7 | 129 | { |
8104c8f7 | 130 | string resultpacket; |
45a2ebc9 GE |
131 | bool got_packet=false; |
132 | long long my_timeout=usec_timeout; | |
fb3345ad GE |
133 | while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0 && !c->is_closed()) |
134 | c->fill_buffer(my_timeout,&my_timeout); | |
8104c8f7 | 135 | |
45a2ebc9 GE |
136 | if (!got_packet) |
137 | throw t2n_transfer_error("timeout exceeded"); | |
138 | ||
139 | return resultpacket; | |
140 | } | |
141 | ||
487afb79 GE |
142 | /** @brief read and check the hello message at the beginning of a connection |
143 | ||
144 | @note throws exceptions if something went wrong | |
145 | */ | |
45a2ebc9 GE |
146 | void command_client::read_hello() |
147 | { | |
b2ba0928 GE |
148 | string resultpacket; |
149 | bool got_packet=false; | |
150 | long long my_timeout=hello_timeout_usec; | |
fb3345ad | 151 | while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0 && !c->is_closed()) |
b2ba0928 | 152 | { |
fb3345ad | 153 | c->fill_buffer(my_timeout,&my_timeout); |
b2ba0928 | 154 | |
fb3345ad | 155 | c->peek_packet(resultpacket); |
b2ba0928 GE |
156 | check_hello(resultpacket); // will throw before timeout if wrong data received |
157 | } | |
158 | ||
159 | if (!got_packet) | |
160 | throw t2n_transfer_error("timeout exceeded"); | |
161 | ||
162 | if (!check_hello(resultpacket)) | |
163 | throw t2n_version_mismatch("illegal hello received (incomplete): "+resultpacket); | |
164 | } | |
165 | ||
487afb79 GE |
166 | /** @brief check if a hello message is valid |
167 | @param hellostr std::string with the message to check | |
168 | @retval true if the hello is good and complete | |
169 | ||
170 | @note you can check incomplete hellos. you will get a false return value | |
171 | but no exception. throws exceptions as soon as something is wrong. | |
172 | */ | |
173 | bool command_client::check_hello(const std::string& hellostr) | |
b2ba0928 GE |
174 | { |
175 | istringstream hello(hellostr); | |
8104c8f7 | 176 | |
b2ba0928 GE |
177 | char chk; |
178 | ||
179 | if (hello.read(&chk,1)) | |
180 | { | |
181 | if (chk != 'T') | |
182 | throw t2n_version_mismatch("illegal hello received (T2N)"); | |
183 | } | |
184 | else | |
185 | return false; | |
186 | ||
187 | if (hello.read(&chk,1)) | |
188 | { | |
189 | if (chk != '2') | |
190 | throw t2n_version_mismatch("illegal hello received (T2N)"); | |
191 | } | |
192 | else | |
193 | return false; | |
194 | ||
195 | if (hello.read(&chk,1)) | |
196 | { | |
197 | if (chk != 'N') | |
198 | throw t2n_version_mismatch("illegal hello received (T2N)"); | |
199 | } | |
200 | else | |
201 | return false; | |
202 | ||
203 | if (hello.read(&chk,1)) | |
204 | { | |
205 | if (chk != 'v') | |
206 | throw t2n_version_mismatch("illegal hello received (T2N)"); | |
207 | } | |
208 | else | |
209 | return false; | |
8104c8f7 GE |
210 | |
211 | int prot_version; | |
b2ba0928 GE |
212 | if (hello >> prot_version) |
213 | { | |
214 | if (prot_version != PROTOCOL_VERSION) | |
215 | throw t2n_version_mismatch("not compatible with the server protocol version"); | |
216 | } | |
217 | else | |
218 | return false; | |
219 | ||
220 | if (hello.read(&chk,1)) | |
221 | { | |
222 | if (chk != ';') | |
223 | throw t2n_version_mismatch("illegal hello received (1. ;)"); | |
224 | } | |
225 | else | |
226 | return false; | |
8104c8f7 | 227 | |
b2ba0928 GE |
228 | unsigned int hbo; |
229 | if (hello.read((char*)&hbo,sizeof(hbo))) | |
230 | { | |
231 | if (hbo != 1) | |
232 | throw t2n_version_mismatch("host byte order not matching"); | |
233 | } | |
234 | else | |
235 | return false; | |
8104c8f7 | 236 | |
b2ba0928 GE |
237 | if (hello.read(&chk,1)) |
238 | { | |
239 | if (chk != ';') | |
240 | throw t2n_version_mismatch("illegal hello received (2. ;)"); | |
241 | } | |
242 | else | |
243 | return false; | |
8104c8f7 | 244 | |
b2ba0928 | 245 | return true; |
8104c8f7 GE |
246 | } |
247 | ||
487afb79 GE |
248 | /** @brief send a command to the server and store the result |
249 | @param cmd pointer to a command-object | |
250 | @param[out] res result container to store the result in | |
251 | ||
252 | @note you can check incomplete hellos. you will get a false return value | |
253 | but no exception. throws exceptions as soon as something is wrong. | |
254 | */ | |
7087e187 GE |
255 | void command_client::send_command(command* cmd, result_container &res) |
256 | { | |
257 | ostringstream ofs; | |
258 | command_container cc(cmd); | |
259 | boost::archive::binary_oarchive oa(ofs); | |
260 | ||
b5922184 GE |
261 | if (is_connection_closed()) |
262 | throw t2n_transfer_error("connection to server is closed"); | |
263 | ||
45a2ebc9 GE |
264 | try |
265 | { | |
266 | oa << cc; | |
267 | } | |
268 | catch(boost::archive::archive_exception &e) | |
269 | { | |
270 | ostringstream msg; | |
271 | msg << "archive_exception while serializing on client-side, code " << e.code << " (" << e.what() << ")"; | |
272 | throw t2n_serialization_error(msg.str()); | |
273 | } | |
274 | catch(...) | |
275 | { throw; } | |
7087e187 | 276 | |
d535333f | 277 | std::ostream* ostr; |
fb3345ad | 278 | if ((ostr=c->get_logstream(fulldebug))!=NULL) |
d535333f GE |
279 | { |
280 | (*ostr) << "sending command, decoded data: " << std::endl; | |
281 | boost::archive::xml_oarchive xo(*ostr); | |
282 | xo << BOOST_SERIALIZATION_NVP(cc); | |
696c95c2 | 283 | } |
d535333f | 284 | |
fb3345ad | 285 | c->write(ofs.str()); |
7087e187 | 286 | |
45a2ebc9 | 287 | istringstream ifs(read_packet(command_timeout_usec)); |
7087e187 GE |
288 | boost::archive::binary_iarchive ia(ifs); |
289 | ||
45a2ebc9 GE |
290 | try |
291 | { | |
292 | ia >> res; | |
293 | } | |
294 | catch(boost::archive::archive_exception &e) | |
295 | { | |
296 | ostringstream msg; | |
297 | msg << "archive_exception while deserializing on client-side, code " << e.code << " (" << e.what() << ")"; | |
298 | throw t2n_serialization_error(msg.str()); | |
299 | } | |
300 | catch(...) | |
301 | { throw; } | |
d535333f | 302 | |
fb3345ad | 303 | if ((ostr=c->get_logstream(fulldebug))!=NULL) |
d535333f GE |
304 | { |
305 | (*ostr) << "received result, decoded data: " << std::endl; | |
306 | boost::archive::xml_oarchive xo(*ostr); | |
307 | xo << BOOST_SERIALIZATION_NVP(res); | |
308 | } | |
7087e187 GE |
309 | } |
310 | ||
311 | } |