Don't catch unknown exceptions if we are only going to rethrow them.
[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     }
76 }
77
78 /**
79  * Destructor
80  */
81 command_client::~command_client()
82 {
83     if (constructorException)
84     {
85         delete constructorException;
86         constructorException = NULL;
87     }
88 }
89
90 /** @brief replace the connection currently in use with a new one
91     @param _c pointer to the new connection
92
93     @note the old connection must still be valid when this method is called,
94           it can safely be deleted after this method returned
95
96     @note all callbacks registered on the old connection will be copied over
97           to the new one
98 */
99 void command_client::replace_connection(client_connection* _c)
100 {
101     // copy all callbacks registered on the old connection
102     for(callback_event_type e=static_cast<callback_event_type>(0);
103         e < __events_end;
104         e=static_cast<callback_event_type>(static_cast<int>(e)+1))
105     {
106         list<boost::function<void ()> > evcb=c->get_callback_list(e);
107
108         for (list<boost::function<void ()> >::iterator i=evcb.begin(); i != evcb.end(); i++)
109             _c->add_callback(e,*i);
110     }
111
112     // replace the connection
113     c=_c;
114
115     read_hello();
116 }
117
118 /** @brief return a complete packet
119     @param usec_timeout maximum microseconds to wait until the packet is complete
120     @retval packet data as std::string
121
122     @note throws a t2n_transfer_error if the timeout is exceeded
123 */
124 std::string command_client::read_packet(const long long &usec_timeout)
125 {
126     string resultpacket;
127     bool got_packet=false;
128     long long my_timeout=usec_timeout;
129     while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0  && !c->is_closed())
130         c->fill_buffer(my_timeout,&my_timeout);
131
132     if (!got_packet)
133         throw t2n_transfer_error("timeout exceeded");
134
135     return resultpacket;
136 }
137
138 /** @brief read and check the hello message at the beginning of a connection
139
140     @note throws exceptions if something went wrong
141 */
142 void command_client::read_hello()
143 {
144     string resultpacket;
145     bool got_packet=false;
146     long long my_timeout=hello_timeout_usec;
147     while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0  && !c->is_closed())
148     {
149         c->fill_buffer(my_timeout,&my_timeout);
150
151         c->peek_packet(resultpacket);
152         check_hello(resultpacket);           // will throw before timeout if wrong data received
153     }
154
155     if (!got_packet)
156         throw t2n_transfer_error("timeout exceeded");
157
158     if (!check_hello(resultpacket))
159         throw t2n_version_mismatch("illegal hello received (incomplete): "+resultpacket);
160 }
161
162 /** @brief check if a hello message is valid
163     @param hellostr std::string with the message to check
164     @retval true if the hello is good and complete
165
166     @note you can check incomplete hellos. you will get a false return value
167           but no exception. throws exceptions as soon as something is wrong.
168 */
169 bool command_client::check_hello(const std::string& hellostr)
170 {
171     istringstream hello(hellostr);
172
173     char chk;
174
175     if (hello.read(&chk,1))
176     {
177         if (chk != 'T')
178             throw t2n_version_mismatch("illegal hello received (T2N)");
179     }
180     else
181         return false;
182
183     if (hello.read(&chk,1))
184     {
185         if (chk != '2')
186             throw t2n_version_mismatch("illegal hello received (T2N)");
187     }
188     else
189         return false;
190
191     if (hello.read(&chk,1))
192     {
193         if (chk != 'N')
194             throw t2n_version_mismatch("illegal hello received (T2N)");
195     }
196     else
197         return false;
198
199     if (hello.read(&chk,1))
200     {
201         if (chk != 'v')
202             throw t2n_version_mismatch("illegal hello received (T2N)");
203     }
204     else
205         return false;
206
207     int prot_version;
208     if (hello >> prot_version)
209     {
210         if (prot_version != PROTOCOL_VERSION)
211             throw t2n_version_mismatch("not compatible with the server protocol version");
212     }
213     else
214         return false;
215
216     if (hello.read(&chk,1))
217     {
218         if (chk != ';')
219             throw t2n_version_mismatch("illegal hello received (1. ;)");
220     }
221     else
222         return false;
223
224     unsigned int hbo;
225     if (hello.read((char*)&hbo,sizeof(hbo)))
226     {
227         if (hbo != 1)
228             throw t2n_version_mismatch("host byte order not matching");
229     }
230     else
231         return false;
232
233     if (hello.read(&chk,1))
234     {
235         if (chk != ';')
236             throw t2n_version_mismatch("illegal hello received (2. ;)");
237     }
238     else
239         return false;
240
241     return true;
242 }
243
244 /** @brief send a command to the server and store the result
245     @param cmd pointer to a command-object
246     @param[out] res result container to store the result in
247
248     @note you can check incomplete hellos. you will get a false return value
249           but no exception. throws exceptions as soon as something is wrong.
250 */
251 void command_client::send_command(command* cmd, result_container &res)
252 {
253     ostringstream ofs;
254     command_container cc(cmd);
255     boost::archive::binary_oarchive oa(ofs);
256
257     if (is_connection_closed())
258         throw t2n_transfer_error("connection to server is closed");
259
260     try
261     {
262         oa << cc;
263     }
264     catch(boost::archive::archive_exception &e)
265     {
266         ostringstream msg;
267         msg << "archive_exception while serializing on client-side, code " << e.code << " (" << e.what() << ")";
268         throw t2n_serialization_error(msg.str());
269     }
270
271     std::ostream* ostr;
272     if ((ostr=c->get_logstream(fulldebug))!=NULL)
273     {
274         (*ostr) << "sending command, decoded data: " << std::endl;
275         boost::archive::xml_oarchive xo(*ostr);
276         xo << BOOST_SERIALIZATION_NVP(cc);
277    }
278
279     c->write(ofs.str());
280
281     istringstream ifs(read_packet(command_timeout_usec));
282     boost::archive::binary_iarchive ia(ifs);
283
284     try
285     {
286         ia >> res;
287     }
288     catch(boost::archive::archive_exception &e)
289     {
290         ostringstream msg;
291         msg << "archive_exception while deserializing on client-side, code " << e.code << " (" << e.what() << ")";
292         throw t2n_serialization_error(msg.str());
293     }
294
295     if ((ostr=c->get_logstream(fulldebug))!=NULL)
296     {
297         (*ostr) << "received result, decoded data: " << std::endl;
298         boost::archive::xml_oarchive xo(*ostr);
299         xo << BOOST_SERIALIZATION_NVP(res);
300     }
301 }
302
303 }