MythTV master
mythsocketmanager.cpp
Go to the documentation of this file.
1// Qt
2#include <QMutex>
3#include <QReadWriteLock>
4#include <QReadLocker>
5#include <QWriteLocker>
6#include <QMutexLocker>
7#include <QWaitCondition>
8#include <QEvent>
9#include <QCoreApplication>
10#include <QNetworkProxy>
11#include <QRunnable>
12
13// MythTV
14#include "libmythbase/mthread.h"
15#include "libmythbase/mythconfig.h"
17#include "libmythbase/mythversion.h"
20
21#include "mythsocketmanager.h"
22#include "sockethandler.h"
24
25#define LOC QString("MythSocketManager: ")
26
27static constexpr std::chrono::milliseconds PRT_TIMEOUT { 10ms };
28
29class ProcessRequestRunnable : public QRunnable
30{
31 public:
33 m_parent(parent), m_sock(sock)
34 {
35 m_sock->IncrRef();
36 }
37
38 void run(void) override // QRunnable
39 {
41 m_sock->DecrRef();
42 m_sock = nullptr;
43 }
44
47};
48
50 m_threadPool("MythSocketManager")
51{
52}
53
55{
57
58 QWriteLocker wlock(&m_handlerLock);
59
60 QMap<QString, SocketRequestHandler*>::iterator i;
61 for (i = m_handlerMap.begin(); i != m_handlerMap.end(); ++i)
62 delete *i;
63
64 m_handlerMap.clear();
65
66 QMutexLocker locker(&m_socketListLock);
67 for (auto iter = m_socketList.begin();
68 iter != m_socketList.end();
69 iter = m_socketList.erase(iter))
70 {
71 (*iter)->DecrRef();
72 }
73}
74
76{
77 if (m_server != nullptr)
78 {
79 m_server->close();
80 delete m_server;
81 m_server = nullptr;
82 }
83
84 m_server = new MythServer(this);
85 m_server->setProxy(QNetworkProxy::NoProxy);
86 if (!m_server->listen(port))
87 {
88 LOG(VB_GENERAL, LOG_ERR, QString("Failed to bind port %1.").arg(port));
89 return false;
90 }
91
94 return true;
95}
96
98{
99 QMutexLocker locker(&m_socketListLock);
100 auto *ms = new MythSocket(sd, this);
101 if (ms->IsConnected())
102 m_socketList.insert(ms);
103 else
104 delete ms;
105}
106
108{
109 QWriteLocker wlock(&m_handlerLock);
110
111 QString name = handler->GetHandlerName();
112 if (m_handlerMap.contains(name))
113 {
114 LOG(VB_GENERAL, LOG_WARNING, LOC + name +
115 " has already been registered.");
116 delete handler;
117 }
118 else
119 {
120 LOG(VB_GENERAL, LOG_INFO, LOC +
121 "Registering socket command handler " + name);
122 handler->SetParent(this);
123 m_handlerMap.insert(name, handler);
124 }
125}
126
128{
129 QWriteLocker wlock(&m_socketLock);
130 if (m_socketMap.contains(sock->GetSocket()))
131 return;
132
133 sock->IncrRef();
134 m_socketMap.insert(sock->GetSocket(), sock);
135}
136
138{
139 QReadLocker rlock(&m_socketLock);
140 if (!m_socketMap.contains(sock))
141 return nullptr;
142
143 SocketHandler *handler = m_socketMap.value(sock);
144 handler->IncrRef();
145 return handler;
146}
147
149{
151 new ProcessRequestRunnable(*this, sock),
152 "ServiceRequest", PRT_TIMEOUT);
153}
154
156{
157 // TODO We should delete the MythSocket's at some point
158 // prior to MythSocketManager shutdown...
159
160 {
161 QReadLocker rlock(&m_handlerLock);
162
163 QMap<QString, SocketRequestHandler*>::const_iterator i;
164 for (i = m_handlerMap.constBegin(); i != m_handlerMap.constEnd(); ++i)
165 (*i)->connectionClosed(sock);
166 }
167
168 {
169 QWriteLocker wlock(&m_socketLock);
170 if (m_socketMap.contains(sock))
171 {
172 SocketHandler *handler = m_socketMap.take(sock);
173 handler->DecrRef();
174 }
175 }
176}
177
179{
180 // used as context manager since MythSocket cannot be used directly
181 // with QMutexLocker
182
183 if (sock->IsDataAvailable())
184 {
185 ProcessRequestWork(sock);
186 }
187}
188
190{
191 QStringList listline;
192 if (!sock->ReadStringList(listline))
193 return;
194
195 QString line = listline[0].simplified();
196 QStringList tokens = line.split(' ', Qt::SkipEmptyParts);
197 QString command = tokens[0];
198
199 bool handled = false;
200
201 // DONE can be called at any time
202 if (command == "DONE")
203 {
204 HandleDone(sock);
205 return;
206 }
207
208 if (!sock->IsValidated())
209 {
210 // all sockets must be validated against the local protocol version
211 // before any subsequent commands can be run
212 if (command == "MYTH_PROTO_VERSION")
213 {
214 HandleVersion(sock, tokens);
215 }
216 else
217 {
218 LOG(VB_SOCKET, LOG_ERR, LOC +
219 "Use of socket attempted before protocol validation.");
220 listline.clear();
221 listline << "ERROR" << "socket has not been validated";
222 sock->WriteStringList(listline);
223 }
224 return;
225 }
226
227 if (!sock->IsAnnounced())
228 {
229 // all sockets must be announced before any subsequent commands can
230 // be run
231 if (command == "ANN")
232 {
233 QReadLocker rlock(&m_handlerLock);
234
235 QMap<QString, SocketRequestHandler*>::const_iterator i
236 = m_handlerMap.constBegin();
237 while (!handled && (i != m_handlerMap.constEnd()))
238 {
239 LOG(VB_SOCKET, LOG_DEBUG, LOC +
240 QString("Attempting to handle announce with: %1")
241 .arg((*i)->GetHandlerName()));
242 handled = (*i)->HandleAnnounce(sock, tokens, listline);
243 ++i;
244 }
245
246 if (handled)
247 {
248 --i;
249 LOG(VB_SOCKET, LOG_DEBUG, LOC +
250 QString("Socket announce handled by: %1")
251 .arg((*i)->GetHandlerName()));
252 for (i = m_handlerMap.constBegin();
253 i != m_handlerMap.constEnd(); ++i)
254 (*i)->connectionAnnounced(sock, tokens, listline);
255 }
256 else
257 {
258 LOG(VB_SOCKET, LOG_ERR, LOC + "Socket announce unhandled.");
259 listline.clear();
260 listline << "ERROR" << "unhandled announce";
261 sock->WriteStringList(listline);
262 }
263
264 return;
265 }
266 LOG(VB_SOCKET, LOG_ERR, LOC +
267 "Use of socket attempted before announcement.");
268 listline.clear();
269 listline << "ERROR" << "socket has not been announced";
270 sock->WriteStringList(listline);
271 return;
272 }
273
274 if (command == "ANN")
275 {
276 LOG(VB_SOCKET, LOG_ERR, LOC + "ANN sent out of sequence.");
277 listline.clear();
278 listline << "ERROR" << "socket has already been announced";
279 sock->WriteStringList(listline);
280 return;
281 }
282
283 {
284 // socket is validated and announced, handle everything else
285 QReadLocker rlock(&m_handlerLock);
286 QReadLocker slock(&m_socketLock);
287
288 if (!m_socketMap.contains(sock))
289 {
290 LOG(VB_SOCKET, LOG_ERR, LOC + "No handler found for socket.");
291 listline.clear();
292 listline << "ERROR" << "socket handler cannot be found";
293 sock->WriteStringList(listline);
294 return;
295 }
296
297 SocketHandler *handler = GetConnectionBySocket(sock);
298 ReferenceLocker hlock(handler);
299
300 QMap<QString, SocketRequestHandler*>::const_iterator i
301 = m_handlerMap.constBegin();
302 while (!handled && (i != m_handlerMap.constEnd()))
303 {
304 handled = (*i)->HandleQuery(handler, tokens, listline);
305 ++i;
306 }
307
308 if (handled)
309 LOG(VB_SOCKET, LOG_DEBUG, LOC + QString("Query handled by: %1")
310 .arg((*--i)->GetHandlerName()));
311 }
312
313 if (!handled)
314 {
315 if (command == "BACKEND_MESSAGE")
316 {
317 // never respond to these... ever, even if they are not otherwise
318 // handled by something in m_handlerMap
319 return;
320 }
321
322 listline.clear();
323 listline << "ERROR" << "unknown command";
324 sock->WriteStringList(listline);
325 }
326}
327
329 const QStringList &slist)
330{
331 QStringList retlist;
332 const QString& version = slist[1];
333 if (version != MYTH_PROTO_VERSION)
334 {
335 LOG(VB_GENERAL, LOG_ERR, LOC +
336 "Client speaks protocol version " + version +
337 " but we speak " + MYTH_PROTO_VERSION + '!');
338 retlist << "REJECT" << MYTH_PROTO_VERSION;
339 socket->WriteStringList(retlist);
340 HandleDone(socket);
341 return;
342 }
343
344 if (slist.size() < 3)
345 {
346 LOG(VB_GENERAL, LOG_ERR, LOC + "Client did not pass protocol "
347 "token. Refusing connection!");
348 retlist << "REJECT" << MYTH_PROTO_VERSION;
349 socket->WriteStringList(retlist);
350 HandleDone(socket);
351 return;
352 }
353
354 const QString& token = slist[2];
355 if (token != QString::fromUtf8(MYTH_PROTO_TOKEN))
356 {
357 LOG(VB_GENERAL, LOG_ERR, LOC +
358 QString("Client sent incorrect protocol token \"%1\" for "
359 "protocol version. Refusing connection!").arg(token));
360 retlist << "REJECT" << MYTH_PROTO_VERSION;
361 socket->WriteStringList(retlist);
362 HandleDone(socket);
363 return;
364 }
365
366 LOG(VB_SOCKET, LOG_DEBUG, LOC + "Client validated");
367 retlist << "ACCEPT" << MYTH_PROTO_VERSION;
368 socket->WriteStringList(retlist);
369 socket->m_isValidated = true;
370}
371
373{
374 sock->DisconnectFromHost();
375}
376
void startReserved(QRunnable *runnable, const QString &debugName, std::chrono::milliseconds waitForAvailMS=0ms)
void Stop(void)
void newConnection(qintptr socket)
QMap< QString, SocketRequestHandler * > m_handlerMap
bool Listen(int port)
static void HandleVersion(MythSocket *socket, const QStringList &slist)
SocketHandler * GetConnectionBySocket(MythSocket *socket)
QReadWriteLock m_handlerLock
void readyRead(MythSocket *socket) override
QReadWriteLock m_socketLock
MythServer * m_server
void AddSocketHandler(SocketHandler *socket)
~MythSocketManager() override
void ProcessRequestWork(MythSocket *socket)
void ProcessRequest(MythSocket *socket)
void RegisterHandler(SocketRequestHandler *handler)
void newConnection(qintptr sd)
static void HandleDone(MythSocket *socket)
void connectionClosed(MythSocket *socket) override
QMap< MythSocket *, SocketHandler * > m_socketMap
QSet< MythSocket * > m_socketList
MThreadPool m_threadPool
Class for communcating between myth backends and frontends.
Definition: mythsocket.h:26
bool IsValidated(void) const
Definition: mythsocket.h:41
bool IsAnnounced(void) const
Definition: mythsocket.h:46
bool m_isValidated
Definition: mythsocket.h:119
bool ReadStringList(QStringList &list, std::chrono::milliseconds timeoutMS=kShortTimeout)
Definition: mythsocket.cpp:313
bool IsDataAvailable(void)
Definition: mythsocket.cpp:557
void DisconnectFromHost(void)
Definition: mythsocket.cpp:498
bool WriteStringList(const QStringList &list)
Definition: mythsocket.cpp:301
void run(void) override
ProcessRequestRunnable(MythSocketManager &parent, MythSocket *sock)
MythSocketManager & m_parent
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
This decrements the reference on destruction.
bool listen(QList< QHostAddress > addrs, quint16 port, bool requireall=true, PoolServerType type=kTCPServer)
Definition: serverpool.cpp:395
void close(void)
Definition: serverpool.cpp:374
void setProxy(const QNetworkProxy &proxy)
Definition: serverpool.h:98
MythSocket * GetSocket(void) const
Definition: sockethandler.h:29
virtual void SetParent(MythSocketManager *parent)
virtual QString GetHandlerName(void)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
static constexpr std::chrono::milliseconds PRT_TIMEOUT
string version
Definition: giantbomb.py:185