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