#include "cmd_executor.h" // This file is part of MRCI. // MRCI is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // MRCI is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with MRCI under the LICENSE.md file. If not, see // . CmdExecutor::CmdExecutor(RWSharedObjs *rwShare, SharedObjs *rdOnlyShare, QSharedMemory *debugInfo, QObject *parent) : QObject(parent) { rwShare->commands = &commands; rwShare->activeLoopCmds = &activeLoopCmds; rwShare->pausedCmds = &pausedCmds; rwShare->moreInputCmds = &moreInputCmds; rwShare->cmdNames = &cmdNames; rdOnlyShare->activeLoopCmds = &activeLoopCmds; rdOnlyShare->pausedCmds = &pausedCmds; rdOnlyShare->moreInputCmds = &moreInputCmds; rdOnlyShare->cmdNames = &cmdNames; loopIndex = 0; exeDebugInfo = debugInfo; rdSharedObjs = rdOnlyShare; rwSharedObjs = rwShare; internalCmds = new InternalCommandLoader(rwShare, this); connect(this, &CmdExecutor::loop, this, &CmdExecutor::exeCmd); } void CmdExecutor::wrCrashDebugInfo(const QString &msg) { if (exeDebugInfo->isAttached()) { exeDebugInfo->lock(); QByteArray data = toTEXT(msg).leftJustified(EXE_DEBUG_INFO_SIZE, static_cast(0), true); memcpy(exeDebugInfo->data(), data.data(), EXE_DEBUG_INFO_SIZE); exeDebugInfo->unlock(); } } void CmdExecutor::connectExternCmd(ExternCommand *cmd, quint16 cmdId, const QString &cmdName) { wrCrashDebugInfo(" exe func: connectExternCmd()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); auto *cmdOutput = new CommandOutput(cmd); connect(cmdOutput, &CommandOutput::dataOut, this, &CmdExecutor::externDataToIPC); connectCommon(cmd, cmdOutput, cmdId, cmdName); } void CmdExecutor::connectInternCmd(InternCommand *cmd, quint16 cmdId, const QString &cmdName) { wrCrashDebugInfo(" exe func: connectInternCmd()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); auto *cmdOutput = new CommandOutput(cmd); connect(cmdOutput, &CommandOutput::dataOut, this, &CmdExecutor::internDataToIPC); connect(cmd, &InternCommand::backendDataOut, this, &CmdExecutor::backendFromCmd); connect(cmd, &InternCommand::authOk, this, &CmdExecutor::authOk); connect(cmd, &InternCommand::termAllCommands, this, &CmdExecutor::termAllCommands); connect(cmd, &InternCommand::termCommandId, this, &CmdExecutor::termCommandId); connect(cmd, &InternCommand::loadMod, this, &CmdExecutor::loadModFile); connect(cmd, &InternCommand::unloadMod, this, &CmdExecutor::unloadModFile); connect(cmd, &InternCommand::reloadCommands, this, &CmdExecutor::buildCommands); connectCommon(cmd, cmdOutput, cmdId, cmdName); } void CmdExecutor::connectCommon(ExternCommand *cmd, CommandOutput *cmdOutput, quint16 cmdId, const QString &cmdName) { wrCrashDebugInfo(" exe func: connectCommon()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); connect(cmdOutput, &CommandOutput::closeChById, this, &CmdExecutor::closeChById); connect(cmdOutput, &CommandOutput::openChById, this, &CmdExecutor::openChById); connect(cmdOutput, &CommandOutput::closeChByName, this, &CmdExecutor::closeChByName); connect(cmdOutput, &CommandOutput::openChByName, this, &CmdExecutor::openChByName); connect(cmdOutput, &CommandOutput::cmdFinished, this, &CmdExecutor::commandFinished); connect(cmdOutput, &CommandOutput::enableLoop, this, &CmdExecutor::enableLoop); connect(cmdOutput, &CommandOutput::enableMoreInput, this, &CmdExecutor::enableMoreInput); connect(cmd, &ExternCommand::closeChById, cmdOutput, &CommandOutput::closeChIdFromCmdObj); connect(cmd, &ExternCommand::openChById, cmdOutput, &CommandOutput::openChIdFromCmdObj); connect(cmd, &ExternCommand::closeChByName, cmdOutput, &CommandOutput::closeChNameFromCmdObj); connect(cmd, &ExternCommand::openChByName, cmdOutput, &CommandOutput::openChNameFromCmdObj); connect(cmd, &ExternCommand::cmdFinished, cmdOutput, &CommandOutput::finished); connect(cmd, &ExternCommand::enableLoop, cmdOutput, &CommandOutput::enableLoopFromCmdObj); connect(cmd, &ExternCommand::enableMoreInput, cmdOutput, &CommandOutput::enableMoreInputFromCmdObj); connect(cmd, &ExternCommand::dataToClient, cmdOutput, &CommandOutput::dataFromCmdObj); connect(cmd, &ExternCommand::castToPeers, this, &CmdExecutor::castToPeers); connect(cmd, &ExternCommand::toPeer, this, &CmdExecutor::toPeer); connect(cmd, &ExternCommand::closeSession, this, &CmdExecutor::endSession); connect(cmd, &ExternCommand::logout, this, &CmdExecutor::logout); cmd->cmdId = cmdId; cmd->setObjectName(cmdName); cmdOutput->setCmdId(cmdId); } void CmdExecutor::enableLoop(quint16 cmdId, bool state) { if (state) { uniqueAdd(cmdId, activeLoopCmds); } else { activeLoopCmds.removeAll(cmdId); } } void CmdExecutor::enableMoreInput(quint16 cmdId, bool state) { if (state) { uniqueAdd(cmdId, moreInputCmds); } else { moreInputCmds.removeAll(cmdId); } } void CmdExecutor::preExe(ExternCommand *cmdObj, quint16 cmdId) { cmdObj->cmdId = cmdId; cmdObj->errSent = false; } void CmdExecutor::preExe(const QList &cmdObjs, quint16 cmdId) { for (auto cmdObj : cmdObjs) { preExe(cmdObj, cmdId); } } void CmdExecutor::exeCmd(quint16 cmdId, const QByteArray &data, uchar typeId) { wrCrashDebugInfo(" exe func: exeCmd()\n cmd id: " + QString::number(cmdId) + "\n type id: " + QString::number(typeId)); if (!commands.contains(cmdId)) { emit dataToSession(cmdId, toTEXT("err: The requested command id: '" + QString::number(cmdId) + "' does not exists.\n"), ERR); emit dataToSession(cmdId, QByteArray(), IDLE); } else if (!pausedCmds.contains(cmdId)) { ExternCommand *cmdObj = commands[cmdId]; preExe(cmdObj, cmdId); preExe(cmdObj->internCommands.values(), cmdId); cmdObj->procBin(rdSharedObjs, data, typeId); if (!activeLoopCmds.contains(cmdId) && !moreInputCmds.contains(cmdId)) { emit cmdObj->cmdFinished(); } nextLoopCmd(); } } void CmdExecutor::nextLoopCmd() { if (!activeLoopCmds.isEmpty()) { if (loopIndex == activeLoopCmds.size()) { loopIndex = 0; } wrCrashDebugInfo(" exe func: nextLoopCmd()\n loop index: " + QString::number(loopIndex)); emit loop(activeLoopCmds[loopIndex++], QByteArray(), TEXT); } } void CmdExecutor::termCommandObj(quint16 cmdId, ExternCommand *cmd, bool del) { wrCrashDebugInfo(" exe func: termCommandObj()\n cmd id: " + QString::number(cmdId)); if (moreInputCmds.contains(cmdId) || activeLoopCmds.contains(cmdId)) { cmd->term(); cmd->inLoopMode = false; cmd->inMoreInputMode = false; for (auto internObj : cmd->internCommands.values()) { if (internObj->inLoopMode || internObj->inMoreInputMode) { internObj->term(); internObj->inLoopMode = false; internObj->inMoreInputMode = false; } } emit dataToSession(cmdId, QByteArray(), IDLE); } moreInputCmds.removeAll(cmdId); activeLoopCmds.removeAll(cmdId); pausedCmds.removeAll(cmdId); if (del) { wrCrashDebugInfo(" exe func: termCommandObj()\n cmd id: " + QString::number(cmdId) + "\n note: calling the command object's aboutToDelete()"); for (auto internObj : cmd->internCommands.values()) { internObj->aboutToDelete(); internObj->deleteLater(); } cmd->aboutToDelete(); cmd->deleteLater(); commands.remove(cmdId); cmdNames.remove(cmdId); emit dataToSession(ASYNC_RM_CMD, wrInt(cmdId, 16), CMD_ID); } } void CmdExecutor::termCommandId(quint16 cmdId) { if (commands.contains(cmdId)) { termCommandObj(cmdId, commands[cmdId]); } } void CmdExecutor::termCommandsInList(const QList &cmds, bool del) { for (auto&& cmdId : cmds) { termCommandObj(cmdId, commands[cmdId], del); } } void CmdExecutor::termAllCommands() { termCommandsInList(moreInputCmds, false); termCommandsInList(activeLoopCmds, false); termCommandsInList(pausedCmds, false); } void CmdExecutor::close() { clearCommands(); cleanupDbConnection(); emit okToDelete(); } void CmdExecutor::clearCommands() { termCommandsInList(commands.keys(), true); QList loaders = mods.keys(); for (auto* loader : loaders) { ModCommandLoader *cmdLoader = qobject_cast(loader->instance()); if (cmdLoader) { wrCrashDebugInfo(" exe func: clearCommands()\n mod file: " + loader->fileName() + "\n note: calling the modules's aboutToDelete()"); cmdLoader->aboutToDelete(); } loader->unload(); loader->deleteLater(); } mods.clear(); } void CmdExecutor::commandFinished(quint16 cmdId) { emit dataToSession(cmdId, QByteArray(), IDLE); moreInputCmds.removeAll(cmdId); activeLoopCmds.removeAll(cmdId); pausedCmds.removeAll(cmdId); } QString CmdExecutor::makeCmdUnique(const QString &name) { QString strNum; QStringList names = cmdNames.values(); for (int j = 1; names.contains(QString(name + strNum).toLower()); ++j) { strNum = "_" + QString::number(j); } return QString(name + strNum).toLower(); } void CmdExecutor::procInternRequest(ExternCommand *cmd, quint16 cmdId, const QString &cmdName) { wrCrashDebugInfo(" exe func: procInternRequest()\n cmd id: " + QString::number(cmdId)); QStringList internCmdNames = internalCmds->cmdList(); for (auto&& reqCmdName : cmd->internRequest()) { if (internCmdNames.contains(reqCmdName, Qt::CaseInsensitive)) { InternCommand *cmdObj = internalCmds->cmdObj(reqCmdName); if (cmdObj != nullptr) { connectInternCmd(cmdObj, cmdId, cmdName); cmdObj->setParent(cmd); cmd->internCommands.insert(reqCmdName, cmdObj); } } } } quint16 CmdExecutor::getModIdOffs(const QString &path) { Query db(this); db.setType(Query::PULL, TABLE_MODULES); db.addColumn(COLUMN_CMD_ID_OFFS); db.addCondition(COLUMN_MOD_MAIN, path); db.exec(); return static_cast(db.getData(COLUMN_CMD_ID_OFFS).toUInt()); } void CmdExecutor::loadModFile(const QString &path) { loadModLib(path, getModIdOffs(path)); } void CmdExecutor::loadModLib(const QString &path, quint16 idOffs) { if (!isModFileLoaded(path) && QFile::exists(path)) { bool modOk = false; auto *pluginLoader = new QPluginLoader(path); QObject *cmdLoaderObj = pluginLoader->instance(); ModCommandLoader *cmdLoader = qobject_cast(cmdLoaderObj); QString modPath = QFileInfo(path).path(); if (idOffs == 0) { qDebug() << "CmdExecutor::loadModLib() err: failed to get a valid command id offset for the module."; } else if (!pluginLoader->isLoaded()) { qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: " << pluginLoader->errorString(); } else if (!cmdLoaderObj) { qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: the root component object could not be instantiated."; } else if (!cmdLoader) { qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: the ModCommandLoader object could not be instantiated."; } else if (cmdLoader->rev() < IMPORT_REV) { qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " reason: module import rev " << cmdLoader->rev() << " not compatible with host rev " << IMPORT_REV << "."; } else if (!cmdLoader->hostRevOk(IMPORT_REV)) { qDebug() << "CmdExecutor::loadModLib() err: failed to load mod lib file: " << path << " the module rejected the host import rev. reason: " << cmdLoader->lastError() << "."; } else { wrCrashDebugInfo(" exe func: loadModLib()\n path: " + path); modOk = true; cmdLoader->modPath(modPath); loadCmds(cmdLoader, idOffs, pluginLoader, QFileInfo(modPath).fileName()); } if (!modOk) { pluginLoader->unload(); pluginLoader->deleteLater(); } } } void CmdExecutor::unloadModFile(const QString &path) { if (isModFileLoaded(path)) { QList modLoaders = mods.keys(); for (auto* loader : modLoaders) { if (rmFileSuffix(loader->fileName()) == path) { QList cmdList = mods.value(loader); termCommandsInList(cmdList, true); ModCommandLoader *cmdLoader = qobject_cast(loader->instance()); if (cmdLoader != nullptr) { wrCrashDebugInfo(" exe func: unloadModFile()\n mod file: " + loader->fileName() + "\n note: calling the modules's aboutToDelete()"); cmdLoader->aboutToDelete(); } loader->unload(); loader->deleteLater(); mods.remove(loader); break; } } } } bool CmdExecutor::isModFileLoaded(const QString &path) { bool ret = false; QList loaders = mods.keys(); for (auto* loader : loaders) { if (rmFileSuffix(loader->fileName()) == path) { ret = true; break; } } return ret; } void CmdExecutor::loadMods() { Query db(this); db.setType(Query::PULL, TABLE_MODULES); db.addColumn(COLUMN_MOD_MAIN); db.addColumn(COLUMN_CMD_ID_OFFS); db.addCondition(COLUMN_LOCKED, false); db.exec(); for (int i = 0; db.rows(); ++i) { loadModLib(db.getData(COLUMN_MOD_MAIN, i).toString(), static_cast(db.getData(COLUMN_CMD_ID_OFFS).toUInt())); } } bool CmdExecutor::allowCmdLoad(const QString &cmdName, const QString &modName, const QStringList &exemptList) { bool ret = false; if (exemptList.contains(cmdName, Qt::CaseInsensitive)) { ret = true; } else { Query db(this); db.setType(Query::PULL, TABLE_CMD_RANKS); db.addColumn(COLUMN_HOST_RANK); db.addCondition(COLUMN_COMMAND, cmdName); db.addCondition(COLUMN_MOD_NAME, modName); db.exec(); if (db.rows()) { uint cmdRank = db.getData(COLUMN_HOST_RANK).toUInt(); if (cmdRank >= *rdSharedObjs->hostRank) { ret = true; } } else if (*rdSharedObjs->hostRank == 1) { ret = true; } } return ret; } void CmdExecutor::loadInternCmd(CommandLoader *loader, const QString &cmdName, const QString &uniqueName, quint16 id) { auto *cmdObj = reinterpret_cast(loader->cmdObj(cmdName)); if (cmdObj != nullptr) { connectInternCmd(cmdObj, id, uniqueName); procInternRequest(cmdObj, id, uniqueName); commands.insert(id, cmdObj); cmdNames.insert(id, uniqueName); emit dataToSession(ASYNC_ADD_CMD, toNEW_CMD(id, uniqueName, cmdObj), NEW_CMD); } } void CmdExecutor::loadExternCmd(CommandLoader *loader, const QString &cmdName, const QString &uniqueName, quint16 id) { ExternCommand *cmdObj = loader->cmdObj(cmdName); if (cmdObj != nullptr) { connectExternCmd(cmdObj, id, uniqueName); procInternRequest(cmdObj, id, uniqueName); commands.insert(id, cmdObj); cmdNames.insert(id, uniqueName); emit dataToSession(ASYNC_ADD_CMD, toNEW_CMD(id, uniqueName, cmdObj), NEW_CMD); } } void CmdExecutor::addToModList(quint16 cmdId, QPluginLoader *pluginLoader) { if (mods.contains(pluginLoader)) { mods[pluginLoader].append(cmdId); } else { mods.insert(pluginLoader, QList() << cmdId); } } void CmdExecutor::loadCmd(CommandLoader *loader, quint16 cmdId, QPluginLoader *pluginLoader, const QString &cmdName, const QString &uniqueCmdName) { wrCrashDebugInfo(" exe func: loadCmd()\n cmd id: " + QString::number(cmdId) + "\n cmd name: " + cmdName); if (pluginLoader == nullptr) { loadInternCmd(loader, cmdName, uniqueCmdName, cmdId); } else { loadExternCmd(loader, cmdName, uniqueCmdName, cmdId); addToModList(cmdId, pluginLoader); } } void CmdExecutor::loadCmds(CommandLoader *loader, quint16 idOffs, QPluginLoader *pluginLoader, const QString &modName) { QStringList list = loader->cmdList(); QStringList pub = loader->pubCmdList(); QStringList exempt = loader->rankExemptList(); list.sort(Qt::CaseInsensitive); for (quint16 id = 0; (id < list.size()) && (id < MAX_CMDS_PER_MOD); ++id) { QString unique = makeCmdUnique(list[id]); quint16 cmdId = idOffs + id; if (validCommandName(unique)) { if (commands.contains(cmdId)) { if (!allowCmdLoad(list[id], modName, exempt)) { termCommandObj(cmdId, commands[cmdId], true); } } else { if (rdSharedObjs->userName->isEmpty()) { if (pub.contains(list[id], Qt::CaseInsensitive)) { loadCmd(loader, cmdId, pluginLoader, list[id], unique); } } else if (allowCmdLoad(list[id], modName, exempt)) { loadCmd(loader, cmdId, pluginLoader, list[id], unique); } } } else { qDebug() << "CmdExecutor::getCmdNames() err: command object name '" << unique << "' is not valid."; } } } void CmdExecutor::buildCommands() { loadCmds(internalCmds, MAX_CMDS_PER_MOD, nullptr, INTERN_MOD_NAME); loadMods(); } bool CmdExecutor::externBlockedTypeId(uchar typeId) { // the internal host objects will handle sending the following TypeIDs to the clients // and peers. any attempt to do so via ExternCommand object will be blocked since these // data types can cause some behaviour issues if sent at an unexpected time. return (typeId == PRIV_IPC) || (typeId == PUB_IPC) || (typeId == PING_PEERS) || (typeId == PEER_STAT) || (typeId == MY_INFO) || (typeId == PEER_INFO) || (typeId == HOST_CERT) || (typeId == IDLE) || (typeId == NEW_CMD); } bool CmdExecutor::p2pBlockedTypeId(uchar typeId) { // this is used to block P2P specific typeIDs. only toPeers() or internDataToIPC() // should be allowed to send these frame types, all others use this to block them. return (typeId == P2P_REQUEST) || (typeId == P2P_OPEN) || (typeId == P2P_CLOSE); } void CmdExecutor::externDataToIPC(quint16 cmdId, const QByteArray &data, uchar typeId) { if (!externBlockedTypeId(typeId) && !p2pBlockedTypeId(typeId)) { emit dataToSession(cmdId, data, typeId); } } void CmdExecutor::internDataToIPC(quint16 cmdId, const QByteArray &data, uchar typeId) { if ((typeId != IDLE) && (typeId != NEW_CMD)) { emit dataToSession(cmdId, data, typeId); } } void CmdExecutor::castToPeers(const QByteArray &data, uchar typeId) { if (!externBlockedTypeId(typeId) && !p2pBlockedTypeId(typeId)) { QByteArray castHeader = *rdSharedObjs->wrAbleChIds + wrInt(typeId, 8); emit dataToSession(ASYNC_CAST, castHeader + data, PUB_IPC); } } void CmdExecutor::toPeer(const QByteArray &dst, const QByteArray &data, uchar typeId) { if (!externBlockedTypeId(typeId) && (dst.size() == 28)) { QByteArray p2pHeader = dst + *rdSharedObjs->sessionId + wrInt(typeId, 8); if (typeId == P2P_REQUEST) { if (!rdSharedObjs->p2pPending->contains(dst)) { rwSharedObjs->p2pPending->append(dst); emit dataToSession(ASYNC_P2P, p2pHeader + toPEER_INFO(rdSharedObjs), PUB_IPC); } } else { if ((typeId == P2P_CLOSE) || (typeId == P2P_OPEN)) { if (rdSharedObjs->p2pPending->contains(dst) || rdSharedObjs->p2pAccepted->contains(dst)) { if (typeId == P2P_CLOSE) { rwSharedObjs->p2pPending->removeAll(dst); rwSharedObjs->p2pAccepted->removeAll(dst); emit dataToSession(ASYNC_P2P, p2pHeader + dst, PUB_IPC); } else if (!rdSharedObjs->p2pAccepted->contains(dst)) { rwSharedObjs->p2pPending->removeAll(dst); rwSharedObjs->p2pAccepted->append(dst); emit dataToSession(ASYNC_P2P, p2pHeader + dst, PUB_IPC); } } } else { emit dataToSession(ASYNC_P2P, p2pHeader + data, PUB_IPC); } } } } void CmdExecutor::backendFromCmd(quint16 cmdId, const QByteArray &data, uchar typeId) { if ((typeId == PUB_IPC) || (typeId == PRIV_IPC) || (typeId == PUB_IPC_WITH_FEEDBACK)) { emit dataToSession(cmdId, data, typeId); } } void CmdExecutor::openOrCloseChByName(quint16 cmdId, const QString &ch, const QString &sub, bool open) { if (!validChName(ch)) { emit dataToSession(cmdId, toTEXT("err: '" + ch + "' is not a valid channel name.\n"), ERR); } else if (!validChName(sub)) { emit dataToSession(cmdId, toTEXT("err: '" + sub + "' is not a valid sub channel name.\n"), ERR); } else if (!channelSubExists(ch, sub)) { emit dataToSession(cmdId, toTEXT("err: Sub-channel: '" + sub + "' does not exists.\n"), ERR); } else { Query db(this); db.setType(Query::PULL, TABLE_SUB_CHANNELS); db.addColumn(COLUMN_CHANNEL_ID); db.addColumn(COLUMN_SUB_CH_ID); db.addCondition(COLUMN_SUB_CH_NAME, sub); db.addCondition(COLUMN_CHANNEL_NAME, ch); db.exec(); if (open) { openChById(cmdId, db.getData(COLUMN_CHANNEL_ID).toULongLong(), static_cast(db.getData(COLUMN_SUB_CH_ID).toUInt())); } else { closeChById(cmdId, db.getData(COLUMN_CHANNEL_ID).toULongLong(), static_cast(db.getData(COLUMN_SUB_CH_ID).toUInt())); } } } void CmdExecutor::openChByName(quint16 cmdId, const QString &ch, const QString &sub) { openOrCloseChByName(cmdId, ch, sub, true); } void CmdExecutor::closeChByName(quint16 cmdId, const QString &ch, const QString &sub) { openOrCloseChByName(cmdId, ch, sub, false); } void CmdExecutor::openChById(quint16 cmdId, quint64 chId, uchar subId) { QByteArray id = wrInt(chId, 64) + wrInt(subId, 8); if (chId == 0) { emit dataToSession(cmdId, toTEXT("err: '0' is not a valid channel id. it must an unsigned integer between 1-18446744073709551615.\n"), ERR); } else if (countChs(*rdSharedObjs->chIds) == 6) { emit dataToSession(cmdId, toTEXT("err: The maximum amount of open sub-channels reached (6).\n"), ERR); } else if (containsChId(id, *rdSharedObjs->chIds)) { emit dataToSession(cmdId, toTEXT("err: The requested sub-channel is already open.\n"), ERR); } else if (!channelSubExists(chId, subId)) { emit dataToSession(cmdId, toTEXT("err: The requested sub-channel does not exists.\n"), ERR); } else if (channelAccessLevel(rdSharedObjs, chId) > lowestAcessLevel(chId, subId)) { emit dataToSession(cmdId, toTEXT("err: Access denied.\n"), ERR); } else { wrOpenCh(rwSharedObjs, id); } } void CmdExecutor::closeChById(quint16 cmdId, quint64 chId, uchar subId) { QByteArray id = wrInt(chId, 64) + wrInt(subId, 8); if (chId == 0) { emit dataToSession(cmdId, toTEXT("err: '0' is not a valid channel id. it must an integer between 1-18446744073709551615.\n"), ERR); } else if (!containsChId(id, *rdSharedObjs->chIds)) { emit dataToSession(cmdId, toTEXT("err: The requested sub-channel is not open.\n"), ERR); } else { QByteArray peerStat; wrCloseCh(rwSharedObjs, id, peerStat); if (!peerStat.isEmpty()) { emit dataToSession(ASYNC_LIMITED_CAST, peerStat, PUB_IPC); } } }