diff --git a/libnymea-remoteproxy/engine.cpp b/libnymea-remoteproxy/engine.cpp index 0baf9bd..82bee33 100644 --- a/libnymea-remoteproxy/engine.cpp +++ b/libnymea-remoteproxy/engine.cpp @@ -73,19 +73,31 @@ void Engine::start(ProxyConfiguration *configuration) m_proxyServer = new ProxyServer(this); m_webSocketServer = new WebSocketServer(m_configuration->sslEnabled(), m_configuration->sslConfiguration(), this); + m_tcpSocketServer = new TcpSocketServer(m_configuration->sslEnabled(), m_configuration->sslConfiguration(), this); + // Configure websocket server QUrl websocketServerUrl; websocketServerUrl.setScheme(m_configuration->sslEnabled() ? "wss" : "ws"); websocketServerUrl.setHost(m_configuration->webSocketServerHost().toString()); websocketServerUrl.setPort(m_configuration->webSocketServerPort()); - m_webSocketServer->setServerUrl(websocketServerUrl); - m_proxyServer->registerTransportInterface(m_webSocketServer); + // Configure tcp socket server + QUrl tcpSocketServerUrl; + tcpSocketServerUrl.setScheme(m_configuration->sslEnabled() ? "ssl" : "tcp"); + tcpSocketServerUrl.setHost(m_configuration->tcpServerHost().toString()); + tcpSocketServerUrl.setPort(m_configuration->tcpServerPort()); + m_tcpSocketServer->setServerUrl(tcpSocketServerUrl); + // Register the transport interfaces in the proxy server + m_proxyServer->registerTransportInterface(m_webSocketServer); + m_proxyServer->registerTransportInterface(m_tcpSocketServer); + + // Start the server qCDebug(dcEngine()) << "Starting proxy server"; m_proxyServer->startServer(); + // Start the monitor server m_monitorServer = new MonitorServer(configuration->monitorSocketFileName(), this); m_monitorServer->startServer(); @@ -153,6 +165,11 @@ ProxyServer *Engine::proxyServer() const return m_proxyServer; } +TcpSocketServer *Engine::tcpSocketServer() const +{ + return m_tcpSocketServer; +} + WebSocketServer *Engine::webSocketServer() const { return m_webSocketServer; diff --git a/libnymea-remoteproxy/engine.h b/libnymea-remoteproxy/engine.h index 247a268..849ae6f 100644 --- a/libnymea-remoteproxy/engine.h +++ b/libnymea-remoteproxy/engine.h @@ -38,6 +38,7 @@ #include "logengine.h" #include "proxyserver.h" #include "monitorserver.h" +#include "tcpsocketserver.h" #include "websocketserver.h" #include "proxyconfiguration.h" #include "authentication/authenticator.h" @@ -67,6 +68,7 @@ public: ProxyConfiguration *configuration() const; Authenticator *authenticator() const; ProxyServer *proxyServer() const; + TcpSocketServer *tcpSocketServer() const; WebSocketServer *webSocketServer() const; MonitorServer *monitorServer() const; LogEngine *logEngine() const; @@ -88,6 +90,7 @@ private: ProxyConfiguration *m_configuration = nullptr; Authenticator *m_authenticator = nullptr; ProxyServer *m_proxyServer = nullptr; + TcpSocketServer *m_tcpSocketServer = nullptr; WebSocketServer *m_webSocketServer = nullptr; MonitorServer *m_monitorServer = nullptr; LogEngine *m_logEngine = nullptr; diff --git a/libnymea-remoteproxy/loggingcategories.cpp b/libnymea-remoteproxy/loggingcategories.cpp index 1122fa6..5bf36b7 100644 --- a/libnymea-remoteproxy/loggingcategories.cpp +++ b/libnymea-remoteproxy/loggingcategories.cpp @@ -32,6 +32,8 @@ Q_LOGGING_CATEGORY(dcEngine, "Engine") Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") Q_LOGGING_CATEGORY(dcTunnel, "Tunnel") Q_LOGGING_CATEGORY(dcJsonRpcTraffic, "JsonRpcTraffic") +Q_LOGGING_CATEGORY(dcTcpSocketServer, "TcpSocketServer") +Q_LOGGING_CATEGORY(dcTcpSocketServerTraffic, "TcpSocketServerTraffic") Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer") Q_LOGGING_CATEGORY(dcWebSocketServerTraffic, "WebSocketServerTraffic") Q_LOGGING_CATEGORY(dcAuthentication, "Authentication") diff --git a/libnymea-remoteproxy/loggingcategories.h b/libnymea-remoteproxy/loggingcategories.h index 329be4c..f9b4464 100644 --- a/libnymea-remoteproxy/loggingcategories.h +++ b/libnymea-remoteproxy/loggingcategories.h @@ -38,6 +38,8 @@ Q_DECLARE_LOGGING_CATEGORY(dcTunnel) Q_DECLARE_LOGGING_CATEGORY(dcJsonRpcTraffic) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServerTraffic) +Q_DECLARE_LOGGING_CATEGORY(dcTcpSocketServer) +Q_DECLARE_LOGGING_CATEGORY(dcTcpSocketServerTraffic) Q_DECLARE_LOGGING_CATEGORY(dcAuthentication) Q_DECLARE_LOGGING_CATEGORY(dcAuthenticationProcess) Q_DECLARE_LOGGING_CATEGORY(dcProxyServer) diff --git a/libnymea-remoteproxy/tcpsocketserver.cpp b/libnymea-remoteproxy/tcpsocketserver.cpp index bf57d65..40725f3 100644 --- a/libnymea-remoteproxy/tcpsocketserver.cpp +++ b/libnymea-remoteproxy/tcpsocketserver.cpp @@ -20,6 +20,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "tcpsocketserver.h" +#include "loggingcategories.h" namespace remoteproxy { @@ -28,47 +29,150 @@ TcpSocketServer::TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslCo m_sslEnabled(sslEnabled), m_sslConfiguration(sslConfiguration) { - + m_serverName = "TCP"; } -quint16 TcpSocketServer::port() const +TcpSocketServer::~TcpSocketServer() { - return m_port; -} - -void TcpSocketServer::setPort(quint16 port) -{ - m_port = port; -} - -QHostAddress TcpSocketServer::hostAddress() const -{ - return m_hostAddress; -} - -void TcpSocketServer::setHostAddress(const QHostAddress &address) -{ - m_hostAddress = address; + stopServer(); } void TcpSocketServer::sendData(const QUuid &clientId, const QByteArray &data) { + QTcpSocket *client = nullptr; + client = m_clientList.value(clientId); + if (!client) { + qCWarning(dcTcpSocketServer()) << "Client" << clientId << "unknown to this transport"; + return; + } + client->write(data + '\n'); } void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason) { + QTcpSocket *client = m_clientList.value(clientId); + if (!client) + return; + qCWarning(dcTcpSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; + client->abort(); +} + +bool TcpSocketServer::running() const +{ + if (!m_server) + return false; + + return m_server->isListening(); +} + +void TcpSocketServer::onDataAvailable(QSslSocket *client, const QByteArray &data) +{ + qCDebug(dcTcpSocketServerTraffic()) << "Emitting data available internal."; + QUuid clientId = m_clientList.key(qobject_cast(client)); + emit dataAvailable(clientId, data); +} + +void TcpSocketServer::onClientConnected(QSslSocket *client) +{ + QUuid clientId = QUuid::createUuid(); + qCDebug(dcTcpSocketServer()) << "New client connected:" << client << client->peerAddress().toString() << clientId.toString(); + m_clientList.insert(clientId, client); + emit clientConnected(clientId, client->peerAddress()); +} + +void TcpSocketServer::onClientDisconnected(QSslSocket *client) +{ + QUuid clientId = m_clientList.key(client); + qCDebug(dcWebSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString(); + m_clientList.take(clientId); + // Note: the SslServer is deleting the socket object + emit clientDisconnected(clientId); } bool TcpSocketServer::startServer() { + qCDebug(dcTcpSocketServer()) << "Starting TCP server" << m_serverUrl.toString(); + m_server = new SslServer(m_sslEnabled, m_sslConfiguration); + if(!m_server->listen(QHostAddress(m_serverUrl.host()), static_cast(m_serverUrl.port()))) { + qCWarning(dcTcpSocketServer()) << "Tcp server error: can not listen on" << m_serverUrl.toString(); + delete m_server; + m_server = nullptr; + return false; + } + connect(m_server, &SslServer::clientConnected, this, &TcpSocketServer::onClientConnected); + connect(m_server, SIGNAL(clientDisconnected(QSslSocket *)), SLOT(onClientDisconnected(QSslSocket *))); + connect(m_server, &SslServer::dataAvailable, this, &TcpSocketServer::onDataAvailable); + qCDebug(dcTcpSocketServer()) << "Server started successfully."; + return true; } bool TcpSocketServer::stopServer() +{ + // Clean up client connections + foreach (const QUuid &clientId, m_clientList.keys()) { + killClientConnection(clientId, "Stop server"); + } + + if (!m_server) + return true; + + qCDebug(dcTcpSocketServer()) << "Stop server" << m_serverUrl.toString(); + m_server->close(); + delete m_server; + m_server = nullptr; + return true; +} + +SslServer::SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent) : + QTcpServer(parent), + m_sslEnabled(sslEnabled), + m_config(config) + { } +void SslServer::incomingConnection(qintptr socketDescriptor) +{ + QSslSocket *sslSocket = new QSslSocket(this); + + qCDebug(dcTcpSocketServer()) << "New client connected:" << sslSocket << sslSocket->peerAddress().toString(); + + connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket](){ emit clientConnected(sslSocket); }); + connect(sslSocket, &QSslSocket::readyRead, this, &SslServer::onSocketReadyRead); + connect(sslSocket, &QSslSocket::disconnected, this, &SslServer::onClientDisconnected); + + if (!sslSocket->setSocketDescriptor(socketDescriptor)) { + qCWarning(dcTcpSocketServer()) << "Failed to set SSL socket descriptor."; + delete sslSocket; + return; + } + + if (m_sslEnabled) { + sslSocket->setSslConfiguration(m_config); + sslSocket->startServerEncryption(); + } else { + emit clientConnected(sslSocket); + } +} + +void SslServer::onClientDisconnected() +{ + QSslSocket *sslSocket = qobject_cast(sender()); + qCDebug(dcTcpSocketServer()) << "Client socket disconnected:" << sslSocket << sslSocket->peerAddress().toString();; + emit clientDisconnected(sslSocket); + sslSocket->deleteLater(); +} + +void SslServer::onSocketReadyRead() +{ + QSslSocket *sslSocket = qobject_cast(sender()); + QByteArray data = sslSocket->readAll(); + qCDebug(dcTcpSocketServerTraffic()) << "Data from socket" << sslSocket->peerAddress().toString() << data; + emit dataAvailable(sslSocket, data); +} + } diff --git a/libnymea-remoteproxy/tcpsocketserver.h b/libnymea-remoteproxy/tcpsocketserver.h index 9d2736a..5d6af91 100644 --- a/libnymea-remoteproxy/tcpsocketserver.h +++ b/libnymea-remoteproxy/tcpsocketserver.h @@ -22,6 +22,7 @@ #ifndef TCPSOCKETSERVER_H #define TCPSOCKETSERVER_H +#include #include #include #include @@ -35,13 +36,12 @@ class SslServer: public QTcpServer { Q_OBJECT public: - SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent = nullptr): - QTcpServer(parent), - m_sslEnabled(sslEnabled), - m_config(config) - { + explicit SslServer(bool sslEnabled, const QSslConfiguration &config, QObject *parent = nullptr); + ~SslServer() override = default; - } +private: + bool m_sslEnabled = false; + QSslConfiguration m_config; signals: void clientConnected(QSslSocket *socket); @@ -55,9 +55,6 @@ private slots: void onClientDisconnected(); void onSocketReadyRead(); -private: - bool m_sslEnabled = false; - QSslConfiguration m_config; }; @@ -68,22 +65,23 @@ public: explicit TcpSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr); ~TcpSocketServer() override; - quint16 port() const; - void setPort(quint16 port); - - QHostAddress hostAddress() const; - void setHostAddress(const QHostAddress &address); - void sendData(const QUuid &clientId, const QByteArray &data) override; void killClientConnection(const QUuid &clientId, const QString &killReason) override; + bool running() const override; + private: - quint16 m_port; - QHostAddress m_hostAddress; bool m_sslEnabled; QSslConfiguration m_sslConfiguration; - QTcpServer *m_server = nullptr; + QHash m_clientList; + + SslServer *m_server = nullptr; + +private slots: + void onDataAvailable(QSslSocket *client, const QByteArray &data); + void onClientConnected(QSslSocket *client); + void onClientDisconnected(QSslSocket *client); public slots: bool startServer() override; diff --git a/libnymea-remoteproxy/transportinterface.cpp b/libnymea-remoteproxy/transportinterface.cpp index 578c3aa..5e969b1 100644 --- a/libnymea-remoteproxy/transportinterface.cpp +++ b/libnymea-remoteproxy/transportinterface.cpp @@ -35,14 +35,24 @@ TransportInterface::TransportInterface(QObject *parent) : } -QString TransportInterface::serverName() const -{ - return m_serverName; -} - TransportInterface::~TransportInterface() { } +QString TransportInterface::serverName() const +{ + return m_serverName; +} + +QUrl TransportInterface::serverUrl() const +{ + return m_serverUrl; +} + +void TransportInterface::setServerUrl(const QUrl &serverUrl) +{ + m_serverUrl = serverUrl; +} + } diff --git a/libnymea-remoteproxy/transportinterface.h b/libnymea-remoteproxy/transportinterface.h index aa13f7c..b507fcd 100644 --- a/libnymea-remoteproxy/transportinterface.h +++ b/libnymea-remoteproxy/transportinterface.h @@ -28,6 +28,7 @@ #ifndef TRANSPORTINTERFACE_H #define TRANSPORTINTERFACE_H +#include #include #include @@ -45,12 +46,18 @@ public: virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0; virtual void killClientConnection(const QUuid &clientId, const QString &killReason) = 0; + QUrl serverUrl() const; + void setServerUrl(const QUrl &serverUrl); + + virtual bool running() const = 0; + signals: void clientConnected(const QUuid &clientId, const QHostAddress &address); void clientDisconnected(const QUuid &clientId); void dataAvailable(const QUuid &clientId, const QByteArray &data); protected: + QUrl m_serverUrl; QString m_serverName; public slots: diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 9a5386f..205fd17 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -37,7 +37,7 @@ WebSocketServer::WebSocketServer(bool sslEnabled, const QSslConfiguration &sslCo m_sslEnabled(sslEnabled), m_sslConfiguration(sslConfiguration) { - m_serverName = "Websocket server"; + m_serverName = "WebSocket"; } WebSocketServer::~WebSocketServer() @@ -45,16 +45,6 @@ WebSocketServer::~WebSocketServer() stopServer(); } -QUrl WebSocketServer::serverUrl() const -{ - return m_serverUrl; -} - -void WebSocketServer::setServerUrl(const QUrl &serverUrl) -{ - m_serverUrl = serverUrl; -} - bool WebSocketServer::running() const { if (!m_server) @@ -215,13 +205,13 @@ bool WebSocketServer::stopServer() } // Delete the server object - if (m_server) { - qCDebug(dcWebSocketServer()) << "Stop server" << m_server->serverName() << serverUrl().toString(); - m_server->close(); - delete m_server; - m_server = nullptr; - } + if (!m_server) + return true; + qCDebug(dcWebSocketServer()) << "Stop server" << m_server->serverName() << serverUrl().toString(); + m_server->close(); + delete m_server; + m_server = nullptr; return true; } diff --git a/libnymea-remoteproxy/websocketserver.h b/libnymea-remoteproxy/websocketserver.h index e51fd5b..c6d172d 100644 --- a/libnymea-remoteproxy/websocketserver.h +++ b/libnymea-remoteproxy/websocketserver.h @@ -46,10 +46,7 @@ public: explicit WebSocketServer(bool sslEnabled, const QSslConfiguration &sslConfiguration, QObject *parent = nullptr); ~WebSocketServer() override; - QUrl serverUrl() const; - void setServerUrl(const QUrl &serverUrl); - - bool running() const; + bool running() const override; QSslConfiguration sslConfiguration() const; @@ -57,7 +54,6 @@ public: void killClientConnection(const QUuid &clientId, const QString &killReason) override; private: - QUrl m_serverUrl; QWebSocketServer *m_server = nullptr; bool m_sslEnabled; QSslConfiguration m_sslConfiguration; diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index a56f6b6..73e0a10 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -27,6 +27,7 @@ #include "proxyconnection.h" #include "proxyjsonrpcclient.h" +#include "tcpsocketconnection.h" #include "websocketconnection.h" #include "remoteproxyconnection.h" @@ -335,8 +336,7 @@ bool RemoteProxyConnection::connectServer(const QUrl &url) m_connection = qobject_cast(new WebSocketConnection(this)); break; case ConnectionTypeTcpSocket: - // FIXME: - //m_connection = qobject_cast(new WebSocketConnection(this)); + m_connection = qobject_cast(new TcpSocketConnection(this)); break; } diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.h b/libnymea-remoteproxyclient/remoteproxyconnection.h index 372a114..1eeeb6d 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -153,6 +153,7 @@ public slots: bool authenticate(const QString &token, const QString &nonce = QString()); void disconnectServer(); bool sendData(const QByteArray &data); + }; } diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp index 77d166b..4a4ee39 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -73,7 +73,7 @@ void RemoteProxyOfflineTests::dummyAuthenticator() params.insert("name", "test"); params.insert("token", "foobar"); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); verifyAuthenticationError(response); @@ -291,7 +291,7 @@ void RemoteProxyOfflineTests::getIntrospect() // Start the server startServer(); - QVariant response = invokeApiCall("RemoteProxy.Introspect"); + QVariant response = invokeWebSocketApiCall("RemoteProxy.Introspect"); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Clean up @@ -303,7 +303,7 @@ void RemoteProxyOfflineTests::getHello() // Start the server startServer(); - QVariantMap response = invokeApiCall("RemoteProxy.Hello").toMap(); + QVariantMap response = invokeWebSocketApiCall("RemoteProxy.Hello").toMap(); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Verify data @@ -342,7 +342,7 @@ void RemoteProxyOfflineTests::apiBasicCalls() // Start the server startServer(); - QVariant response = injectSocketData(data); + QVariant response = injectWebSocketData(data); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); QCOMPARE(response.toMap().value("id").toInt(), responseId); @@ -409,7 +409,7 @@ void RemoteProxyOfflineTests::authenticate() params.insert("token", token); if (!nonce.isEmpty()) params.insert("nonce", nonce); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); verifyAuthenticationError(response, expectedError); @@ -925,7 +925,7 @@ void RemoteProxyOfflineTests::jsonRpcTimeout() params.insert("name", "name"); params.insert("token", "token"); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); QVERIFY(response.toMap().value("status").toString() == "error"); @@ -976,7 +976,7 @@ void RemoteProxyOfflineTests::authenticationReplyTimeout() params.insert("name", "Sleepy test client"); params.insert("token", "sleepy token zzzZZZ"); - QVariant response = invokeApiCall("Authentication.Authenticate", params); + QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); verifyAuthenticationError(response, Authenticator::AuthenticationErrorTimeout); diff --git a/tests/testbase/basetest.cpp b/tests/testbase/basetest.cpp index 1a52857..6b398cd 100644 --- a/tests/testbase/basetest.cpp +++ b/tests/testbase/basetest.cpp @@ -89,6 +89,7 @@ void BaseTest::startServer() QVERIFY(Engine::instance()->running()); QVERIFY(Engine::instance()->developerMode()); QVERIFY(Engine::instance()->webSocketServer()->running()); + QVERIFY(Engine::instance()->tcpSocketServer()->running()); QVERIFY(Engine::instance()->monitorServer()->running()); } @@ -101,7 +102,7 @@ void BaseTest::stopServer() QVERIFY(!Engine::instance()->running()); } -QVariant BaseTest::invokeApiCall(const QString &method, const QVariantMap params, bool remainsConnected) +QVariant BaseTest::invokeWebSocketApiCall(const QString &method, const QVariantMap params, bool remainsConnected) { Q_UNUSED(remainsConnected) @@ -152,11 +153,12 @@ QVariant BaseTest::invokeApiCall(const QString &method, const QVariantMap params return jsonDoc.toVariant(); } } + m_commandCounter++; return QVariant(); } -QVariant BaseTest::injectSocketData(const QByteArray &data) +QVariant BaseTest::injectWebSocketData(const QByteArray &data) { QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors); diff --git a/tests/testbase/basetest.h b/tests/testbase/basetest.h index 09e223f..60c029c 100644 --- a/tests/testbase/basetest.h +++ b/tests/testbase/basetest.h @@ -78,8 +78,8 @@ protected: void startServer(); void stopServer(); - QVariant invokeApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true); - QVariant injectSocketData(const QByteArray &data); + QVariant invokeWebSocketApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true); + QVariant injectWebSocketData(const QByteArray &data); bool createRemoteConnection(const QString &token, const QString &nonce, QObject *parent);