1 // ----------------------------------------------------------------------------
2 //
3 // flxmlrpc Copyright (c) 2015 by W1HKJ, Dave Freese <iam_w1hkj@w1hkj.com>
4 //
5 // XmlRpc++ Copyright (c) 2002-2008 by Chris Morley
6 //
7 // This file is part of fldigi
8 //
9 // flxmlrpc is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU Lesser General Public License as published by
11 // the Free Software Foundation; either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 // ----------------------------------------------------------------------------
17 
18 #include <config.h>
19 
20 #include "XmlRpcDispatch.h"
21 #include "XmlRpcSource.h"
22 #include "XmlRpcUtil.h"
23 
24 #include <errno.h>
25 #include <math.h>
26 
27 #if defined(__FreeBSD__) || defined(__OpenBSD__)
28 #	ifdef USE_FTIME
29 #		include <sys/timeb.h>
30 #	endif
31 #else
32 #	include <sys/timeb.h>
33 #endif // __FreeBSD__
34 
35 #if defined(_WINDOWS)
36 # include <winsock2.h>
37 
38 # define USE_FTIME
39 # if defined(_MSC_VER)
40 #  define timeb _timeb
41 #  define ftime _ftime
42 # endif
43 #else
44 # include <sys/time.h>
45 #endif  // _WINDOWS
46 
47 
48 using namespace XmlRpc;
49 
50 
XmlRpcDispatch()51 XmlRpcDispatch::XmlRpcDispatch()
52 {
53   _endTime = -1.0;
54   _doClear = false;
55   _inWork = false;
56 }
57 
58 
~XmlRpcDispatch()59 XmlRpcDispatch::~XmlRpcDispatch()
60 {
61 }
62 
63 // Monitor this source for the specified events and call its event handler
64 // when the event occurs
65 void
addSource(XmlRpcSource * source,unsigned mask)66 XmlRpcDispatch::addSource(XmlRpcSource* source, unsigned mask)
67 {
68   _sources.push_back(MonitoredSource(source, mask));
69 }
70 
71 // Stop monitoring this source. Does not close the source.
72 void
removeSource(XmlRpcSource * source)73 XmlRpcDispatch::removeSource(XmlRpcSource* source)
74 {
75   for (SourceList::iterator it=_sources.begin(); it!=_sources.end(); ++it)
76     if (it->getSource() == source)
77     {
78       _sources.erase(it);
79       break;
80     }
81 }
82 
83 
84 // Modify the types of events to watch for on this source
85 void
setSourceEvents(XmlRpcSource * source,unsigned eventMask)86 XmlRpcDispatch::setSourceEvents(XmlRpcSource* source, unsigned eventMask)
87 {
88   for (SourceList::iterator it=_sources.begin(); it!=_sources.end(); ++it)
89     if (it->getSource() == source)
90     {
91       it->getMask() = eventMask;
92       break;
93     }
94 }
95 
96 
97 
98 // Watch current set of sources and process events
99 void
work(double timeoutSeconds)100 XmlRpcDispatch::work(double timeoutSeconds)
101 {
102   // Compute end time
103   double timeNow = getTime();
104   _endTime = (timeoutSeconds < 0.0) ? -1.0 : (timeNow + timeoutSeconds);
105   _doClear = false;
106   _inWork = true;
107 
108   // Only work while there is something to monitor
109   while (_sources.size() > 0) {
110 
111     // Wait for and dispatch events
112     if ( ! waitForAndProcessEvents(timeoutSeconds))
113     {
114       _inWork = false;
115       return;
116     }
117 
118 
119     // Check whether to clear all sources
120     if (_doClear)
121     {
122       SourceList sourcesToClose;
123       _sources.swap(sourcesToClose);
124       for (SourceList::iterator it=sourcesToClose.begin(); it!=sourcesToClose.end(); ++it)
125       {
126         XmlRpcSource *src = it->getSource();
127         src->close();
128       }
129 
130       _doClear = false;
131     }
132 
133     // Check whether end time has passed or exit has been called
134     if (_endTime == 0.0)        // Exit
135     {
136       break;
137     }
138     else if (_endTime > 0.0)    // Check for timeout
139     {
140       double t = getTime();
141       if (t > _endTime)
142         break;
143 
144       // Decrement timeout by elapsed time
145       timeoutSeconds -= (t - timeNow);
146       if (timeoutSeconds < 0.0)
147         timeoutSeconds = 0.0;    // Shouldn't happen but its fp math...
148       timeNow = t;
149     }
150   }
151 
152   _inWork = false;
153 }
154 
155 
156 
157 // Exit from work routine. Presumably this will be called from
158 // one of the source event handlers.
159 void
exit()160 XmlRpcDispatch::exit()
161 {
162   _endTime = 0.0;   // Return from work asap
163 }
164 
165 
166 // Clear all sources from the monitored sources list
167 void
clear()168 XmlRpcDispatch::clear()
169 {
170   if (_inWork)
171   {
172     _doClear = true;  // Finish reporting current events before clearing
173   }
174   else
175   {
176     SourceList sourcesToClose;
177     _sources.swap(sourcesToClose);
178     for (SourceList::iterator it=sourcesToClose.begin(); it!=sourcesToClose.end(); ++it)
179       it->getSource()->close();
180   }
181 }
182 
183 
184 // Time utility- return time in seconds
185 double
getTime()186 XmlRpcDispatch::getTime()
187 {
188 #ifdef USE_FTIME
189   struct timeb	tbuff;
190 
191   ftime(&tbuff);
192   return ((double) tbuff.time + ((double)tbuff.millitm / 1000.0) +
193 	  ((double) tbuff.timezone * 60));
194 #else
195   struct timeval	tv;
196   struct timezone	tz;
197 
198   gettimeofday(&tv, &tz);
199   return (tv.tv_sec + tv.tv_usec / 1000000.0);
200 #endif /* USE_FTIME */
201 }
202 
203 
204 // Wait for I/O on any source, timeout, or interrupt signal.
205 bool
waitForAndProcessEvents(double timeoutSeconds)206 XmlRpcDispatch::waitForAndProcessEvents(double timeoutSeconds)
207 {
208   // Construct the sets of descriptors we are interested in
209   fd_set inFd, outFd, excFd;
210   FD_ZERO(&inFd);
211   FD_ZERO(&outFd);
212   FD_ZERO(&excFd);
213 
214   XmlRpcSocket::Socket maxFd = 0;
215   for (SourceList::iterator it=_sources.begin(); it!=_sources.end(); ++it)
216   {
217     XmlRpcSocket::Socket fd = it->getSource()->getfd();
218     if (it->getMask() & ReadableEvent) FD_SET(fd, &inFd);
219     if (it->getMask() & WritableEvent) FD_SET(fd, &outFd);
220     if (it->getMask() & Exception)     FD_SET(fd, &excFd);
221     if (it->getMask() && fd > maxFd)   maxFd = fd;
222   }
223 
224   // Check for events
225   int nEvents;
226   if (_endTime < 0.0)
227   {
228     nEvents = select(int(maxFd+1), &inFd, &outFd, &excFd, NULL);
229   }
230   else
231   {
232     struct timeval tv;
233     tv.tv_sec = (int)floor(timeoutSeconds);
234     tv.tv_usec = ((int)floor(1000000.0 * (timeoutSeconds-floor(timeoutSeconds)))) % 1000000;
235     nEvents = select(int(maxFd+1), &inFd, &outFd, &excFd, &tv);
236   }
237 
238   if (nEvents < 0 && errno != EINTR)
239   {
240     XmlRpcUtil::error("Error in XmlRpcDispatch::work: error in select (%d).", nEvents);
241     return false;
242   }
243 
244   // Process events. Copy source list to avoid invalidating iterator by removing sources.
245   SourceList s(_sources);
246   for (SourceList::iterator it=s.begin(); it != s.end(); ++it)
247   {
248     XmlRpcSource* src = it->getSource();
249     XmlRpcSocket::Socket fd = src->getfd();
250 
251     if (fd <= maxFd)
252     {
253       // handleEvent is called once per event type signalled
254       unsigned newMask = 0;
255       int nset = 0;
256       if (FD_ISSET(fd, &inFd))
257       {
258         newMask |= src->handleEvent(ReadableEvent);
259         ++nset;
260       }
261       if (FD_ISSET(fd, &outFd))
262       {
263         newMask |= src->handleEvent(WritableEvent);
264         ++nset;
265       }
266       if (FD_ISSET(fd, &excFd))
267       {
268         newMask |= src->handleEvent(Exception);
269         ++nset;
270       }
271 
272       // Some event occurred
273       if (nset)
274       {
275         // This bit is not terribly efficient if many connections are active...
276         if (newMask)
277         {
278           setSourceEvents(src, newMask);
279         }
280         else       // Stop monitoring this one
281         {
282           removeSource(src);
283 
284           if ( ! src->getKeepOpen())
285             src->close();
286         }
287       }
288     }
289   }
290 
291   return true;
292 }
293