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