Update README
[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 }
76}
77
78/**
79 * Destructor
80 */
81command_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*/
99void 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*/
124std::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*/
142void 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*/
169bool 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*/
251void 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}