diff --git a/DapRPCProtocol/DapRPCProtocol.pri b/DapRPCProtocol/DapRPCProtocol.pri new file mode 100644 index 0000000000000000000000000000000000000000..c6899ffaeda20af18e162318bfcdf8b75c5f6227 --- /dev/null +++ b/DapRPCProtocol/DapRPCProtocol.pri @@ -0,0 +1,25 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/DapRpcAbstractServer.h \ + $$PWD/DapRpcLocalServer.h \ + $$PWD/DapRpcMessage.h \ + $$PWD/DapRpcService.h \ + $$PWD/DapRpcServiceProvider.h \ + $$PWD/DapRpcServiceReply.h \ + $$PWD/DapRpcServiceRequest.h \ + $$PWD/DapRpcSocket.h \ + $$PWD/DapRpcTCPServer.h + +SOURCES += \ + $$PWD/DapRpcAbstractServer.cpp \ + $$PWD/DapRpcLocalServer.cpp \ + $$PWD/DapRpcMessage.cpp \ + $$PWD/DapRpcService.cpp \ + $$PWD/DapRpcServiceProvider.cpp \ + $$PWD/DapRpcServiceReply.cpp \ + $$PWD/DapRpcServiceRequest.cpp \ + $$PWD/DapRpcSocket.cpp \ + $$PWD/DapRpcTCPServer.cpp + + diff --git a/DapRPCProtocol/DapRpcAbstractServer.cpp b/DapRPCProtocol/DapRpcAbstractServer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7a8882dac983a6799e36a84a2be615270d904c58 --- /dev/null +++ b/DapRPCProtocol/DapRpcAbstractServer.cpp @@ -0,0 +1,29 @@ +#include "DapRpcAbstractServer.h" + +DapRpcAbstractServer::DapRpcAbstractServer() +{ + +} + +DapRpcAbstractServer::~DapRpcAbstractServer() +{ + +} + +int DapRpcAbstractServer::connectedClientCount() const +{ + return m_clients.size(); +} + +void DapRpcAbstractServer::notifyConnectedClients(const DapRpcMessage &message) +{ + for (int i = 0; i < m_clients.size(); ++i) + m_clients[i]->sendMessage(message); +} + +void DapRpcAbstractServer::notifyConnectedClients(const QString &method, const QJsonArray ¶ms) +{ + DapRpcMessage notification = + DapRpcMessage::createNotification(method, params); + notifyConnectedClients(notification); +} diff --git a/DapRPCProtocol/DapRpcAbstractServer.h b/DapRPCProtocol/DapRpcAbstractServer.h new file mode 100644 index 0000000000000000000000000000000000000000..56fa07daddf96f28ecc525f5c61910c0d5a6012e --- /dev/null +++ b/DapRPCProtocol/DapRpcAbstractServer.h @@ -0,0 +1,31 @@ +#ifndef DapRPCABSTRACTSERVER_H +#define DapRPCABSTRACTSERVER_H + +#include <QList> +#include <QHostAddress> + +#include "DapRpcSocket.h" +#include "DapRpcMessage.h" +#include "DapRpcServiceProvider.h" + +class DapRpcAbstractServer : public DapRpcServiceProvider +{ +protected: + QList<DapRpcSocket*> m_clients; + +public: + DapRpcAbstractServer(); + + virtual ~DapRpcAbstractServer(); + virtual int connectedClientCount() const; + virtual bool listen(const QString &asAddress = QString(), quint16 aPort = 0) = 0; +// signals: + virtual void onClientConnected() = 0; + virtual void onClientDisconnected() = 0; + +// public slots: + virtual void notifyConnectedClients(const DapRpcMessage &message); + virtual void notifyConnectedClients(const QString &method, const QJsonArray ¶ms); +}; + +#endif // DapRPCABSTRACTSERVER_H diff --git a/DapRPCProtocol/DapRpcLocalServer.cpp b/DapRPCProtocol/DapRpcLocalServer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..11a2e842352b3a1d14409ad41abfd1e5ad0505ba --- /dev/null +++ b/DapRPCProtocol/DapRpcLocalServer.cpp @@ -0,0 +1,108 @@ +#include "DapRpcLocalServer.h" + +DapRpcLocalServer::DapRpcLocalServer(QObject *apParent) + : QLocalServer(apParent) +{ + this->setSocketOptions(QLocalServer::WorldAccessOption); +} + +DapRpcLocalServer::~DapRpcLocalServer() +{ + foreach (QLocalSocket *socket, m_socketLookup.keys()) { + socket->flush(); + socket->deleteLater(); + } + m_socketLookup.clear(); + + foreach (DapRpcSocket *client, m_clients) + client->deleteLater(); + m_clients.clear(); +} + +bool DapRpcLocalServer::listen(const QString &asAddress, quint16 aPort) +{ + Q_UNUSED(aPort); + + return QLocalServer::listen(asAddress); +} + +bool DapRpcLocalServer::addService(DapRpcService *apService) +{ + if (!DapRpcServiceProvider::addService(apService)) + return false; + + connect(apService, SIGNAL(notifyConnectedClients(DapRpcMessage)), + this, SLOT(notifyConnectedClients(DapRpcMessage))); + connect(apService, SIGNAL(notifyConnectedClients(QString,QJsonArray)), + this, SLOT(notifyConnectedClients(QString,QJsonArray))); + return true; +} + +bool DapRpcLocalServer::removeService(DapRpcService *apService) +{ + if (!DapRpcServiceProvider::removeService(apService)) + return false; + + disconnect(apService, SIGNAL(notifyConnectedClients(DapRpcMessage)), + this, SLOT(notifyConnectedClients(DapRpcMessage))); + disconnect(apService, SIGNAL(notifyConnectedClients(QString,QJsonArray)), + this, SLOT(notifyConnectedClients(QString,QJsonArray))); + return true; +} + +void DapRpcLocalServer::clientDisconnected() +{ + QLocalSocket *localSocket = static_cast<QLocalSocket*>(sender()); + if (!localSocket) { + qJsonRpcDebug() << "called with invalid socket"; + return; + } + if (m_socketLookup.contains(localSocket)) { + DapRpcSocket *socket = m_socketLookup.take(localSocket); + m_clients.removeAll(socket); + socket->deleteLater(); + } + + localSocket->deleteLater(); + emit onClientDisconnected(); +} + +void DapRpcLocalServer::messageProcessing(const DapRpcMessage &asMessage) +{ + DapRpcSocket *socket = static_cast<DapRpcSocket*>(sender()); + if (!socket) { + qJsonRpcDebug() << "called without service socket"; + return; + } + + processMessage(socket, asMessage); +} + +void DapRpcLocalServer::notifyConnectedClients(const DapRpcMessage &message) +{ + DapRpcAbstractServer::notifyConnectedClients(message); +} + +void DapRpcLocalServer::notifyConnectedClients(const QString &method, const QJsonArray ¶ms) +{ + DapRpcAbstractServer::notifyConnectedClients(method, params); +} + +void DapRpcLocalServer::incomingConnection(quintptr aSocketDescriptor) +{ + QLocalSocket *localSocket = new QLocalSocket(this); + if (!localSocket->setSocketDescriptor(aSocketDescriptor)) { + qJsonRpcDebug() << "nextPendingConnection is null"; + localSocket->deleteLater(); + return; + } + + QIODevice *device = qobject_cast<QIODevice*>(localSocket); + DapRpcSocket *socket = new DapRpcSocket(device, this); + connect(socket, SIGNAL(messageReceived(DapRpcMessage)), + this, SLOT(messageProcessing(DapRpcMessage))); + m_clients.append(socket); + connect(localSocket, SIGNAL(disconnected()), this, SLOT(clientDisconnected())); + m_socketLookup.insert(localSocket, socket); + emit onClientConnected(); +} diff --git a/DapRPCProtocol/DapRpcLocalServer.h b/DapRPCProtocol/DapRpcLocalServer.h new file mode 100644 index 0000000000000000000000000000000000000000..9d7a7e776e0df4a24a40dde79dcb79f43148d8c8 --- /dev/null +++ b/DapRPCProtocol/DapRpcLocalServer.h @@ -0,0 +1,44 @@ +#ifndef DapRPCLOCALSERVER_H +#define DapRPCLOCALSERVER_H + +#include <QObject> +#include <QLocalSocket> +#include <QLocalServer> + +#include "DapRpcSocket.h" +#include "DapRpcService.h" +#include "DapRpcAbstractServer.h" + +class DapRpcLocalServer : public QLocalServer, public DapRpcAbstractServer +{ + Q_OBJECT + Q_DISABLE_COPY(DapRpcLocalServer) + + QHash<QLocalSocket*, DapRpcSocket*> m_socketLookup; + +protected: + virtual void incomingConnection(quintptr aSocketDescriptor); + +public: + explicit DapRpcLocalServer(QObject *apParent = nullptr); + virtual ~DapRpcLocalServer(); + + virtual bool listen(const QString &asAddress = QString(), quint16 aPort = 0); + bool addService(DapRpcService *apService); + bool removeService(DapRpcService *apService); + +signals: + void onClientConnected(); + void onClientDisconnected(); + +private slots: + void clientDisconnected(); + void messageProcessing(const DapRpcMessage &asMessage); + + // DapRpcAbstractServer interface +public slots: + void notifyConnectedClients(const DapRpcMessage &message); + void notifyConnectedClients(const QString &method, const QJsonArray ¶ms); +}; + +#endif // DapRPCLOCALSERVER_H diff --git a/DapRPCProtocol/DapRpcMessage.cpp b/DapRPCProtocol/DapRpcMessage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d07901ba323cb17da89f438a5c3adb773e5216e8 --- /dev/null +++ b/DapRPCProtocol/DapRpcMessage.cpp @@ -0,0 +1,373 @@ +#include "DapRpcMessage.h" + +class DapRpcMessagePrivate : public QSharedData +{ +public: + DapRpcMessagePrivate(); + ~DapRpcMessagePrivate(); + DapRpcMessagePrivate(const DapRpcMessagePrivate &aDapRpcMessagePrivate); + + void initializeWithObject(const QJsonObject &aMessage); + static DapRpcMessage createBasicRequest(const QString &asMethod, const QJsonArray &aParams); + static DapRpcMessage createBasicRequest(const QString &asMethod, + const QJsonObject &aNamedParameters); + + DapRpcMessage::Type m_type; + QScopedPointer<QJsonObject> m_pObject; + + static int uniqueRequestCounter; +}; + +int DapRpcMessagePrivate::uniqueRequestCounter = 0; + +DapRpcMessagePrivate::DapRpcMessagePrivate() + : m_type(DapRpcMessage::Invalid), + m_pObject(nullptr) +{ +} + +DapRpcMessagePrivate::DapRpcMessagePrivate(const DapRpcMessagePrivate &aDapRpcMessagePrivate) + : QSharedData(aDapRpcMessagePrivate), + m_type(aDapRpcMessagePrivate.m_type), + m_pObject(aDapRpcMessagePrivate.m_pObject ? new QJsonObject(*aDapRpcMessagePrivate.m_pObject) : nullptr) +{ +} + +void DapRpcMessagePrivate::initializeWithObject(const QJsonObject &aMessage) +{ + m_pObject.reset(new QJsonObject(aMessage)); + if (aMessage.contains(QLatin1String("id"))) { + if (aMessage.contains(QLatin1String("result")) || + aMessage.contains(QLatin1String("error"))) { + if (aMessage.contains(QLatin1String("error")) && + !aMessage.value(QLatin1String("error")).isNull()) + m_type = DapRpcMessage::Error; + else + m_type = DapRpcMessage::Response; + } else if (aMessage.contains(QLatin1String("method"))) { + m_type = DapRpcMessage::Request; + } + } else { + if (aMessage.contains(QLatin1String("method"))) + m_type = DapRpcMessage::Notification; + } +} + +DapRpcMessagePrivate::~DapRpcMessagePrivate() +{ +} + +DapRpcMessage::DapRpcMessage() + : d(new DapRpcMessagePrivate) +{ + d->m_pObject.reset(new QJsonObject); +} + +DapRpcMessage::DapRpcMessage(const DapRpcMessage &aDapRPCMessage) + : d(aDapRPCMessage.d) +{ +} + +DapRpcMessage::~DapRpcMessage() +{ +} + +DapRpcMessage &DapRpcMessage::operator=(const DapRpcMessage &aDapRPCMessage) +{ + d = aDapRPCMessage.d; + return *this; +} + +bool DapRpcMessage::operator==(const DapRpcMessage &aDapRpcMessage) const +{ + if (aDapRpcMessage.d == d) + return true; + + if (aDapRpcMessage.type() == type()) { + if (aDapRpcMessage.type() == DapRpcMessage::Error) { + return (aDapRpcMessage.errorCode() == errorCode() && + aDapRpcMessage.errorMessage() == errorMessage() && + aDapRpcMessage.errorData() == errorData()); + } else { + if (aDapRpcMessage.type() == DapRpcMessage::Notification) { + return (aDapRpcMessage.method() == method() && + aDapRpcMessage.params() == params()); + } else { + return (aDapRpcMessage.id() == id() && + aDapRpcMessage.method() == method() && + aDapRpcMessage.params() == params()); + } + } + } + + return false; +} + +DapRpcMessage DapRpcMessage::fromJson(const QByteArray &aData) +{ + DapRpcMessage result; + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(aData, &error); + if (error.error != QJsonParseError::NoError) { + qJsonRpcDebug() << error.errorString(); + return result; + } + + if (!document.isObject()) { + qJsonRpcDebug() << "invalid message: " << aData; + return result; + } + + result.d->initializeWithObject(document.object()); + return result; +} + +DapRpcMessage DapRpcMessage::fromObject(const QJsonObject &aObject) +{ + DapRpcMessage result; + result.d->initializeWithObject(aObject); + return result; +} + +QJsonObject DapRpcMessage::toObject() const +{ + if (d->m_pObject) + return QJsonObject(*d->m_pObject); + return QJsonObject(); +} + +QByteArray DapRpcMessage::toJson() const +{ + if (d->m_pObject) { + QJsonDocument doc(*d->m_pObject); + return doc.toJson(); + } + + return QByteArray(); +} + +bool DapRpcMessage::isValid() const +{ + return d->m_type != DapRpcMessage::Invalid; +} + +DapRpcMessage::Type DapRpcMessage::type() const +{ + return d->m_type; +} + +DapRpcMessage DapRpcMessagePrivate::createBasicRequest(const QString &asMethod, const QJsonArray &aParams) +{ + DapRpcMessage request; + request.d->m_pObject->insert(QLatin1String("jsonrpc"), QLatin1String("2.0")); + request.d->m_pObject->insert(QLatin1String("method"), asMethod); + if (!aParams.isEmpty()) + request.d->m_pObject->insert(QLatin1String("params"), aParams); + return request; +} + +DapRpcMessage DapRpcMessagePrivate::createBasicRequest(const QString &asMethod, + const QJsonObject &aNamedParameters) +{ + DapRpcMessage request; + request.d->m_pObject->insert(QLatin1String("jsonrpc"), QLatin1String("2.0")); + request.d->m_pObject->insert(QLatin1String("method"), asMethod); + if (!aNamedParameters.isEmpty()) + request.d->m_pObject->insert(QLatin1String("params"), aNamedParameters); + return request; +} + +DapRpcMessage DapRpcMessage::createRequest(const QString &asMethod, const QJsonArray &aParams) +{ + DapRpcMessage request = DapRpcMessagePrivate::createBasicRequest(asMethod, aParams); + request.d->m_type = DapRpcMessage::Request; + DapRpcMessagePrivate::uniqueRequestCounter++; + request.d->m_pObject->insert(QLatin1String("id"), DapRpcMessagePrivate::uniqueRequestCounter); + return request; +} + +DapRpcMessage DapRpcMessage::createRequest(const QString &asMethod, const QJsonValue &aParam) +{ + QJsonArray params; + params.append(aParam); + return createRequest(asMethod, params); +} + +DapRpcMessage DapRpcMessage::createRequest(const QString &asMethod, + const QJsonObject &aNamedParameters) +{ + DapRpcMessage request = + DapRpcMessagePrivate::createBasicRequest(asMethod, aNamedParameters); + request.d->m_type = DapRpcMessage::Request; + DapRpcMessagePrivate::uniqueRequestCounter++; + request.d->m_pObject->insert(QLatin1String("id"), DapRpcMessagePrivate::uniqueRequestCounter); + return request; +} + +DapRpcMessage DapRpcMessage::createNotification(const QString &asMethod, const QJsonArray &aParams) +{ + DapRpcMessage notification = DapRpcMessagePrivate::createBasicRequest(asMethod, aParams); + notification.d->m_type = DapRpcMessage::Notification; + return notification; +} + +DapRpcMessage DapRpcMessage::createNotification(const QString &asMethod, const QJsonValue &aParam) +{ + QJsonArray params; + params.append(aParam); + return createNotification(asMethod, params); +} + +DapRpcMessage DapRpcMessage::createNotification(const QString &asMethod, + const QJsonObject &aNamedParameters) +{ + DapRpcMessage notification = + DapRpcMessagePrivate::createBasicRequest(asMethod, aNamedParameters); + notification.d->m_type = DapRpcMessage::Notification; + return notification; +} + +DapRpcMessage DapRpcMessage::createResponse(const QJsonValue &aResult) const +{ + DapRpcMessage response; + if (d->m_pObject->contains(QLatin1String("id"))) { + QJsonObject *object = response.d->m_pObject.data(); + object->insert(QLatin1String("jsonrpc"), QLatin1String("2.0")); + object->insert(QLatin1String("id"), d->m_pObject->value(QLatin1String("id"))); + object->insert(QLatin1String("result"), aResult); + response.d->m_type = DapRpcMessage::Response; + } + + return response; +} + +DapRpcMessage DapRpcMessage::createErrorResponse(DapErrorCode aCode, + const QString &asMessage, + const QJsonValue &aData) const +{ + DapRpcMessage response; + QJsonObject error; + error.insert(QLatin1String("code"), aCode); + if (!asMessage.isEmpty()) + error.insert(QLatin1String("message"), asMessage); + if (!aData.isUndefined()) + error.insert(QLatin1String("data"), aData); + + response.d->m_type = DapRpcMessage::Error; + QJsonObject *object = response.d->m_pObject.data(); + object->insert(QLatin1String("jsonrpc"), QLatin1String("2.0")); + if (d->m_pObject->contains(QLatin1String("id"))) + object->insert(QLatin1String("id"), d->m_pObject->value(QLatin1String("id"))); + else + object->insert(QLatin1String("id"), 0); + object->insert(QLatin1String("error"), error); + return response; +} + +int DapRpcMessage::id() const +{ + if (d->m_type == DapRpcMessage::Notification || !d->m_pObject) + return -1; + + const QJsonValue &value = d->m_pObject->value(QLatin1String("id")); + if (value.isString()) + return value.toString().toInt(); + return value.toInt(); +} + +QString DapRpcMessage::method() const +{ + if (d->m_type == DapRpcMessage::Response || !d->m_pObject) + return QString(); + + return d->m_pObject->value(QLatin1String("method")).toString(); +} + +QJsonValue DapRpcMessage::params() const +{ + if (d->m_type == DapRpcMessage::Response || d->m_type == DapRpcMessage::Error) + return QJsonValue(QJsonValue::Undefined); + if (!d->m_pObject) + return QJsonValue(QJsonValue::Undefined); + + return d->m_pObject->value(QLatin1String("params")); +} + +QJsonValue DapRpcMessage::result() const +{ + if (d->m_type != DapRpcMessage::Response || !d->m_pObject) + return QJsonValue(QJsonValue::Undefined); + + return d->m_pObject->value(QLatin1String("result")); +} + +int DapRpcMessage::errorCode() const +{ + if (d->m_type != DapRpcMessage::Error || !d->m_pObject) + return 0; + + QJsonObject error = + d->m_pObject->value(QLatin1String("error")).toObject(); + const QJsonValue &value = error.value(QLatin1String("code")); + if (value.isString()) + return value.toString().toInt(); + return value.toInt(); +} + +QString DapRpcMessage::errorMessage() const +{ + if (d->m_type != DapRpcMessage::Error || !d->m_pObject) + return QString(); + + QJsonObject error = + d->m_pObject->value(QLatin1String("error")).toObject(); + return error.value(QLatin1String("message")).toString(); +} + +QJsonValue DapRpcMessage::errorData() const +{ + if (d->m_type != DapRpcMessage::Error || !d->m_pObject) + return QJsonValue(QJsonValue::Undefined); + + QJsonObject error = + d->m_pObject->value(QLatin1String("error")).toObject(); + return error.value(QLatin1String("data")); +} + +static QDebug operator<<(QDebug dbg, DapRpcMessage::Type type) +{ + switch (type) { + case DapRpcMessage::Request: + return dbg << "DapRpcMessage::Request"; + case DapRpcMessage::Response: + return dbg << "DapRpcMessage::Response"; + case DapRpcMessage::Notification: + return dbg << "DapRpcMessage::Notification"; + case DapRpcMessage::Error: + return dbg << "DapRpcMessage::Error"; + default: + return dbg << "DapRpcMessage::Invalid"; + } +} + +QDebug operator<<(QDebug dbg, const DapRpcMessage &msg) +{ + dbg.nospace() << "DapRpcMessage(type=" << msg.type(); + if (msg.type() != DapRpcMessage::Notification) { + dbg.nospace() << ", id=" << msg.id(); + } + + if (msg.type() == DapRpcMessage::Request || + msg.type() == DapRpcMessage::Notification) { + dbg.nospace() << ", method=" << msg.method() + << ", params=" << msg.params(); + } else if (msg.type() == DapRpcMessage::Response) { + dbg.nospace() << ", result=" << msg.result(); + } else if (msg.type() == DapRpcMessage::Error) { + dbg.nospace() << ", code=" << msg.errorCode() + << ", message=" << msg.errorMessage() + << ", data=" << msg.errorData(); + } + dbg.nospace() << ")"; + return dbg.space(); +} diff --git a/DapRPCProtocol/DapRpcMessage.h b/DapRPCProtocol/DapRpcMessage.h new file mode 100644 index 0000000000000000000000000000000000000000..92b47aaa6648a9b84b656d7c621efb163c61cb77 --- /dev/null +++ b/DapRPCProtocol/DapRpcMessage.h @@ -0,0 +1,96 @@ +#ifndef DapRPCMESSAGE_H +#define DapRPCMESSAGE_H + +#include <QSharedDataPointer> +#include <QMetaType> +#include <QJsonDocument> +#include <QJsonValue> +#include <QJsonObject> +#include <QJsonArray> +#include <QDebug> + +#define qJsonRpcDebug if (qgetenv("QJSONRPC_DEBUG").isEmpty()); else qDebug + +enum DapErrorCode { + NoError = 0, + ParseError = -32700, // Invalid JSON was received by the server. + // An error occurred on the server while parsing the JSON text. + InvalidRequest = -32600, // The JSON sent is not a valid Request object. + MethodNotFound = -32601, // The method does not exist / is not available. + InvalidParams = -32602, // Invalid method parameter(s). + InternalError = -32603, // Internal JSON-RPC error. + ServerErrorBase = -32000, // Reserved for implementation-defined server-errors. + UserError = -32099, // Anything after this is user defined + TimeoutError = -32100 +}; +Q_DECLARE_METATYPE(DapErrorCode) + +class DapRpcMessagePrivate; +class DapRpcMessage +{ + friend class DapRpcMessagePrivate; + QSharedDataPointer<DapRpcMessagePrivate> d; + +public: + DapRpcMessage(); + DapRpcMessage(const DapRpcMessage &aDapRPCMessage); + DapRpcMessage &operator=(const DapRpcMessage &aDapRPCMessage); + ~DapRpcMessage(); + + inline void swap(DapRpcMessage &aDapRPCMessage) { qSwap(d, aDapRPCMessage.d); } + + enum Type { + Invalid, + Request, + Response, + Notification, + Error + }; + + static DapRpcMessage createRequest(const QString &asMethod, + const QJsonArray &aParams = QJsonArray()); + static DapRpcMessage createRequest(const QString &asMethod, const QJsonValue &aParam); + static DapRpcMessage createRequest(const QString &asMethod, const QJsonObject &aNamedParameters); + + static DapRpcMessage createNotification(const QString &asMethod, + const QJsonArray &aParams = QJsonArray()); + static DapRpcMessage createNotification(const QString &asMethod, const QJsonValue &aParam); + static DapRpcMessage createNotification(const QString &asMethod, + const QJsonObject &aNamedParameters); + + DapRpcMessage createResponse(const QJsonValue &aResult) const; + DapRpcMessage createErrorResponse(DapErrorCode aCode, + const QString &asMessage = QString(), + const QJsonValue &aData = QJsonValue()) const; + + DapRpcMessage::Type type() const; + bool isValid() const; + int id() const; + + // request + QString method() const; + QJsonValue params() const; + + // response + QJsonValue result() const; + + // error + int errorCode() const; + QString errorMessage() const; + QJsonValue errorData() const; + + QJsonObject toObject() const; + static DapRpcMessage fromObject(const QJsonObject &aObject); + + QByteArray toJson() const; + static DapRpcMessage fromJson(const QByteArray &aData); + + bool operator==(const DapRpcMessage &aDapRpcMessage) const; + inline bool operator!=(const DapRpcMessage &aDapRpcMessage) const { return !(operator==(aDapRpcMessage)); } +}; + +QDebug operator<<(QDebug, const DapRpcMessage &); +Q_DECLARE_METATYPE(DapRpcMessage) +Q_DECLARE_SHARED(DapRpcMessage) + +#endif // DapRPCMESSAGE_H diff --git a/DapRPCProtocol/DapRpcService.cpp b/DapRPCProtocol/DapRpcService.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f52ce29db5812e3c3cddcffca0c209ec93c5e480 --- /dev/null +++ b/DapRPCProtocol/DapRpcService.cpp @@ -0,0 +1,340 @@ +#include "DapRpcService.h" +#include "DapRpcSocket.h" + +ParameterInfo::ParameterInfo(const QString &asName, int aType, bool aOut) + : type(aType), + jsType(DapRpcService::convertVariantTypeToJSType(aType)), + name(asName), + out(aOut) +{ +} + +MethodInfo::MethodInfo() + : returnType(QMetaType::Void), + valid(false), + hasOut(false) +{ +} + +MethodInfo::MethodInfo(const QMetaMethod &aMethod) + : returnType(QMetaType::Void), + valid(true), + hasOut(false) +{ + returnType = aMethod.returnType(); + if (returnType == QMetaType::UnknownType) { + qJsonRpcDebug() << "DapRpcService: can't bind method's return type" + << QString(aMethod.name()); + valid = false; + return; + } + + parameters.reserve(aMethod.parameterCount()); + + const QList<QByteArray> &types = aMethod.parameterTypes(); + const QList<QByteArray> &names = aMethod.parameterNames(); + for (int i = 0; i < types.size(); ++i) { + QByteArray parameterType = types.at(i); + const QByteArray ¶meterName = names.at(i); + bool out = parameterType.endsWith('&'); + + if (out) { + hasOut = true; + parameterType.resize(parameterType.size() - 1); + } + + int type = QMetaType::type(parameterType); + if (type == 0) { + qJsonRpcDebug() << "DapRpcService: can't bind method's parameter" + << QString(parameterType); + valid = false; + break; + } + + parameters.append(ParameterInfo(parameterName, type, out)); + } +} + +void DapRpcService::setCurrentRequest(const DapRpcServiceRequest &aCurrentRequest) +{ + m_currentRequest = aCurrentRequest; +} + +DapRpcService::DapRpcService(QObject *apParent) + : QObject(apParent) +{ +} + +DapRpcService::~DapRpcService() +{ +} + +DapRpcServiceRequest DapRpcService::currentRequest() const +{ + return m_currentRequest; +} + +void DapRpcService::beginDelayedResponse() +{ + m_delayedResponse = true; +} + +int DapRpcService::convertVariantTypeToJSType(int aType) +{ + switch (aType) { + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::Double: + case QMetaType::Long: + case QMetaType::LongLong: + case QMetaType::Short: + case QMetaType::Char: + case QMetaType::ULong: + case QMetaType::ULongLong: + case QMetaType::UShort: + case QMetaType::UChar: + case QMetaType::Float: + return QJsonValue::Double; // all numeric types in js are doubles + case QMetaType::QVariantList: + case QMetaType::QStringList: + return QJsonValue::Array; + case QMetaType::QVariantMap: + return QJsonValue::Object; + case QMetaType::QString: + return QJsonValue::String; + case QMetaType::Bool: + return QJsonValue::Bool; + default: + break; + } + + return QJsonValue::Undefined; +} + +int DapRpcService::qjsonRpcMessageType = qRegisterMetaType<DapRpcMessage>("DapRpcMessage"); +void DapRpcService::cacheInvokableInfo() +{ + const QMetaObject *obj = metaObject(); + int startIdx = staticMetaObject.methodCount(); // skip QObject slots + for (int idx = startIdx; idx < obj->methodCount(); ++idx) { + const QMetaMethod method = obj->method(idx); + if ((method.methodType() == QMetaMethod::Slot && + method.access() == QMetaMethod::Public) || + method.methodType() == QMetaMethod::Signal) { + + QByteArray signature = method.methodSignature(); + QByteArray methodName = method.name(); + + MethodInfo info(method); + if (!info.valid) + continue; + + if (signature.contains("QVariant")) + m_invokableMethodHash[methodName].append(idx); + else + m_invokableMethodHash[methodName].prepend(idx); + m_methodInfoHash[idx] = info; + } + } +} + +static bool jsParameterCompare(const QJsonArray ¶meters, + const MethodInfo &info) +{ + int j = 0; + for (int i = 0; i < info.parameters.size() && j < parameters.size(); ++i) { + int jsType = info.parameters.at(i).jsType; + if (jsType != QJsonValue::Undefined && jsType != parameters.at(j).type()) { + if (!info.parameters.at(i).out) + return false; + } else { + ++j; + } + } + + return (j == parameters.size()); +} + +static bool jsParameterCompare(const QJsonObject ¶meters, + const MethodInfo &info) +{ + for (int i = 0; i < info.parameters.size(); ++i) { + int jsType = info.parameters.at(i).jsType; + QJsonValue value = parameters.value(info.parameters.at(i).name); + if (value == QJsonValue::Undefined) { + if (!info.parameters.at(i).out) + return false; + } else if (jsType == QJsonValue::Undefined) { + continue; + } else if (jsType != value.type()) { + return false; + } + } + + return true; +} + +static inline QVariant convertArgument(const QJsonValue &argument, + const ParameterInfo &info) +{ + if (argument.isUndefined()) + return QVariant(info.type, Q_NULLPTR); + + if (info.type == QMetaType::QJsonValue || info.type == QMetaType::QVariant || + info.type >= QMetaType::User) { + + if (info.type == QMetaType::QVariant) + return argument.toVariant(); + + QVariant result(argument); + if (info.type >= QMetaType::User && result.canConvert(info.type)) + result.convert(info.type); + return result; + } + + QVariant result = argument.toVariant(); + if (result.userType() == info.type || info.type == QMetaType::QVariant) { + return result; + } else if (result.canConvert(info.type)) { + result.convert(info.type); + return result; + } else if (info.type < QMetaType::User) { + // already tried for >= user, this is the last resort + QVariant result(argument); + if (result.canConvert(info.type)) { + result.convert(info.type); + return result; + } + } + + return QVariant(); +} + +QJsonValue DapRpcService::convertReturnValue(QVariant &aReturnValue) +{ + if (static_cast<int>(aReturnValue.type()) == qMetaTypeId<QJsonObject>()) + return QJsonValue(aReturnValue.toJsonObject()); + else if (static_cast<int>(aReturnValue.type()) == qMetaTypeId<QJsonArray>()) + return QJsonValue(aReturnValue.toJsonArray()); + + switch (aReturnValue.type()) { + case QMetaType::Bool: + case QMetaType::Int: + case QMetaType::Double: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::UInt: + case QMetaType::QString: + case QMetaType::QStringList: + case QMetaType::QVariantList: + case QMetaType::QVariantMap: + return QJsonValue::fromVariant(aReturnValue); + default: + // if a conversion operator was registered it will be used + if (aReturnValue.convert(QMetaType::QJsonValue)) + return aReturnValue.toJsonValue(); + else + return QJsonValue(); + } +} + +static inline QByteArray methodName(const DapRpcMessage &request) +{ + const QString &methodPath(request.method()); + return methodPath.midRef(methodPath.lastIndexOf('.') + 1).toLatin1(); +} + +DapRpcMessage DapRpcService::dispatch(const DapRpcMessage &aRequest) +{ + if (aRequest.type() != DapRpcMessage::Request && + aRequest.type() != DapRpcMessage::Notification) { + return aRequest.createErrorResponse(DapErrorCode::InvalidRequest, "invalid request"); + } + + const QByteArray &method(methodName(aRequest)); + if (!m_invokableMethodHash.contains(method)) { + return aRequest.createErrorResponse(DapErrorCode::MethodNotFound, "invalid method called"); + } + + int idx = -1; + QVariantList arguments; + const QList<int> &indexes = m_invokableMethodHash.value(method); + const QJsonValue ¶ms = aRequest.params(); + QVarLengthArray<void *, 10> parameters; + QVariant returnValue; + QMetaType::Type returnType = QMetaType::Void; + + bool usingNamedParameters = params.isObject(); + foreach (int methodIndex, indexes) { + MethodInfo &info = m_methodInfoHash[methodIndex]; + bool methodMatch = usingNamedParameters ? + jsParameterCompare(params.toObject(), info) : + jsParameterCompare(params.toArray(), info); + + if (methodMatch) { + idx = methodIndex; + arguments.reserve(info.parameters.size()); + returnType = static_cast<QMetaType::Type>(info.returnType); + returnValue = (returnType == QMetaType::Void) ? + QVariant() : QVariant(returnType, Q_NULLPTR); + if (returnType == QMetaType::QVariant) + parameters.append(&returnValue); + else + parameters.append(returnValue.data()); + + for (int i = 0; i < info.parameters.size(); ++i) { + const ParameterInfo ¶meterInfo = info.parameters.at(i); + QJsonValue incomingArgument = usingNamedParameters ? + params.toObject().value(parameterInfo.name) : + params.toArray().at(i); + + QVariant argument = convertArgument(incomingArgument, parameterInfo); + if (!argument.isValid()) { + QString message = incomingArgument.isUndefined() ? + QString("failed to construct default object for '%1'").arg(parameterInfo.name) : + QString("failed to convert from JSON for '%1'").arg(parameterInfo.name); + return aRequest.createErrorResponse(DapErrorCode::InvalidParams, message); + } + + arguments.push_back(argument); + if (parameterInfo.type == QMetaType::QVariant) + parameters.append(static_cast<void *>(&arguments.last())); + else + parameters.append(const_cast<void *>(arguments.last().constData())); + } + break; + } + } + + if (idx == -1) { + return aRequest.createErrorResponse(DapErrorCode::InvalidParams, "invalid parameters"); + } + + MethodInfo &info = m_methodInfoHash[idx]; + + bool success = + const_cast<DapRpcService*>(this)->qt_metacall(QMetaObject::InvokeMetaMethod, idx, parameters.data()) < 0; + if (!success) { + QString message = QString("dispatch for method '%1' failed").arg(method.constData()); + return aRequest.createErrorResponse(DapErrorCode::InvalidRequest, message); + } + + if (m_delayedResponse) { + m_delayedResponse = false; + return DapRpcMessage(); + } + + if (info.hasOut) { + QJsonArray ret; + if (info.returnType != QMetaType::Void) + ret.append(convertReturnValue(returnValue)); + for (int i = 0; i < info.parameters.size(); ++i) + if (info.parameters.at(i).out) + ret.append(convertReturnValue(arguments[i])); + if (ret.size() > 1) + return aRequest.createResponse(ret); + return aRequest.createResponse(ret.first()); + } + + return aRequest.createResponse(convertReturnValue(returnValue)); +} diff --git a/DapRPCProtocol/DapRpcService.h b/DapRPCProtocol/DapRpcService.h new file mode 100644 index 0000000000000000000000000000000000000000..c13786febbeeddf22ae5d4257c4b3add6353348e --- /dev/null +++ b/DapRPCProtocol/DapRpcService.h @@ -0,0 +1,72 @@ +#ifndef DapRPCSERVICE_H +#define DapRPCSERVICE_H + +#include <QObject> +#include <QVariant> +#include <QPointer> +#include <QVarLengthArray> +#include <QMetaMethod> +#include <QEventLoop> +#include <QDebug> + + +#include "DapRpcMessage.h" +#include "DapRpcServiceRequest.h" + +struct ParameterInfo +{ + ParameterInfo(const QString &asName = QString(), int aType = 0, bool aOut = false); + + int type; + int jsType; + QString name; + bool out; +}; + +struct MethodInfo +{ + MethodInfo(); + MethodInfo(const QMetaMethod &aMethod); + + QVarLengthArray<ParameterInfo> parameters; + int returnType; + bool valid; + bool hasOut; +}; + +class DapRpcService : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(DapRpcService) + + QHash<int, MethodInfo > m_methodInfoHash; + QHash<QByteArray, QList<int> > m_invokableMethodHash; + DapRpcServiceRequest m_currentRequest; + bool m_delayedResponse {false}; + +protected: + DapRpcServiceRequest currentRequest() const; + void beginDelayedResponse(); + +public: + explicit DapRpcService(QObject *apParent = nullptr); + ~DapRpcService(); + + void cacheInvokableInfo(); + static int qjsonRpcMessageType; + static int convertVariantTypeToJSType(int aType); + static QJsonValue convertReturnValue(QVariant &aReturnValue); + + void setCurrentRequest(const DapRpcServiceRequest &aCurrentRequest); + +signals: + void result(const DapRpcMessage &aDapRpcMessage); + void notifyConnectedClients(const DapRpcMessage &aDapRpcMessage); + void notifyConnectedClients(const QString &asMethod, const QJsonArray &aParams = QJsonArray()); + +public slots: + DapRpcMessage dispatch(const DapRpcMessage &aRequest); +}; + +#endif // DapRPCSERVICE_H + diff --git a/DapRPCProtocol/DapRpcServiceProvider.cpp b/DapRPCProtocol/DapRpcServiceProvider.cpp new file mode 100644 index 0000000000000000000000000000000000000000..106e9fe61c693f896dc18f60d03362a22406ecd6 --- /dev/null +++ b/DapRPCProtocol/DapRpcServiceProvider.cpp @@ -0,0 +1,93 @@ +#include "DapRpcServiceProvider.h" +#include "DapRpcSocket.h" + +DapRpcServiceProvider::DapRpcServiceProvider() +{ +} + +DapRpcServiceProvider::~DapRpcServiceProvider() +{ +} + +QByteArray DapRpcServiceProvider::getServiceName(DapRpcService *apService) +{ + const QMetaObject *mo = apService->metaObject(); + for (int i = 0; i < mo->classInfoCount(); i++) { + const QMetaClassInfo mci = mo->classInfo(i); + if (mci.name() == QLatin1String("serviceName")) + return mci.value(); + } + + return QByteArray(mo->className()).toLower(); +} + +bool DapRpcServiceProvider::addService(DapRpcService *apService) +{ + QByteArray serviceName = getServiceName(apService); + if (serviceName.isEmpty()) { + qJsonRpcDebug() << "service added without serviceName classinfo, aborting"; + return false; + } + + if (m_services.contains(serviceName)) { + qJsonRpcDebug() << "service with name " << serviceName << " already exist"; + return false; + } + + apService->cacheInvokableInfo(); + m_services.insert(serviceName, apService); + if (!apService->parent()) + m_cleanupHandler.add(apService); + return true; +} + +bool DapRpcServiceProvider::removeService(DapRpcService *apService) +{ + QByteArray serviceName = getServiceName(apService); + if (!m_services.contains(serviceName)) { + qJsonRpcDebug() << "can not find service with name " << serviceName; + return false; + } + + m_cleanupHandler.remove(m_services.value(serviceName)); + m_services.remove(serviceName); + return true; +} + +void DapRpcServiceProvider::processMessage(DapRpcSocket *apSocket, const DapRpcMessage &aMessage) +{ + switch (aMessage.type()) { + case DapRpcMessage::Request: + case DapRpcMessage::Notification: { + QByteArray serviceName = aMessage.method().section(".", 0, -2).toLatin1(); + if (serviceName.isEmpty() || !m_services.contains(serviceName)) { + if (aMessage.type() == DapRpcMessage::Request) { + DapRpcMessage error = + aMessage.createErrorResponse(DapErrorCode::MethodNotFound, + QString("service '%1' not found").arg(serviceName.constData())); + apSocket->notify(error); + } + } else { + DapRpcService *service = m_services.value(serviceName); + service->setCurrentRequest(DapRpcServiceRequest(aMessage, apSocket)); + if (aMessage.type() == DapRpcMessage::Request) + QObject::connect(service, SIGNAL(result(DapRpcMessage)), + apSocket, SLOT(notify(DapRpcMessage)), Qt::UniqueConnection); + DapRpcMessage response = service->dispatch(aMessage); + if (response.isValid()) + apSocket->notify(response); + } + } + break; + + case DapRpcMessage::Response: + break; + + default: { + DapRpcMessage error = + aMessage.createErrorResponse(DapErrorCode::InvalidRequest, QString("invalid request")); + apSocket->notify(error); + break; + } + }; +} diff --git a/DapRPCProtocol/DapRpcServiceProvider.h b/DapRPCProtocol/DapRpcServiceProvider.h new file mode 100644 index 0000000000000000000000000000000000000000..6e74d30c0027397fc8616ae964305a924604c1b8 --- /dev/null +++ b/DapRPCProtocol/DapRpcServiceProvider.h @@ -0,0 +1,29 @@ +#ifndef DapRPCSERVICEPROVIDER_H +#define DapRPCSERVICEPROVIDER_H + +#include <QScopedPointer> +#include <QObjectCleanupHandler> +#include <QHash> +#include <QMetaObject> +#include <QMetaClassInfo> +#include <QDebug> + +#include "DapRpcService.h" + +class DapRpcServiceProvider +{ + QHash<QByteArray, DapRpcService*> m_services; + QObjectCleanupHandler m_cleanupHandler; + +protected: + DapRpcServiceProvider(); + void processMessage(DapRpcSocket *apSocket, const DapRpcMessage &aMessage); + +public: + virtual ~DapRpcServiceProvider(); + virtual bool addService(DapRpcService *apService); + virtual bool removeService(DapRpcService *apService); + QByteArray getServiceName(DapRpcService *apService); +}; + +#endif // DapRPCSERVICEPROVIDER_H diff --git a/DapRPCProtocol/DapRpcServiceReply.cpp b/DapRPCProtocol/DapRpcServiceReply.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ba26f389bbad1cde1d328fbe6ea12d6a1e1e70e8 --- /dev/null +++ b/DapRPCProtocol/DapRpcServiceReply.cpp @@ -0,0 +1,31 @@ +#include "DapRpcServiceReply.h" + +DapRpcServiceReply::DapRpcServiceReply(QObject *apParent) + : QObject(apParent) +{ +} + +DapRpcServiceReply::~DapRpcServiceReply() +{ + +} + +void DapRpcServiceReply::setRequest(const DapRpcMessage &aRequest) +{ + m_request = aRequest; +} + +void DapRpcServiceReply::setResponse(const DapRpcMessage &aResponse) +{ + m_response = aResponse; +} + +DapRpcMessage DapRpcServiceReply::request() const +{ + return m_request; +} + +DapRpcMessage DapRpcServiceReply::response() const +{ + return m_response; +} diff --git a/DapRPCProtocol/DapRpcServiceReply.h b/DapRPCProtocol/DapRpcServiceReply.h new file mode 100644 index 0000000000000000000000000000000000000000..2916114cb7149e1a680373725af698e3a82a02bb --- /dev/null +++ b/DapRPCProtocol/DapRpcServiceReply.h @@ -0,0 +1,30 @@ +#ifndef DapRPCSERVICEREPLY_H +#define DapRPCSERVICEREPLY_H + +#include <QObject> +#include <QNetworkReply> + +#include "DapRpcMessage.h" + +class DapRpcServiceReply : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(DapRpcServiceReply) + DapRpcMessage m_request; + DapRpcMessage m_response; + +public: + explicit DapRpcServiceReply(QObject *apParent = nullptr); + virtual ~DapRpcServiceReply(); + + DapRpcMessage request() const; + DapRpcMessage response() const; + + void setRequest(const DapRpcMessage &aRequest); + void setResponse(const DapRpcMessage &aResponse); + +signals: + void finished(); +}; + +#endif // DapRPCSERVICEREPLY_H diff --git a/DapRPCProtocol/DapRpcServiceRequest.cpp b/DapRPCProtocol/DapRpcServiceRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3a00a07b5b8d984bea7e0f74f0182eca4886b364 --- /dev/null +++ b/DapRPCProtocol/DapRpcServiceRequest.cpp @@ -0,0 +1,69 @@ +#include "DapRpcSocket.h" +#include "DapRpcServiceRequest.h" + + +DapRpcServiceRequest::DapRpcServiceRequest() +{ +} + +DapRpcServiceRequest::~DapRpcServiceRequest() +{ +} + +DapRpcServiceRequest::DapRpcServiceRequest(const DapRpcServiceRequest &aDapRpcServiceRequest) +{ + m_request = aDapRpcServiceRequest.m_request; + m_socket = aDapRpcServiceRequest.m_socket; +} + +DapRpcServiceRequest::DapRpcServiceRequest(const DapRpcMessage &aRequest, + DapRpcSocket *apSocket) +{ + m_request = aRequest; + m_socket = apSocket; +} + +DapRpcServiceRequest &DapRpcServiceRequest::operator=(const DapRpcServiceRequest &other) +{ + m_request = other.m_request; + m_socket = other.m_socket; + return *this; +} + +bool DapRpcServiceRequest::isValid() const +{ + return (m_request.isValid() && !m_socket.isNull()); +} + +DapRpcMessage DapRpcServiceRequest::request() const +{ + return m_request; +} + +DapRpcSocket *DapRpcServiceRequest::socket() const +{ + return m_socket; +} + +bool DapRpcServiceRequest::respond(QVariant aReturnValue) +{ + if (!m_socket) { + qJsonRpcDebug() << "socket was closed"; + return false; + } + + DapRpcMessage response = + m_request.createResponse(DapRpcService::convertReturnValue(aReturnValue)); + return respond(response); +} + +bool DapRpcServiceRequest::respond(const DapRpcMessage &aResponse) +{ + if (!m_socket) { + qJsonRpcDebug() << "socket was closed"; + return false; + } + + QMetaObject::invokeMethod(m_socket, "notify", Q_ARG(DapRpcMessage, aResponse)); + return true; +} diff --git a/DapRPCProtocol/DapRpcServiceRequest.h b/DapRPCProtocol/DapRpcServiceRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..aa689f8d143c761586c4808ead96f01c885ee427 --- /dev/null +++ b/DapRPCProtocol/DapRpcServiceRequest.h @@ -0,0 +1,31 @@ +#ifndef DapRPCSERVICEREQUEST_H +#define DapRPCSERVICEREQUEST_H + +#include <QPointer> +#include <QMetaObject> +#include <QDebug> + +#include "DapRpcMessage.h" + +class DapRpcSocket; +class DapRpcServiceRequest +{ + DapRpcMessage m_request; + QPointer<DapRpcSocket> m_socket; + +public: + DapRpcServiceRequest(); + DapRpcServiceRequest(const DapRpcServiceRequest &aDapRpcServiceRequest); + DapRpcServiceRequest(const DapRpcMessage &aRequest, DapRpcSocket *apSocket); + DapRpcServiceRequest &operator=(const DapRpcServiceRequest &aDapRpcServiceRequest); + ~DapRpcServiceRequest(); + + bool isValid() const; + DapRpcMessage request() const; + DapRpcSocket *socket() const; + + bool respond(const DapRpcMessage &aResponse); + bool respond(QVariant aReturnValue); +}; + +#endif // DapRPCSERVICEREQUEST_H diff --git a/DapRPCProtocol/DapRpcSocket.cpp b/DapRPCProtocol/DapRpcSocket.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d769b41a00f887bd376d04ec6379e9f95a1b5a9e --- /dev/null +++ b/DapRPCProtocol/DapRpcSocket.cpp @@ -0,0 +1,262 @@ +#include "DapRpcSocket.h" +#include "DapRpcService.h" + +DapRpcSocket::DapRpcSocket(QObject *apParent) + : QObject(apParent) +{ + +} + +DapRpcSocket::DapRpcSocket(QIODevice *apDevice, QObject *apParent) + : QObject(apParent) +{ + m_pDevice = apDevice; + connect(m_pDevice, SIGNAL(readyRead()), this, SLOT(processIncomingData())); +} + +DapRpcSocket::~DapRpcSocket() +{ +} + +int DapRpcSocket::findJsonDocumentEnd(const QByteArray &aJsonData) +{ + const char* pos = aJsonData.constData(); + const char* end = pos + aJsonData.length(); + + char blockStart = 0; + char blockEnd = 0; + int index = 0; + + while (true) { + if (pos == end) { + return -1; + } else if (*pos == '{') { + blockStart = '{'; + blockEnd = '}'; + break; + } else if(*pos == '[') { + blockStart = '['; + blockEnd = ']'; + break; + } + + pos++; + index++; + } + + pos++; + index++; + int depth = 1; + bool inString = false; + while (depth > 0 && pos <= end) { + if (*pos == '\\') { + pos += 2; + index += 2; + continue; + } else if (*pos == '"') { + inString = !inString; + } else if (!inString) { + if (*pos == blockStart) + depth++; + else if (*pos == blockEnd) + depth--; + } + + pos++; + index++; + } + + return depth == 0 ? index-1 : -1; +} + +void DapRpcSocket::writeData(const DapRpcMessage &asMessage) +{ + QJsonDocument doc = QJsonDocument(asMessage.toObject()); + QByteArray data = doc.toJson(QJsonDocument::Compact); + + m_pDevice.data()->write(data); + qJsonRpcDebug() << "sending: " << data; +} + +void DapRpcSocket::setDefaultRequestTimeout(int aiMsecs) +{ + if (aiMsecs < 0) { + qJsonRpcDebug() << "Cannot set a negative request timeout msecs value"; + return; + } + + m_defaultRequestTimeout = aiMsecs; +} + +int DapRpcSocket::getDefaultRequestTimeout() const +{ + return m_defaultRequestTimeout; +} + +bool DapRpcSocket::isValid() const +{ + return m_pDevice && m_pDevice.data()->isOpen(); +} + +DapRpcMessage DapRpcSocket::sendMessageBlocking(const DapRpcMessage &asMessage, int aMsecs) +{ + DapRpcServiceReply *reply = sendMessage(asMessage); + QScopedPointer<DapRpcServiceReply> replyPtr(reply); + + QEventLoop responseLoop; + connect(reply, SIGNAL(finished()), &responseLoop, SLOT(quit())); + QTimer::singleShot(aMsecs, &responseLoop, SLOT(quit())); + responseLoop.exec(); + + if (!reply->response().isValid()) { + m_replies.remove(asMessage.id()); + return asMessage.createErrorResponse(DapErrorCode::TimeoutError, "request timed out"); + } + + return reply->response(); +} + +DapRpcServiceReply *DapRpcSocket::sendMessage(const DapRpcMessage &asMessage) +{ + if (!m_pDevice) { + qJsonRpcDebug() << "trying to send message without device"; + return nullptr; + } + + notify(asMessage); + QPointer<DapRpcServiceReply> reply(new DapRpcServiceReply); + reply->setRequest(asMessage); + m_replies.insert(asMessage.id(), reply); + return reply; +} + +void DapRpcSocket::notify(const DapRpcMessage &asMessage) +{ + if (!m_pDevice) { + qJsonRpcDebug() << "trying to send message without device"; + return; + } + + DapRpcService *service = qobject_cast<DapRpcService*>(sender()); + if (service) + disconnect(service, SIGNAL(result(DapRpcMessage)), this, SLOT(notify(DapRpcMessage))); + + writeData(asMessage); +} + +DapRpcMessage DapRpcSocket::invokeRemoteMethodBlocking(const QString &asMethod, int aMsecs, const QVariant ¶m1, + const QVariant ¶m2, const QVariant ¶m3, + const QVariant ¶m4, const QVariant ¶m5, + const QVariant ¶m6, const QVariant ¶m7, + const QVariant ¶m8, const QVariant ¶m9, + const QVariant ¶m10) +{ + QVariantList params; + if (param1.isValid()) params.append(param1); + if (param2.isValid()) params.append(param2); + if (param3.isValid()) params.append(param3); + if (param4.isValid()) params.append(param4); + if (param5.isValid()) params.append(param5); + if (param6.isValid()) params.append(param6); + if (param7.isValid()) params.append(param7); + if (param8.isValid()) params.append(param8); + if (param9.isValid()) params.append(param9); + if (param10.isValid()) params.append(param10); + + DapRpcMessage request = + DapRpcMessage::createRequest(asMethod, QJsonArray::fromVariantList(params)); + return sendMessageBlocking(request, aMsecs); +} + +DapRpcMessage DapRpcSocket::invokeRemoteMethodBlocking(const QString &asMethod, const QVariant ¶m1, + const QVariant ¶m2, const QVariant ¶m3, + const QVariant ¶m4, const QVariant ¶m5, + const QVariant ¶m6, const QVariant ¶m7, + const QVariant ¶m8, const QVariant ¶m9, + const QVariant ¶m10) +{ + return invokeRemoteMethodBlocking(asMethod, m_defaultRequestTimeout, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); +} + +DapRpcServiceReply *DapRpcSocket::invokeRemoteMethod(const QString &asMethod, const QVariant ¶m1, + const QVariant ¶m2, const QVariant ¶m3, + const QVariant ¶m4, const QVariant ¶m5, + const QVariant ¶m6, const QVariant ¶m7, + const QVariant ¶m8, const QVariant ¶m9, + const QVariant ¶m10) +{ + QVariantList params; + if (param1.isValid()) params.append(param1); + if (param2.isValid()) params.append(param2); + if (param3.isValid()) params.append(param3); + if (param4.isValid()) params.append(param4); + if (param5.isValid()) params.append(param5); + if (param6.isValid()) params.append(param6); + if (param7.isValid()) params.append(param7); + if (param8.isValid()) params.append(param8); + if (param9.isValid()) params.append(param9); + if (param10.isValid()) params.append(param10); + + DapRpcMessage request = + DapRpcMessage::createRequest(asMethod, QJsonArray::fromVariantList(params)); + return sendMessage(request); +} + +void DapRpcSocket::processIncomingData() +{ + if (!m_pDevice) { + qJsonRpcDebug() << "called without device"; + return; + } + + m_aBuffer.append(m_pDevice.data()->readAll()); + while (!m_aBuffer.isEmpty()) { + int dataSize = findJsonDocumentEnd(m_aBuffer); + if (dataSize == -1) { + return; + } + + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_aBuffer.mid(0, dataSize + 1), &error); + if (document.isEmpty()) { + if (error.error != QJsonParseError::NoError) { + qJsonRpcDebug() << error.errorString(); + } + + break; + } + + m_aBuffer = m_aBuffer.mid(dataSize + 1); + if (document.isArray()) { + qJsonRpcDebug() << "bulk support is current disabled"; + } else if (document.isObject()){ + qJsonRpcDebug() << "received: " << document.toJson(QJsonDocument::Compact); + DapRpcMessage message = DapRpcMessage::fromObject(document.object()); + Q_EMIT messageReceived(message); + + if (message.type() == DapRpcMessage::Response || + message.type() == DapRpcMessage::Error) { + if (m_replies.contains(message.id())) { + QPointer<DapRpcServiceReply> reply = m_replies.take(message.id()); + if (!reply.isNull()) { + reply->setResponse(message); + reply->finished(); + } + } + } else { + processRequestMessage(message); + } + } + } +} + +void DapRpcSocket::setIODevice(QIODevice *pDevice) +{ + m_pDevice = pDevice; + connect(m_pDevice, SIGNAL(readyRead()), this, SLOT(processIncomingData())); +} + +void DapRpcSocket::processRequestMessage(const DapRpcMessage &asMessage) +{ + Q_UNUSED(asMessage) +} diff --git a/DapRPCProtocol/DapRpcSocket.h b/DapRPCProtocol/DapRpcSocket.h new file mode 100644 index 0000000000000000000000000000000000000000..9a2836d9a52ed435d4b304b595c48297b7858153 --- /dev/null +++ b/DapRPCProtocol/DapRpcSocket.h @@ -0,0 +1,74 @@ +#ifndef DapRPCSOCKET_H +#define DapRPCSOCKET_H + +#include <QObject> +#include <QIODevice> +#include <QPointer> +#include <QTimer> +#include <QEventLoop> +#include <QDebug> +#include <QJsonDocument> + +#include "DapRpcServiceProvider.h" +#include "DapRpcMessage.h" +#include "DapRpcServiceReply.h" + +#define DEFAULT_MSECS_REQUEST_TIMEOUT (30000) + +class DapRpcSocket : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(DapRpcSocket) + + QPointer<QIODevice> m_pDevice; + QByteArray m_aBuffer; + QHash<int, QPointer<DapRpcServiceReply>> m_replies; + int m_defaultRequestTimeout; + +protected: + virtual void processRequestMessage(const DapRpcMessage &asMessage); + +public: + explicit DapRpcSocket(QObject *apParent = nullptr); + explicit DapRpcSocket(QIODevice *apDevice, QObject *apParent = nullptr); + virtual ~DapRpcSocket(); + + virtual bool isValid() const; + void setDefaultRequestTimeout(int aiMsecs); + int getDefaultRequestTimeout() const; + + void setIODevice(QIODevice *pDevice); + +signals: + void messageReceived(const DapRpcMessage &asMessage); + +private slots: + virtual void processIncomingData(); + int findJsonDocumentEnd(const QByteArray &aJsonData); + void writeData(const DapRpcMessage &asMessage); + +public slots: + virtual void notify(const DapRpcMessage &asMessage); + virtual DapRpcMessage sendMessageBlocking(const DapRpcMessage &asMessage, int aMsecs = DEFAULT_MSECS_REQUEST_TIMEOUT); + virtual DapRpcServiceReply *sendMessage(const DapRpcMessage &asMessage); + DapRpcMessage invokeRemoteMethodBlocking(const QString &asMethod, int aMsecs, const QVariant &arg1 = QVariant(), + const QVariant &arg2 = QVariant(), const QVariant &arg3 = QVariant(), + const QVariant &arg4 = QVariant(), const QVariant &arg5 = QVariant(), + const QVariant &arg6 = QVariant(), const QVariant &arg7 = QVariant(), + const QVariant &arg8 = QVariant(), const QVariant &arg9 = QVariant(), + const QVariant &arg10 = QVariant()); + DapRpcMessage invokeRemoteMethodBlocking(const QString &asMethod, const QVariant &arg1 = QVariant(), + const QVariant &arg2 = QVariant(), const QVariant &arg3 = QVariant(), + const QVariant &arg4 = QVariant(), const QVariant &arg5 = QVariant(), + const QVariant &arg6 = QVariant(), const QVariant &arg7 = QVariant(), + const QVariant &arg8 = QVariant(), const QVariant &arg9 = QVariant(), + const QVariant &arg10 = QVariant()); + DapRpcServiceReply *invokeRemoteMethod(const QString &asMethod, const QVariant &arg1 = QVariant(), + const QVariant &arg2 = QVariant(), const QVariant &arg3 = QVariant(), + const QVariant &arg4 = QVariant(), const QVariant &arg5 = QVariant(), + const QVariant &arg6 = QVariant(), const QVariant &arg7 = QVariant(), + const QVariant &arg8 = QVariant(), const QVariant &arg9 = QVariant(), + const QVariant &arg10 = QVariant()); +}; + +#endif // DapRPCSOCKET_H diff --git a/DapRPCProtocol/DapRpcTCPServer.cpp b/DapRPCProtocol/DapRpcTCPServer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c361b121cc85e8f40939f733403d5d4dc62e1a6 --- /dev/null +++ b/DapRPCProtocol/DapRpcTCPServer.cpp @@ -0,0 +1,110 @@ +#include "DapRpcTCPServer.h" + +DapRpcTCPServer::DapRpcTCPServer(QObject *apParent) + : QTcpServer(apParent) +{ + +} + +void DapRpcTCPServer::notifyConnectedClients(const DapRpcMessage &message) +{ + DapRpcAbstractServer::notifyConnectedClients(message); +} + +void DapRpcTCPServer::notifyConnectedClients(const QString &method, const QJsonArray ¶ms) +{ + DapRpcAbstractServer::notifyConnectedClients(method, params); +} + +DapRpcTCPServer::~DapRpcTCPServer() +{ + foreach (QTcpSocket *socket, m_socketLookup.keys()) { + socket->flush(); + socket->deleteLater(); + } + m_socketLookup.clear(); + + foreach (DapRpcSocket *client, m_clients) + client->deleteLater(); + m_clients.clear(); +} + +bool DapRpcTCPServer::listen(const QString &asAddress, quint16 aPort) +{ + return ((asAddress.isNull() || asAddress.isEmpty()) ? + QTcpServer::listen(QHostAddress::Any, aPort) : + QTcpServer::listen(QHostAddress(asAddress), aPort)); +} + +bool DapRpcTCPServer::addService(DapRpcService *apService) +{ + if (!DapRpcServiceProvider::addService(apService)) + return false; + + connect(apService, SIGNAL(notifyConnectedClients(DapRpcMessage)), + this, SLOT(notifyConnectedClients(DapRpcMessage))); + connect(apService, SIGNAL(notifyConnectedClients(QString,QJsonArray)), + this, SLOT(notifyConnectedClients(QString,QJsonArray))); + return true; +} + +bool DapRpcTCPServer::removeService(DapRpcService *apService) +{ + if (!DapRpcServiceProvider::removeService(apService)) + return false; + + disconnect(apService, SIGNAL(notifyConnectedClients(DapRpcMessage)), + this, SLOT(notifyConnectedClients(DapRpcMessage))); + disconnect(apService, SIGNAL(notifyConnectedClients(QString,QJsonArray)), + this, SLOT(notifyConnectedClients(QString,QJsonArray))); + return true; +} + +void DapRpcTCPServer::clientDisconnected() +{ + QTcpSocket *tcpSocket = static_cast<QTcpSocket*>(sender()); + if (!tcpSocket) { + qJsonRpcDebug() << "called with invalid socket"; + return; + } + + if (m_socketLookup.contains(tcpSocket)) { + DapRpcSocket *socket = m_socketLookup.take(tcpSocket); + m_clients.removeAll(socket); + socket->deleteLater(); + } + + tcpSocket->deleteLater(); + emit onClientDisconnected(); +} + +void DapRpcTCPServer::messageProcessing(const DapRpcMessage &asMessage) +{ + DapRpcSocket *socket = static_cast<DapRpcSocket*>(sender()); + if (!socket) { + qJsonRpcDebug() << "called without service socket"; + return; + } + + processMessage(socket, asMessage); +} + +void DapRpcTCPServer::incomingConnection(qintptr aSocketDescriptor) +{ + QTcpSocket *tcpSocket = new QTcpSocket(this); + if (!tcpSocket->setSocketDescriptor(aSocketDescriptor)) + { + qJsonRpcDebug() << "can't set socket descriptor"; + tcpSocket->deleteLater(); + return; + } + + QIODevice *device = qobject_cast<QIODevice*>(tcpSocket); + DapRpcSocket *socket = new DapRpcSocket(device, this); + connect(socket, SIGNAL(messageReceived(DapRpcMessage)), + this, SLOT(_q_processMessage(DapRpcMessage))); + m_clients.append(socket); + connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(_q_clientDisconnected())); + m_socketLookup.insert(tcpSocket, socket); + emit onClientConnected(); +} diff --git a/DapRPCProtocol/DapRpcTCPServer.h b/DapRPCProtocol/DapRpcTCPServer.h new file mode 100644 index 0000000000000000000000000000000000000000..00b2e47085e3e781be265c039471c6b33374c133 --- /dev/null +++ b/DapRPCProtocol/DapRpcTCPServer.h @@ -0,0 +1,44 @@ +#ifndef DapRPCTCPSERVER_H +#define DapRPCTCPSERVER_H + +#include <QObject> +#include <QTcpSocket> +#include <QTcpServer> + +#include "DapRpcSocket.h" +#include "DapRpcAbstractServer.h" + +class DapRpcTCPServer : public QTcpServer, public DapRpcAbstractServer +{ + Q_OBJECT + Q_DISABLE_COPY(DapRpcTCPServer) + + QHash<QTcpSocket*, DapRpcSocket*> m_socketLookup; + +protected: + virtual void incomingConnection(qintptr aSocketDescriptor); + +public: + explicit DapRpcTCPServer(QObject *apParent = nullptr); + virtual ~DapRpcTCPServer(); + + virtual bool listen(const QString &asAddress = QString(), quint16 aPort = 0); + + bool addService(DapRpcService *apService); + bool removeService(DapRpcService *apService); + +signals: + void onClientConnected(); + void onClientDisconnected(); + +protected slots: + void clientDisconnected(); + void messageProcessing(const DapRpcMessage &asMessage); + + // DapRpcAbstractServer interface +public slots: + void notifyConnectedClients(const DapRpcMessage &message); + void notifyConnectedClients(const QString &method, const QJsonArray ¶ms); +}; + +#endif // DapRPCTCPSERVER_H diff --git a/KelvinDashboardGUI/qml.qrc b/KelvinDashboardGUI/qml.qrc index 9bc4dd25ee4b7621dbf660c8c7d5ac634ef773a1..8d93e1b02c9a6c266a93892ce9f6505fecba7074 100755 --- a/KelvinDashboardGUI/qml.qrc +++ b/KelvinDashboardGUI/qml.qrc @@ -12,8 +12,6 @@ <file>DapUiQmlWidgetChainWallet.ui.qml</file> <file>DapUiQmlScreenDialog.qml</file> <file>Resources/Icons/icon.png</file> - <file>Resources/Icons/icon.ico</file> - <file>Resources/Icons/add.png</file> <file>DapUiQmlScreenAbout.ui.qml</file> <file>DapQmlScreenAbout.qml</file> <file>Resources/Icons/iconErrorNetwork.png</file> @@ -22,5 +20,7 @@ <file>DapUiQmlWidgetDelegateForm.ui.qml</file> <file>DapUiQmlScreenChangeWidget.qml</file> <file>DapUiQmlWidgetChainNodeLogs.ui.qml</file> + <file>Resources/Icons/add.png</file> + <file>Resources/Icons/icon.ico</file> </qresource> </RCC> diff --git a/KelvinDashboardService/Resources/Icons/iconErrorNetwork.png b/KelvinDashboardService/Resources/Icons/iconErrorNetwork.png new file mode 100644 index 0000000000000000000000000000000000000000..57fa419adeeb2cd4c7de38cf11d9ca445b628f25 Binary files /dev/null and b/KelvinDashboardService/Resources/Icons/iconErrorNetwork.png differ diff --git a/KelvinDashboardService/Resources/Icons/iconNetwork.png b/KelvinDashboardService/Resources/Icons/iconNetwork.png new file mode 100644 index 0000000000000000000000000000000000000000..aa2d0ac25a1a88aeebe52eb8cb9dada732a46287 Binary files /dev/null and b/KelvinDashboardService/Resources/Icons/iconNetwork.png differ