libt2n: (gerd) socket reconnect added (incl. unit tests)
[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     read_hello();
52 }
53
54 /** @brief replace the connection currently in use with a new one
55
56     @param _c reference of the new connection
57
58     @note the old connection must still be valid when this method is called,
59           it can safely be deleted after this method returned
60
61     @note all callbacks registered on the old connection will be copied over
62           to the new one
63 */
64 void command_client::replace_connection(client_connection& _c)
65 {
66     // copy all callbacks registered on the old connection
67     for(callback_event_type e=static_cast<callback_event_type>(0);
68         e < __events_end;
69         e=static_cast<callback_event_type>(static_cast<int>(e)+1))
70     {
71         list<boost::function<void ()> > evcb=c.get_callback_list(e);
72
73         for (list<boost::function<void ()> >::iterator i=evcb.begin(); i != evcb.end(); i++)
74             _c.add_callback(e,*i);
75     }
76
77     // replace the connection
78     c=_c;
79
80     read_hello();
81 }
82
83 std::string command_client::read_packet(const long long &usec_timeout)
84 {
85     string resultpacket;
86     bool got_packet=false;
87     long long my_timeout=usec_timeout;
88     while(!(got_packet=c.get_packet(resultpacket)) && my_timeout > 0  && !c.is_closed())
89         c.fill_buffer(my_timeout,&my_timeout);
90
91     if (!got_packet)
92         throw t2n_transfer_error("timeout exceeded");
93
94     return resultpacket;
95 }
96
97 void command_client::read_hello()
98 {
99     string resultpacket;
100     bool got_packet=false;
101     long long my_timeout=hello_timeout_usec;
102     while(!(got_packet=c.get_packet(resultpacket)) && my_timeout > 0  && !c.is_closed())
103     {
104         c.fill_buffer(my_timeout,&my_timeout);
105
106         c.peek_packet(resultpacket);
107         check_hello(resultpacket);           // will throw before timeout if wrong data received
108     }
109
110     if (!got_packet)
111         throw t2n_transfer_error("timeout exceeded");
112
113     if (!check_hello(resultpacket))
114         throw t2n_version_mismatch("illegal hello received (incomplete): "+resultpacket);
115 }
116
117 bool command_client::check_hello(const string& hellostr)
118 {
119     istringstream hello(hellostr);
120
121     char chk;
122
123     if (hello.read(&chk,1))
124     {
125         if (chk != 'T')
126             throw t2n_version_mismatch("illegal hello received (T2N)");
127     }
128     else
129         return false;
130
131     if (hello.read(&chk,1))
132     {
133         if (chk != '2')
134             throw t2n_version_mismatch("illegal hello received (T2N)");
135     }
136     else
137         return false;
138
139     if (hello.read(&chk,1))
140     {
141         if (chk != 'N')
142             throw t2n_version_mismatch("illegal hello received (T2N)");
143     }
144     else
145         return false;
146
147     if (hello.read(&chk,1))
148     {
149         if (chk != 'v')
150             throw t2n_version_mismatch("illegal hello received (T2N)");
151     }
152     else
153         return false;
154
155     int prot_version;
156     if (hello >> prot_version)
157     {
158         if (prot_version != PROTOCOL_VERSION)
159             throw t2n_version_mismatch("not compatible with the server protocol version");
160     }
161     else
162         return false;
163
164     if (hello.read(&chk,1))
165     {
166         if (chk != ';')
167             throw t2n_version_mismatch("illegal hello received (1. ;)");
168     }
169     else
170         return false;
171
172     unsigned int hbo;
173     if (hello.read((char*)&hbo,sizeof(hbo)))
174     {
175         if (hbo != 1)
176             throw t2n_version_mismatch("host byte order not matching");
177     }
178     else
179         return false;
180
181     if (hello.read(&chk,1))
182     {
183         if (chk != ';')
184             throw t2n_version_mismatch("illegal hello received (2. ;)");
185     }
186     else
187         return false;
188
189     return true;
190 }
191
192 void command_client::send_command(command* cmd, result_container &res)
193 {
194     ostringstream ofs;
195     command_container cc(cmd);
196     boost::archive::binary_oarchive oa(ofs);
197
198     try
199     {
200         oa << cc;
201     }
202     catch(boost::archive::archive_exception &e)
203     {
204         ostringstream msg;
205         msg << "archive_exception while serializing on client-side, code " << e.code << " (" << e.what() << ")";
206         throw t2n_serialization_error(msg.str());
207     }
208     catch(...)
209         { throw; }
210
211     std::ostream* ostr;
212     if ((ostr=c.get_logstream(fulldebug))!=NULL)
213     {
214         (*ostr) << "sending command, decoded data: " << std::endl;
215         boost::archive::xml_oarchive xo(*ostr);
216         xo << BOOST_SERIALIZATION_NVP(cc);
217     }
218
219     c.write(ofs.str());
220
221     istringstream ifs(read_packet(command_timeout_usec));
222     boost::archive::binary_iarchive ia(ifs);
223
224     try
225     {
226         ia >> res;
227     }
228     catch(boost::archive::archive_exception &e)
229     {
230         ostringstream msg;
231         msg << "archive_exception while deserializing on client-side, code " << e.code << " (" << e.what() << ")";
232         throw t2n_serialization_error(msg.str());
233     }
234     catch(...)
235         { throw; }
236
237     if ((ostr=c.get_logstream(fulldebug))!=NULL)
238     {
239         (*ostr) << "received result, decoded data: " << std::endl;
240         boost::archive::xml_oarchive xo(*ostr);
241         xo << BOOST_SERIALIZATION_NVP(res);
242     }
243 }
244
245 }