Change license from LGPL to GPL version 2 + linking exception. This fixes C++ templat...
[libt2n] / src / command_client.cpp
... / ...
CommitLineData
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*/
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>
31
32#include <boost/bind.hpp>
33
34#include "command_client.hxx"
35
36#ifdef HAVE_CONFIG_H
37#include <config.h>
38#endif
39
40using namespace std;
41
42namespace libt2n
43{
44
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 */
51command_client::command_client(client_connection* _c, long long _command_timeout_usec, long long _hello_timeout_usec)
52 : c(_c)
53 , constructorException(NULL)
54{
55 command_timeout_usec=_command_timeout_usec;
56 hello_timeout_usec=_hello_timeout_usec;
57
58 // for reconnects
59 c->add_callback(new_connection,bind(&command_client::read_hello, boost::ref(*this)));
60
61 // don't expect hello from an always closed connection (like dummy_client_connection)
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
73 constructorException = e.clone();
74 }
75 catch (...)
76 {
77 throw;
78 }
79 }
80}
81
82/**
83 * Destructor
84 */
85command_client::~command_client()
86{
87 if (constructorException)
88 {
89 delete constructorException;
90 constructorException = NULL;
91 }
92}
93
94/** @brief replace the connection currently in use with a new one
95 @param _c pointer to the new connection
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*/
103void command_client::replace_connection(client_connection* _c)
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 {
110 list<boost::function<void ()> > evcb=c->get_callback_list(e);
111
112 for (list<boost::function<void ()> >::iterator i=evcb.begin(); i != evcb.end(); i++)
113 _c->add_callback(e,*i);
114 }
115
116 // replace the connection
117 c=_c;
118
119 read_hello();
120}
121
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*/
128std::string command_client::read_packet(const long long &usec_timeout)
129{
130 string resultpacket;
131 bool got_packet=false;
132 long long my_timeout=usec_timeout;
133 while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0 && !c->is_closed())
134 c->fill_buffer(my_timeout,&my_timeout);
135
136 if (!got_packet)
137 throw t2n_transfer_error("timeout exceeded");
138
139 return resultpacket;
140}
141
142/** @brief read and check the hello message at the beginning of a connection
143
144 @note throws exceptions if something went wrong
145*/
146void command_client::read_hello()
147{
148 string resultpacket;
149 bool got_packet=false;
150 long long my_timeout=hello_timeout_usec;
151 while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0 && !c->is_closed())
152 {
153 c->fill_buffer(my_timeout,&my_timeout);
154
155 c->peek_packet(resultpacket);
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
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)
174{
175 istringstream hello(hellostr);
176
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;
210
211 int prot_version;
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;
227
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;
236
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;
244
245 return true;
246}
247
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*/
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
261 if (is_connection_closed())
262 throw t2n_transfer_error("connection to server is closed");
263
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; }
276
277 std::ostream* ostr;
278 if ((ostr=c->get_logstream(fulldebug))!=NULL)
279 {
280 (*ostr) << "sending command, decoded data: " << std::endl;
281 boost::archive::xml_oarchive xo(*ostr);
282 xo << BOOST_SERIALIZATION_NVP(cc);
283 }
284
285 c->write(ofs.str());
286
287 istringstream ifs(read_packet(command_timeout_usec));
288 boost::archive::binary_iarchive ia(ifs);
289
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; }
302
303 if ((ostr=c->get_logstream(fulldebug))!=NULL)
304 {
305 (*ostr) << "received result, decoded data: " << std::endl;
306 boost::archive::xml_oarchive xo(*ostr);
307 xo << BOOST_SERIALIZATION_NVP(res);
308 }
309}
310
311}