Switch to using QAtomicInc instead of "volatile" flags in more places.
[MUtilities.git] / src / IPCChannel.cpp
bloba129c0522eb15c00881d0e313c4367463757ad71
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2017 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 // http://www.gnu.org/licenses/lgpl-2.1.txt
20 //////////////////////////////////////////////////////////////////////////////////
22 //MUtils
23 #include <MUtils/IPCChannel.h>
24 #include <MUtils/Exception.h>
26 //Internal
27 #include "3rd_party/adler32/include/adler32.h"
29 //Qt includes
30 #include <QRegExp>
31 #include <QSharedMemory>
32 #include <QSystemSemaphore>
33 #include <QMutex>
34 #include <QWriteLocker>
35 #include <QCryptographicHash>
36 #include <QStringList>
37 //CRT
38 #include <cassert>
40 ///////////////////////////////////////////////////////////////////////////////
41 // UTILITIES
42 ///////////////////////////////////////////////////////////////////////////////
44 namespace MUtils
46 namespace Internal
48 static const quint32 ADLER_SEED = 0x5D90C356;
50 template<class T>
51 static inline void UPDATE_CHECKSUM(T &data)
53 data.checksum = Internal::adler32(ADLER_SEED, &data.payload, sizeof(data.payload));
56 template<class T>
57 static inline bool VERIFY_CHECKSUM(const T &data)
59 return (data.checksum == Internal::adler32(ADLER_SEED, &data.payload, sizeof(data.payload)));
64 ///////////////////////////////////////////////////////////////////////////////
65 // TYPES
66 ///////////////////////////////////////////////////////////////////////////////
68 namespace MUtils
70 namespace Internal
72 static const size_t HDR_LEN = 40;
73 static const size_t IPC_SLOTS = 128;
75 typedef struct
77 quint64 counter;
78 quint32 pos_wr;
79 quint32 pos_rd;
81 ipc_status_data_t;
83 typedef struct
85 ipc_status_data_t payload;
86 quint32 checksum;
88 ipc_status_t;
90 typedef struct
92 char values[MUtils::IPCChannel::MAX_PARAM_CNT][MUtils::IPCChannel::MAX_PARAM_LEN];
93 quint32 count;
95 ipc_msg_data_params_t;
97 typedef struct
99 quint32 command_id;
100 quint32 flags;
101 ipc_msg_data_params_t params;
102 quint64 timestamp;
104 ipc_msg_data_t;
106 typedef struct
108 ipc_msg_data_t payload;
109 quint32 checksum;
111 ipc_msg_t;
113 typedef struct
115 char header[HDR_LEN];
116 ipc_status_t status;
117 ipc_msg_t data[IPC_SLOTS];
119 ipc_t;
123 ///////////////////////////////////////////////////////////////////////////////
124 // UTILITIES
125 ///////////////////////////////////////////////////////////////////////////////
127 static QScopedPointer<QRegExp> g_escape_regexp;
128 static QMutex g_escape_lock;
130 #define ESCAPE(STR) (QString((STR)).replace(*g_escape_regexp, QLatin1String("_")).toLower())
132 static QString MAKE_ID(const QString &applicationId, const unsigned int &appVersionNo, const QString &channelId, const QString &itemId)
134 QMutexLocker locker(&g_escape_lock);
136 if(g_escape_regexp.isNull())
138 g_escape_regexp.reset(new QRegExp(QLatin1String("[^A-Za-z0-9_\\-]")));
141 return QString("com.muldersoft.mutilities.ipc.%1.r%2.%3.%4").arg(ESCAPE(applicationId), QString::number(appVersionNo, 16).toUpper(), ESCAPE(channelId), ESCAPE(itemId));
144 ///////////////////////////////////////////////////////////////////////////////
145 // PRIVATE DATA
146 ///////////////////////////////////////////////////////////////////////////////
148 namespace MUtils
150 class IPCChannel_Private
152 friend class IPCChannel;
154 protected:
155 QAtomicInt initialized;
156 QScopedPointer<QSharedMemory> sharedmem;
157 QScopedPointer<QSystemSemaphore> semaphore_rd;
158 QScopedPointer<QSystemSemaphore> semaphore_wr;
159 QReadWriteLock lock;
163 ///////////////////////////////////////////////////////////////////////////////
164 // CONSTRUCTOR & DESTRUCTOR
165 ///////////////////////////////////////////////////////////////////////////////
167 MUtils::IPCChannel::IPCChannel(const QString &applicationId, const quint32 &appVersionNo, const QString &channelId)
169 p(new IPCChannel_Private()),
170 m_applicationId(applicationId),
171 m_channelId(channelId),
172 m_appVersionNo(appVersionNo),
173 m_headerStr(QCryptographicHash::hash(MAKE_ID(applicationId, appVersionNo, channelId, "header").toLatin1(), QCryptographicHash::Sha1).toHex())
175 if(m_headerStr.length() != Internal::HDR_LEN)
177 MUTILS_THROW("Invalid header length has been detected!");
181 MUtils::IPCChannel::~IPCChannel(void)
183 if(MUTILS_BOOLIFY(p->initialized))
185 if(p->sharedmem->isAttached())
187 p->sharedmem->detach();
191 delete p;
194 ///////////////////////////////////////////////////////////////////////////////
195 // INITIALIZATION
196 ///////////////////////////////////////////////////////////////////////////////
198 int MUtils::IPCChannel::initialize(void)
200 QWriteLocker writeLock(&p->lock);
202 if(MUTILS_BOOLIFY(p->initialized))
204 return RET_ALREADY_INITIALIZED;
207 p->sharedmem. reset(new QSharedMemory (MAKE_ID(m_applicationId, m_appVersionNo, m_channelId, "sharedmem"), 0));
208 p->semaphore_rd.reset(new QSystemSemaphore(MAKE_ID(m_applicationId, m_appVersionNo, m_channelId, "semaph_rd"), 0));
209 p->semaphore_wr.reset(new QSystemSemaphore(MAKE_ID(m_applicationId, m_appVersionNo, m_channelId, "semaph_wr"), 0));
211 if(p->semaphore_rd->error() != QSystemSemaphore::NoError)
213 const QString errorMessage = p->semaphore_rd->errorString();
214 qWarning("Failed to create system smaphore: %s", MUTILS_UTF8(errorMessage));
215 return RET_FAILURE;
218 if(p->semaphore_wr->error() != QSystemSemaphore::NoError)
220 const QString errorMessage = p->semaphore_wr->errorString();
221 qWarning("Failed to create system smaphore: %s", MUTILS_UTF8(errorMessage));
222 return RET_FAILURE;
225 if(!p->sharedmem->create(sizeof(Internal::ipc_t)))
227 if(p->sharedmem->error() == QSharedMemory::AlreadyExists)
229 if(!p->sharedmem->attach())
231 const QString errorMessage = p->sharedmem->errorString();
232 qWarning("Failed to attach to shared memory: %s", MUTILS_UTF8(errorMessage));
233 return RET_FAILURE;
235 if(p->sharedmem->error() != QSharedMemory::NoError)
237 const QString errorMessage = p->sharedmem->errorString();
238 qWarning("Failed to attach to shared memory: %s", MUTILS_UTF8(errorMessage));
239 return RET_FAILURE;
241 if(p->sharedmem->size() < sizeof(Internal::ipc_t))
243 qWarning("Failed to attach to shared memory: Size verification has failed!");
244 return RET_FAILURE;
246 if(Internal::ipc_t *const ptr = reinterpret_cast<Internal::ipc_t*>(p->sharedmem->data()))
248 if(memcmp(&ptr->header[0], m_headerStr.constData(), Internal::HDR_LEN) != 0)
250 qWarning("Failed to attach to shared memory: Header verification has failed!");
251 return RET_FAILURE;
254 else
256 const QString errorMessage = p->sharedmem->errorString();
257 qWarning("Failed to access shared memory: %s", MUTILS_UTF8(errorMessage));
258 return RET_FAILURE;
260 p->initialized.ref();
261 return RET_SUCCESS_SLAVE;
263 else
265 const QString errorMessage = p->sharedmem->errorString();
266 qWarning("Failed to create shared memory: %s", MUTILS_UTF8(errorMessage));
267 return RET_FAILURE;
271 if(p->sharedmem->error() != QSharedMemory::NoError)
273 const QString errorMessage = p->sharedmem->errorString();
274 qWarning("Failed to create shared memory: %s", MUTILS_UTF8(errorMessage));
275 return RET_FAILURE;
278 if(Internal::ipc_t *const ptr = reinterpret_cast<Internal::ipc_t*>(p->sharedmem->data()))
280 memset(ptr, 0, sizeof(Internal::ipc_t));
281 memcpy(&ptr->header[0], m_headerStr.constData(), Internal::HDR_LEN);
282 UPDATE_CHECKSUM(ptr->status);
284 else
286 const QString errorMessage = p->sharedmem->errorString();
287 qWarning("Failed to access shared memory: %s", MUTILS_UTF8(errorMessage));
288 return RET_FAILURE;
291 if(!p->semaphore_wr->release(Internal::IPC_SLOTS))
293 const QString errorMessage = p->semaphore_wr->errorString();
294 qWarning("Failed to release system semaphore: %s", MUTILS_UTF8(errorMessage));
295 return RET_FAILURE;
298 //qDebug("IPC KEY #1: %s", MUTILS_UTF8(p->sharedmem->key()));
299 //qDebug("IPC KEY #2: %s", MUTILS_UTF8(p->semaphore_rd->key()));
300 //qDebug("IPC KEY #3: %s", MUTILS_UTF8(p->semaphore_wr->key()));
302 p->initialized.ref();
303 return RET_SUCCESS_MASTER;
306 ///////////////////////////////////////////////////////////////////////////////
307 // SEND MESSAGE
308 ///////////////////////////////////////////////////////////////////////////////
310 bool MUtils::IPCChannel::send(const quint32 &command, const quint32 &flags, const QStringList &params)
312 bool success = false;
313 QReadLocker readLock(&p->lock);
315 if(!p->initialized)
317 MUTILS_THROW("Shared memory for IPC not initialized yet.");
320 if(!p->semaphore_wr->acquire())
322 const QString errorMessage = p->semaphore_wr->errorString();
323 qWarning("Failed to acquire system semaphore: %s", MUTILS_UTF8(errorMessage));
324 return false;
327 if(!p->sharedmem->lock())
329 const QString errorMessage = p->sharedmem->errorString();
330 qWarning("Failed to lock shared memory: %s", MUTILS_UTF8(errorMessage));
331 return false;
334 if(Internal::ipc_t *const ptr = reinterpret_cast<Internal::ipc_t*>(p->sharedmem->data()))
336 if(VERIFY_CHECKSUM(ptr->status))
338 Internal::ipc_msg_t ipc_msg;
339 memset(&ipc_msg, 0, sizeof(Internal::ipc_msg_t));
341 ipc_msg.payload.command_id = command;
342 ipc_msg.payload.flags = flags;
343 if(!params.isEmpty())
345 const quint32 param_count = qMin(MAX_PARAM_CNT, (quint32)params.count());
346 for(quint32 i = 0; i < param_count; i++)
348 strncpy_s(ipc_msg.payload.params.values[i], MAX_PARAM_LEN, MUTILS_UTF8(params[i].trimmed()), _TRUNCATE);
350 ipc_msg.payload.params.count = param_count;
352 ipc_msg.payload.timestamp = ptr->status.payload.counter++;
353 UPDATE_CHECKSUM(ipc_msg);
355 memcpy(&ptr->data[ptr->status.payload.pos_wr], &ipc_msg, sizeof(Internal::ipc_msg_t));
356 ptr->status.payload.pos_wr = (ptr->status.payload.pos_wr + 1) % Internal::IPC_SLOTS;
357 UPDATE_CHECKSUM(ptr->status);
359 success = true;
361 else
363 qWarning("Corrupted IPC status detected -> skipping!");
366 else
368 qWarning("Shared memory pointer is NULL -> unable to write data!");
371 if(!p->sharedmem->unlock())
373 const QString errorMessage = p->sharedmem->errorString();
374 qFatal("Failed to unlock shared memory: %s", MUTILS_UTF8(errorMessage));
377 if(!p->semaphore_rd->release())
379 const QString errorMessage = p->semaphore_rd->errorString();
380 qWarning("Failed to release system semaphore: %s", MUTILS_UTF8(errorMessage));
383 return success;
386 ///////////////////////////////////////////////////////////////////////////////
387 // READ MESSAGE
388 ///////////////////////////////////////////////////////////////////////////////
390 bool MUtils::IPCChannel::read(quint32 &command, quint32 &flags, QStringList &params)
392 bool success = false;
393 QReadLocker readLock(&p->lock);
394 command = 0;
395 params.clear();
397 if(!p->initialized)
399 MUTILS_THROW("Shared memory for IPC not initialized yet.");
402 Internal::ipc_msg_t ipc_msg;
403 memset(&ipc_msg, 0, sizeof(Internal::ipc_msg_t));
405 if(!p->semaphore_rd->acquire())
407 const QString errorMessage = p->semaphore_rd->errorString();
408 qWarning("Failed to acquire system semaphore: %s", MUTILS_UTF8(errorMessage));
409 return false;
412 if(!p->sharedmem->lock())
414 const QString errorMessage = p->sharedmem->errorString();
415 qWarning("Failed to lock shared memory: %s", MUTILS_UTF8(errorMessage));
416 return false;
419 if(Internal::ipc_t *const ptr = reinterpret_cast<Internal::ipc_t*>(p->sharedmem->data()))
421 if(VERIFY_CHECKSUM(ptr->status))
423 memcpy(&ipc_msg, &ptr->data[ptr->status.payload.pos_rd], sizeof(Internal::ipc_msg_t));
424 ptr->status.payload.pos_rd = (ptr->status.payload.pos_rd + 1) % Internal::IPC_SLOTS;
425 UPDATE_CHECKSUM(ptr->status);
427 if(VERIFY_CHECKSUM(ipc_msg) || (ipc_msg.payload.timestamp < ptr->status.payload.counter))
429 command = ipc_msg.payload.command_id;
430 flags = ipc_msg.payload.flags;
431 const quint32 param_count = qMin(ipc_msg.payload.params.count, MAX_PARAM_CNT);
432 char temp[MAX_PARAM_LEN];
433 for(quint32 i = 0; i < param_count; i++)
435 strncpy_s(temp, MAX_PARAM_LEN, ipc_msg.payload.params.values[i], _TRUNCATE);
436 params.append(QString::fromUtf8(temp));
438 success = true;
440 else
442 qWarning("Malformed or corrupted IPC message, will be ignored!");
445 else
447 qWarning("Corrupted IPC status detected -> skipping!");
450 else
452 qWarning("Shared memory pointer is NULL -> unable to read data!");
455 if(!p->sharedmem->unlock())
457 const QString errorMessage = p->sharedmem->errorString();
458 qFatal("Failed to unlock shared memory: %s", MUTILS_UTF8(errorMessage));
461 if(!p->semaphore_wr->release())
463 const QString errorMessage = p->semaphore_wr->errorString();
464 qWarning("Failed to release system semaphore: %s", MUTILS_UTF8(errorMessage));
467 return success;