2020-09-15 21:16:35 +03:00
/*
* Copyright ( C ) 2018 - 2020 QuasarApp .
* Distributed under the lgplv3 software license , see the accompanying
* Everyone is permitted to copy and distribute verbatim copies
* of this license document , but changing it is not allowed .
*/
# ifndef ABSTRACTNODE_H
# define ABSTRACTNODE_H
# include "abstractnodeinfo.h"
# include <openssl/evp.h>
# include <QAbstractSocket>
# include <QFutureWatcher>
# include <QMutex>
# include <QSslConfiguration>
# include <QTcpServer>
# include <QTimer>
# include "abstractdata.h"
# include "workstate.h"
# include "cryptopairkeys.h"
# include "icrypto.h"
# include "heart_global.h"
# include "receivedata.h"
# include "packagemanager.h"
class QSslCertificate ;
class QSslKey ;
class QSslConfiguration ;
2020-09-15 22:07:49 +03:00
namespace QH {
2020-09-15 21:16:35 +03:00
class DataSender ;
/**
* @ brief The ParserResult enum
* Error - parser detect a errorob package
* NotProcessed - the parser does not know what to do with the package or has not finished processing it .
* Processed - the parser finished processing correctly
*/
enum class ParserResult {
/// parser detect a errorob package
Error = 0 ,
/// the parser does not know what to do with the package or has not finished processing it.
NotProcessed = 1 ,
/// the parser finished processing correctly
Processed = 2
} ;
2020-09-16 23:40:42 +03:00
/**
* @ brief The SslMode enum This enum contatins options for set ssl mode of node ( server )
* For more information see AbstractNode : : useSelfSignedSslConfiguration AbstractNode : : useSystemSslConfiguration and AbstractNode : : disableSSL methods .
*/
2020-09-15 21:16:35 +03:00
enum class SslMode {
2020-09-16 23:40:42 +03:00
//// This is not secure connection without ssl encription. It is default value of new any node see AbstractNode(SslMode mode = SslMode::NoSSL, QObject * ptr = nullptr)
2020-09-15 21:16:35 +03:00
NoSSL ,
2020-09-16 23:40:42 +03:00
//// This option try enable ssl connection from system configuration form fore information see Qt Documentation https://doc.qt.io/qt-5/qsslconfiguration.html
2020-09-15 21:16:35 +03:00
InitFromSystem ,
2020-09-16 23:40:42 +03:00
//// This option force a current node geneerate self signed sertificat and work with it. For more information see a SslSrtData struct
2020-09-15 21:16:35 +03:00
InitSelfSigned
} ;
/**
2020-09-16 23:40:42 +03:00
* @ brief The SslSrtData struct This structure contains base information for generate self signed ssl certefication .
* If yo want change selfSigned certificate then use method AbstractNode : : useSelfSignedSslConfiguration
2020-09-15 21:16:35 +03:00
*/
struct SslSrtData {
QString country = " BY " ;
QString organization = " QuasarApp " ;
QString commonName = " Dev " ;
long long endTime = 31536000L ; //1 year
} ;
# define CRITICAL_ERROOR -50
# define LOGICK_ERROOR -20
# define REQUEST_ERROR -5
class Abstract ;
/**
* @ brief The AbstractNode class - Abstract implementation of node .
* this implementation have a methods for send and receive data messages ,
* and work with crypto method for crease a security connections betwin nodes .
* AbstractNode - is thread save class
*/
2020-09-15 22:33:48 +03:00
class HEARTSHARED_EXPORT AbstractNode : public QTcpServer
2020-09-15 21:16:35 +03:00
{
Q_OBJECT
public :
/**
2020-09-16 23:40:42 +03:00
* @ brief AbstractNode - base constructor of node .
* @ param ptr - pointrt to parent Qt object , the AbstractNode class is Q_OBJECT
2020-09-15 21:16:35 +03:00
*/
2020-09-16 23:40:42 +03:00
AbstractNode ( QObject * ptr = nullptr ) ;
2020-09-15 21:16:35 +03:00
~ AbstractNode ( ) override ;
/**
2020-09-16 23:40:42 +03:00
* @ brief run - this method implement deployment a network node ( server ) on selected address .
* @ param addres - If address is empty then server weel be listen all addreses of all interfaces else listen only selected address .
* @ param port - This is port of deployment node ( server )
* @ return Result of deployment node ( sever ) . ( True if deploy finished successful else false ) .
2020-09-15 21:16:35 +03:00
*/
virtual bool run ( const QString & addres , unsigned short port ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief stop - stopped this node and close all network connections .
2020-09-15 21:16:35 +03:00
*/
virtual void stop ( ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief getInfoPtr - This method return information class pointer about netwok connection .
* If Connection with id not found then return nullptr .
* @ param id - it is network address of requested node
* @ return The pointer of information about node . if address not found return nullptr
2020-09-15 21:16:35 +03:00
*/
virtual AbstractNodeInfo * getInfoPtr ( const HostAddress & id ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief getInfoPtr - this is some that getInfoPtr ( const HostAddress & id ) bod it is constant implementation .
* @ param id - it is network address of requested node
* @ return The pointer of information about node . if address not found return nullptr
2020-09-15 21:16:35 +03:00
*/
virtual const AbstractNodeInfo * getInfoPtr ( const HostAddress & id ) const ;
/**
2020-09-16 23:40:42 +03:00
* @ brief ban - this method set for target connection a trust property to 0 and target connection will been aborted .
* @ param target - it is network address of target connection .
2020-09-15 21:16:35 +03:00
*/
virtual void ban ( const HostAddress & target ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief unBan - this method set for target connection a trust property to 100.
* @ param target - it is network address of target connection .
2020-09-15 21:16:35 +03:00
*/
virtual void unBan ( const HostAddress & target ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief connectToHost - connect to node ( server ) with address .
* @ param address - This is Network address of node ( server )
* @ param mode - This is mode of connection see SslMode . By default using SslMode : : NoSSL connection mode , it is not secure .
2020-09-15 21:16:35 +03:00
*/
virtual bool connectToHost ( const HostAddress & address , SslMode mode = SslMode : : NoSSL ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief connectToHost - connect to node ( server ) with domain , bud this method find ip address of domain befor connecting
* @ param domain - This is domain address of node ( server )
* @ param port - This is target port of node ( server )
* @ param mode - This is mode of connection see SslMode . By default using SslMode : : NoSSL connection mode , it is not secure .
2020-09-15 21:16:35 +03:00
*/
virtual bool connectToHost ( const QString & domain , unsigned short port , SslMode mode = SslMode : : NoSSL ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief addNode - add new node ( server ) for this mode
* @ param nodeAdderess - This is network addres of a new node ( server ) .
* @ note By Default This immplementation move called function into main Thread and invoke connectToHost method .
2020-09-15 21:16:35 +03:00
*/
void addNode ( const HostAddress & nodeAdderess ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief removeNode - remove node and disconnected forom node ( server )
* @ param nodeAdderess - This is network adddress of removed node ( server ) .
2020-09-15 21:16:35 +03:00
*/
void removeNode ( const HostAddress & nodeAdderess ) ;
/**
2020-09-16 23:40:42 +03:00
* @ brief address - Thim method return own network address of current node ( server )
* @ return The current network adderss
2020-09-15 21:16:35 +03:00
*/
HostAddress address ( ) const ;
/**
2020-09-16 23:40:42 +03:00
* @ brief getSslConfig - This method return ssl configuration of current node ( server ) .
* @ return current ssl configuration on this node ( server )
2020-09-15 21:16:35 +03:00
*/
QSslConfiguration getSslConfig ( ) const ;
/**
2020-09-16 23:40:42 +03:00
* @ brief getMode - This method return SSL mode of corrent node ( server ) .
* @ return current mode for more information see SslMode
2020-09-15 21:16:35 +03:00
*/
SslMode getMode ( ) const ;
/**
2020-09-16 23:40:42 +03:00
* @ brief getWorkState - This method collect general information about this server .
* For more information about returned data see getWorkState
* @ return state value for more information see WorkState class
2020-09-15 21:16:35 +03:00
*/
virtual WorkState getWorkState ( ) const ;
/**
2020-09-16 23:40:42 +03:00
* @ brief pareseResultToString This method convert ParserResult value to string .
* @ return The String value of pareseresult
2020-09-15 21:16:35 +03:00
*/
QString pareseResultToString ( const ParserResult & parseResult ) const ;
/**
2020-09-16 23:40:42 +03:00
* @ brief connectionsCount - return count fo connections ( connections with status connected )
* @ return count valid connections .
2020-09-15 21:16:35 +03:00
*/
int connectionsCount ( ) const ;
/**
* @ brief connectionsCount - return count of nodes with status confirmend
2020-09-16 23:40:42 +03:00
* @ return return confirmend connections of this node ( server )
2020-09-15 21:16:35 +03:00
*/
int confirmendCount ( ) const ;
/**
2020-09-16 23:40:42 +03:00
* @ brief ping This method send ping package to address for testing connection
* @ param address This is address of target node ( server )
* @ return true if ping sendet successful .
2020-09-15 21:16:35 +03:00
*/
bool ping ( const HostAddress & address ) ;
signals :
2020-09-16 23:40:42 +03:00
/**
2020-09-21 16:30:48 +03:00
* @ brief requestError This signal emited when client or node received from remoute server or node the BadRequest package .
2020-09-16 23:40:42 +03:00
* @ param msg - received text of remoute node ( server ) .
*/
2020-09-15 21:16:35 +03:00
void requestError ( QString msg ) ;
protected :
/**
2020-09-21 16:30:48 +03:00
* @ brief generateRSAforSSL This method generate ssl rsa pair keys for using in selfsigned cetificate .
* By default generate RSA 2048 , if you want change algorithm or keys size then override this method .
* @ param pkey This is openssl pointer to RSA pair key .
* @ return True if keys generated successful .
2020-09-15 21:16:35 +03:00
*/
virtual bool generateRSAforSSL ( EVP_PKEY * pkey ) const ;
/**
2020-09-21 16:30:48 +03:00
* @ brief generateSslDataPrivate This method generate a ssl certificate and a ssl keys using The SslSrtData structure .
* @ param data The data for generate a selfSigned certificate .
* @ param r_srt This is return value of a certivicate
* @ param r_key - This is return value of private ssl key
* @ return True if generate the selfSigned certificate finished succesful .
2020-09-15 21:16:35 +03:00
*/
virtual bool generateSslDataPrivate ( const SslSrtData & data , QSslCertificate & r_srt , QSslKey & r_key ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief selfSignedSslConfiguration This method create a new ssl configuration with selfsigned certificates .
* @ param data This is data for generate selfsigned certification for more information see SslSrtData structure .
* @ return The new selfsigned ssl configuration
2020-09-15 21:16:35 +03:00
*/
2020-09-21 16:30:48 +03:00
virtual QSslConfiguration selfSignedSslConfiguration ( const SslSrtData & data = { } ) ;
2020-09-15 21:16:35 +03:00
/**
2020-09-21 16:30:48 +03:00
* @ brief createNodeInfo This method create a nodeInfo object .
* override this method for create your own nodeInfo objects . for more in
* @ param socket This is socket of network address
* @ param clientAddress This parameter need to set when the socket du not contains a address or invalid .
* @ return return pointer to info object .
2020-09-15 21:16:35 +03:00
*/
virtual AbstractNodeInfo * createNodeInfo ( QAbstractSocket * socket ,
const HostAddress * clientAddress = nullptr ) const ;
/**
2020-09-21 16:30:48 +03:00
* @ brief registerSocket This method registration new socket object .
* @ param socket This is incomming socket pointer
* @ param address This is host address of socket . By default is nullptr .
2020-09-15 21:16:35 +03:00
* Set this value for nodes created on this host
* @ return return true if the scoeket has been registered successful
*/
virtual bool registerSocket ( QAbstractSocket * socket , const HostAddress * address = nullptr ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief parsePackage This is main method of all childs classes of an AbstractNode class .
* This method work on own thread .
* If you ovveride this method you need to create this than an example :
* \ code
ParserResult DataBaseNode : : parsePackage ( const Package & pkg ,
const AbstractNodeInfo * sender ) {
auto parentResult = AbstractNode : : parsePackage ( pkg , sender ) ;
if ( parentResult ! = ParserResult : : NotProcessed ) {
return parentResult ;
}
if ( H_16 < MyCommand > ( ) = = pkg . hdr . command ) {
MyCommand obj
obj . fromPackage ( pkg ) ;
BaseId requesterId = getSender ( sender , & obj ) ;
. . .
if ( FailCondition ) {
return ParserResult : : Error ;
}
. . .
return ParserResult : : Processed ;
}
return ParserResult : : NotProcessed ;
}
* \ endcode
* @ param pkg This is package with incomming data .
* @ param sender This is sender this pacakge
* @ return item of ParserResult . For more information see The ParserResult enum .
2020-09-15 21:16:35 +03:00
*/
virtual ParserResult parsePackage ( const Package & pkg , const AbstractNodeInfo * sender ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief sendPackage This method prepare and send to target address a package .
* @ param pkg This is sendet pakcage to target node .
* @ param target This is target node .
* @ return return true if The package is sendet succesfull
2020-09-15 21:16:35 +03:00
*/
virtual bool sendPackage ( const Package & pkg , QAbstractSocket * target ) const ;
/**
2020-09-21 16:30:48 +03:00
* @ brief sendData This pakcage send data package to address and prepare object to sending .
* @ param resp This is pointer to sendet object
* @ param address This is target addres for sending
* @ param req This is header of request
2020-09-15 21:16:35 +03:00
* @ return true if data sendet succesful .
*/
2020-09-16 15:10:13 +03:00
virtual bool sendData ( PKG : : AbstractData * resp , const HostAddress & addere ,
2020-09-15 21:16:35 +03:00
const Header * req = nullptr ) ;
/**
* @ brief sendData this is some as a sendData ( AbstractData * resp . . . ) exept this method not prepare object for sending .
2020-09-21 16:30:48 +03:00
* @ param resp This is pointer to sendet object
* @ param address This is target addres for sending
* @ param req This is header of request
2020-09-15 21:16:35 +03:00
* @ return true if data sendet succesful .
*/
2020-09-16 15:10:13 +03:00
virtual bool sendData ( const PKG : : AbstractData * resp , const HostAddress & addere ,
2020-09-15 21:16:35 +03:00
const Header * req = nullptr ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief badRequest This method is send data about error of request
* @ param address This is addrees of receiver
* @ param req This is header of incomming request
* @ param msg This is message of error
2020-09-15 21:16:35 +03:00
*/
virtual void badRequest ( const HostAddress & address , const Header & req ,
const QString msg = " " ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief getWorkStateString This method generate string about work state of server .
2020-09-15 21:16:35 +03:00
* @ return string of work state
*/
virtual QString getWorkStateString ( ) const ;
/**
2020-09-21 16:30:48 +03:00
* @ brief connectionState This method return string value about the cocction state
2020-09-15 21:16:35 +03:00
* @ return string with count users state
*/
virtual QString connectionState ( ) const ;
/**
2020-09-21 16:30:48 +03:00
* @ brief banedList This method retrun list of banned clients of nodes .
2020-09-15 21:16:35 +03:00
* @ return list of baned nodes
*/
QList < HostAddress > banedList ( ) const ;
/**
2020-09-21 16:30:48 +03:00
* @ brief isBanned This method checks if the node is banned .
* @ param socket This is node info object for validation
* @ return true if node is banned
2020-09-15 21:16:35 +03:00
*/
bool isBanned ( QAbstractSocket * socket ) const ;
/**
2020-09-21 16:30:48 +03:00
* @ brief incomingConnection This is ovverided method of QTCPServer
* @ param handle This is socket handel
2020-09-15 21:16:35 +03:00
*/
2020-09-21 16:30:48 +03:00
void incomingConnection ( qintptr handle ) override final ;
2020-09-15 21:16:35 +03:00
/**
2020-09-21 16:30:48 +03:00
* @ brief changeTrust This method change trust of connected node
* @ param id This is id of select node
* @ param diff This is difference of current trust ( currenTrus + = diff )
* @ return true if node Trust is changed successful
2020-09-15 21:16:35 +03:00
*/
virtual bool changeTrust ( const HostAddress & id , int diff ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief incomingConnection This methiod work with incomming ssl sockets .
2020-09-15 21:16:35 +03:00
* @ param handle - handle of socket
*/
virtual void incomingSsl ( qintptr handle ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief incomingConnection This methiod work with incomming tcp sockets .
* @ param handle - handle of socket
2020-09-15 21:16:35 +03:00
*/
virtual void incomingTcp ( qintptr handle ) ;
2020-09-16 23:40:42 +03:00
/**
2020-09-21 16:30:48 +03:00
* @ brief useSelfSignedSslConfiguration This method reconfigure current node to use selfSigned certificate .
2020-09-16 23:40:42 +03:00
* @ note Befor invoke this method stop this node ( server ) see AbstractNode : : stop . if mode will be working then this method return false .
* The self signed certificate is temp value , this is will be changed after reboot node ( server )
* @ param crtData - This is data for generation a new self signed certification .
* @ return result of change node ssl configuration .
*/
bool useSelfSignedSslConfiguration ( const SslSrtData & crtData ) ;
2020-09-15 21:16:35 +03:00
/**
2020-09-21 16:30:48 +03:00
* @ brief useSystemSslConfiguration This method reconfigure current node to use sslConfig .
2020-09-16 23:40:42 +03:00
* @ note Befor invoke this method stop this node ( server ) see AbstractNode : : stop . if mode will be working then this method return false .
2020-09-21 16:30:48 +03:00
* @ param sslConfig This is ssl configuration ot a current node ( server )
2020-09-16 23:40:42 +03:00
* @ return result of change node ssl configuration .
2020-09-15 21:16:35 +03:00
*/
2020-09-16 23:40:42 +03:00
bool useSystemSslConfiguration ( const QSslConfiguration & sslConfig ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief disableSSL This method disable ssl mode for this node
2020-09-16 23:40:42 +03:00
* @ note Befor invoke this method stop this node ( server ) see AbstractNode : : stop . if mode will be working then this method return false .
* @ return true if changes is completed .
*/
bool disableSSL ( ) ;
2020-09-15 21:16:35 +03:00
/**
2020-09-21 16:30:48 +03:00
* @ brief incomingData This method invoked when node get command or ansver . But in default implemmentation it using only for Ping command . Add
* \ code
* incomingData ( pkg , sender ) ;
* \ endcode
* Into the overrided AbstractNode : : ParsePacakge method on your own server or client class .
* @ param pkg This is received package ( in this implementation it is only the Ping command )
* @ param sender This is information of sender of the package
2020-09-15 21:16:35 +03:00
* @ note override this method for get a signals .
*/
2020-09-16 15:10:13 +03:00
virtual void incomingData ( PKG : : AbstractData * pkg ,
2020-09-15 21:16:35 +03:00
const HostAddress & sender ) ;
/**
* @ brief connections - return hash map of all connections of this node .
2020-09-21 16:30:48 +03:00
* @ return return map of connections .
2020-09-15 21:16:35 +03:00
*/
QHash < HostAddress , AbstractNodeInfo * > connections ( ) const ;
/**
* @ brief connectionRegistered Override this method for get registered incoming connections .
* @ param info - connection information .
*/
virtual void connectionRegistered ( const AbstractNodeInfo * info ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief nodeStatusChanged This method invoked when status of node chganged .
2020-09-15 21:16:35 +03:00
* Base implementation do nothing . Override this method for add own functionality .
2020-09-21 16:30:48 +03:00
* @ param node This is address of changed node .
* @ param status This is new status of node .
2020-09-15 21:16:35 +03:00
*
*/
void nodeStatusChanged ( const HostAddress & node , NodeCoonectionStatus status ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief nodeConfirmend This method invocked when the node status changed to " confirmend "
2020-09-15 21:16:35 +03:00
* default implementatio do nothing
2020-09-21 16:30:48 +03:00
* @ param node This is address of changed node
2020-09-15 21:16:35 +03:00
*/
virtual void nodeConfirmend ( const HostAddress & node ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief nodeConnected This method invocked when the node status changed to " connected "
2020-09-15 21:16:35 +03:00
* default implementatio do nothing
2020-09-21 16:30:48 +03:00
* @ param node This is address of changed node
2020-09-15 21:16:35 +03:00
*/
virtual void nodeConnected ( const HostAddress & node ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief nodeConnected This method invocked when the node status changed to " disconnected "
2020-09-15 21:16:35 +03:00
* default implementatio do nothing
2020-09-21 16:30:48 +03:00
* @ param node This is address of changed node
2020-09-15 21:16:35 +03:00
*/
virtual void nodeDisconnected ( const HostAddress & node ) ;
/**
* @ brief pushToQueue - This method add action to queue . When the node status will be equal ' triggerStatus ' then node run a action method .
2020-09-21 16:30:48 +03:00
* @ param node This is address of node that changed status .
* @ param action This is lyamda function with action .
* @ param triggerStatus This is awaiting status of node .
2020-09-15 21:16:35 +03:00
*/
void pushToQueue ( const std : : function < void ( ) > & action ,
const HostAddress & node ,
NodeCoonectionStatus triggerStatus ) ;
/**
2020-09-21 16:30:48 +03:00
* @ brief takeFromQueue This method take the list of actions of node .
* After invoke take elements will be removed .
* @ param node This is address of node that changed status .
* @ param triggerStatus This is awaiting status of node .
* @ return The list of an actions
2020-09-15 21:16:35 +03:00
*/
QList < std : : function < void ( ) > > takeFromQueue ( const HostAddress & node ,
NodeCoonectionStatus triggerStatus ) ;
private slots :
void avelableBytes ( ) ;
void handleDisconnected ( ) ;
void handleConnected ( ) ;
void handleCheckConfirmendOfNode ( HostAddress node ) ;
/**
* @ brief handleWorkerStoped
*/
void handleWorkerStoped ( ) ;
/**
* @ brief handleForceRemoveNode - force remove connection .
* @ param node
*/
void handleForceRemoveNode ( HostAddress node ) ;
/**
* @ brief connectNodePrivate
*/
2020-09-15 22:07:49 +03:00
void connectNodePrivate ( QH : : HostAddress ) ;
2020-09-15 21:16:35 +03:00
private :
/**
@ note just disaable listen method in the node objects .
*/
bool listen ( const HostAddress & address = HostAddress : : Any ) ;
/**
* @ brief newWork - this method it is wraper of the parsePackage method .
* the newWork invoke a parsePackage in the new thread .
* @ param pkg
* @ param sender
*/
void newWork ( const Package & pkg , const AbstractNodeInfo * sender , const HostAddress & id ) ;
/**
* @ brief nodeConfirmet - this metthod invoked when node is confirment .
* @ param sender - node with new status ;
*/
void nodeConfirmet ( const HostAddress & node ) ;
/**
* @ brief checkConfirmendOfNode - this method remove old not confirmed node .
* @ param node - node address
*/
void checkConfirmendOfNode ( const HostAddress & node ) ;
SslMode _mode ;
QSslConfiguration _ssl ;
QHash < HostAddress , AbstractNodeInfo * > _connections ;
QHash < HostAddress , ReceiveData > _receiveData ;
QHash < HostAddress , QHash < NodeCoonectionStatus , QList < std : : function < void ( ) > > > > _actionCache ;
DataSender * _dataSender = nullptr ;
QSet < QFutureWatcher < bool > * > _workers ;
PackageManager _packageManager ;
mutable QMutex _connectionsMutex ;
mutable QMutex _actionCacheMutex ;
mutable QMutex _confirmNodeMutex ;
friend class WebSocketController ;
} ;
}
# endif // ABSTRACTNODE_H