Change license from LGPL to GPL version 2 + linking exception. This fixes C++ templat...
[libt2n] / src / command_client.cpp
CommitLineData
19facd85
TJ
1/*
2Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy
3
4The software in this package is distributed under the GNU General
5Public License version 2 (with a special exception described below).
6
7A copy of GNU General Public License (GPL) is included in this distribution,
8in the file COPYING.GPL.
9
10As a special exception, if other files instantiate templates or use macros
11or inline functions from this file, or you compile this file and link it
12with other works to produce a work based on this file, this file
13does not by itself cause the resulting work to be covered
14by the GNU General Public License.
15
16However the source code for this file must still be made available
17in accordance with section (3) of the GNU General Public License.
18
19This exception does not invalidate any other reasons why a work based
20on 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
40using namespace std;
41
42namespace 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 51command_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 */
85command_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 103void 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 128std::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
146void 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*/
173bool 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
255void 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}