Update README
[libt2n] / src / command_client.cpp
CommitLineData
19facd85
TJ
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*/
7087e187
GE
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>
7087e187 31
8104c8f7
GE
32#include <boost/bind.hpp>
33
7087e187
GE
34#include "command_client.hxx"
35
8104c8f7
GE
36#ifdef HAVE_CONFIG_H
37#include <config.h>
38#endif
39
7087e187
GE
40using namespace std;
41
42namespace libt2n
43{
44
696c95c2
TJ
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 */
fb3345ad 51command_client::command_client(client_connection* _c, long long _command_timeout_usec, long long _hello_timeout_usec)
8104c8f7 52 : c(_c)
696c95c2 53 , constructorException(NULL)
8104c8f7 54{
45a2ebc9
GE
55 command_timeout_usec=_command_timeout_usec;
56 hello_timeout_usec=_hello_timeout_usec;
57
8104c8f7 58 // for reconnects
fb3345ad 59 c->add_callback(new_connection,bind(&command_client::read_hello, boost::ref(*this)));
8104c8f7 60
fb3345ad 61 // don't expect hello from an always closed connection (like dummy_client_connection)
b5922184
GE
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
696c95c2 73 constructorException = e.clone();
b5922184 74 }
b5922184 75 }
8104c8f7
GE
76}
77
696c95c2
TJ
78/**
79 * Destructor
80 */
81command_client::~command_client()
82{
83 if (constructorException)
84 {
85 delete constructorException;
86 constructorException = NULL;
87 }
88}
89
af84dfb5 90/** @brief replace the connection currently in use with a new one
fb3345ad 91 @param _c pointer to the new connection
af84dfb5
GE
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*/
fb3345ad 99void command_client::replace_connection(client_connection* _c)
af84dfb5
GE
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 {
fb3345ad 106 list<boost::function<void ()> > evcb=c->get_callback_list(e);
af84dfb5
GE
107
108 for (list<boost::function<void ()> >::iterator i=evcb.begin(); i != evcb.end(); i++)
fb3345ad 109 _c->add_callback(e,*i);
af84dfb5
GE
110 }
111
112 // replace the connection
113 c=_c;
114
115 read_hello();
116}
117
487afb79
GE
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*/
45a2ebc9 124std::string command_client::read_packet(const long long &usec_timeout)
8104c8f7 125{
8104c8f7 126 string resultpacket;
45a2ebc9
GE
127 bool got_packet=false;
128 long long my_timeout=usec_timeout;
fb3345ad
GE
129 while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0 && !c->is_closed())
130 c->fill_buffer(my_timeout,&my_timeout);
8104c8f7 131
45a2ebc9
GE
132 if (!got_packet)
133 throw t2n_transfer_error("timeout exceeded");
134
135 return resultpacket;
136}
137
487afb79
GE
138/** @brief read and check the hello message at the beginning of a connection
139
140 @note throws exceptions if something went wrong
141*/
45a2ebc9
GE
142void command_client::read_hello()
143{
b2ba0928
GE
144 string resultpacket;
145 bool got_packet=false;
146 long long my_timeout=hello_timeout_usec;
fb3345ad 147 while(!(got_packet=c->get_packet(resultpacket)) && my_timeout > 0 && !c->is_closed())
b2ba0928 148 {
fb3345ad 149 c->fill_buffer(my_timeout,&my_timeout);
b2ba0928 150
fb3345ad 151 c->peek_packet(resultpacket);
b2ba0928
GE
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
487afb79
GE
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)
b2ba0928
GE
170{
171 istringstream hello(hellostr);
8104c8f7 172
b2ba0928
GE
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;
8104c8f7
GE
206
207 int prot_version;
b2ba0928
GE
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;
8104c8f7 223
b2ba0928
GE
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;
8104c8f7 232
b2ba0928
GE
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;
8104c8f7 240
b2ba0928 241 return true;
8104c8f7
GE
242}
243
487afb79
GE
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*/
7087e187
GE
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
b5922184
GE
257 if (is_connection_closed())
258 throw t2n_transfer_error("connection to server is closed");
259
45a2ebc9
GE
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 }
7087e187 270
d535333f 271 std::ostream* ostr;
fb3345ad 272 if ((ostr=c->get_logstream(fulldebug))!=NULL)
d535333f
GE
273 {
274 (*ostr) << "sending command, decoded data: " << std::endl;
275 boost::archive::xml_oarchive xo(*ostr);
276 xo << BOOST_SERIALIZATION_NVP(cc);
696c95c2 277 }
d535333f 278
fb3345ad 279 c->write(ofs.str());
7087e187 280
45a2ebc9 281 istringstream ifs(read_packet(command_timeout_usec));
7087e187
GE
282 boost::archive::binary_iarchive ia(ifs);
283
45a2ebc9
GE
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 }
d535333f 294
fb3345ad 295 if ((ostr=c->get_logstream(fulldebug))!=NULL)
d535333f
GE
296 {
297 (*ostr) << "received result, decoded data: " << std::endl;
298 boost::archive::xml_oarchive xo(*ostr);
299 xo << BOOST_SERIALIZATION_NVP(res);
300 }
7087e187
GE
301}
302
303}