Initial cmake conversion of the libt2n project except the example projects. The unit...
[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 #include <config.h>
37
38 using namespace std;
39
40 namespace 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  */
49 command_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  */
79 command_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 */
97 void 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 */
122 std::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 */
140 void 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 */
167 bool 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 */
249 void 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 }