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