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