Change license from LGPL to GPL version 2 + linking exception. This fixes C++ templat...
[libt2n] / src / command_client.cpp
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 */
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
40 using namespace std;
41
42 namespace 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  */
51 command_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  */
85 command_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 */
103 void 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 */
128 std::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 */
146 void 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 */
173 bool 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 */
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
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 }