You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
590 lines
18 KiB
C++
590 lines
18 KiB
C++
/***************************************************************************
|
|
* Copyright (C) gempa GmbH *
|
|
* All rights reserved. *
|
|
* Contact: gempa GmbH (seiscomp-dev@gempa.de) *
|
|
* *
|
|
* GNU Affero General Public License Usage *
|
|
* This file may be used under the terms of the GNU Affero *
|
|
* Public License version 3.0 as published by the Free Software Foundation *
|
|
* and appearing in the file LICENSE included in the packaging of this *
|
|
* file. Please review the following information to ensure the GNU Affero *
|
|
* Public License version 3.0 requirements will be met: *
|
|
* https://www.gnu.org/licenses/agpl-3.0.html. *
|
|
* *
|
|
* Other Usage *
|
|
* Alternatively, this file may be used in accordance with the terms and *
|
|
* conditions contained in a signed written agreement between you and *
|
|
* gempa GmbH. *
|
|
***************************************************************************/
|
|
|
|
|
|
#ifndef SEISCOMP_CLIENT_PROTOCOL_H
|
|
#define SEISCOMP_CLIENT_PROTOCOL_H
|
|
|
|
|
|
#include <seiscomp/core/interruptible.h>
|
|
#include <seiscomp/core/enumeration.h>
|
|
#include <seiscomp/core/interfacefactory.h>
|
|
#include <seiscomp/core/optional.h>
|
|
#include <seiscomp/messaging/packet.h>
|
|
|
|
#include <boost/thread/mutex.hpp>
|
|
|
|
#include <deque>
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
|
|
namespace Seiscomp {
|
|
namespace Client {
|
|
|
|
|
|
PREPAREENUM(Result,
|
|
EVALUES(
|
|
/** Everything went find */
|
|
OK = 0,
|
|
/** The value/string passed is not an URL */
|
|
InvalidURL,
|
|
/** The URL parameters are not correct / not understood */
|
|
InvalidURLParameters,
|
|
/** Protocol implementation is not available */
|
|
InvalidProtocol,
|
|
/** No content encoding specified e.g. when sending messages */
|
|
ContentEncodingRequired,
|
|
/** No content type specified e.g. when sending messages */
|
|
ContentTypeRequired,
|
|
/** An unknown content encoding was received */
|
|
ContentEncodingUnknown,
|
|
/** An unknown content type was received */
|
|
ContentTypeUnknown,
|
|
/** There is already an active connection */
|
|
AlreadyConnected,
|
|
/** There is currently no active connection to handle */
|
|
NotConnected,
|
|
/** The server closed the connection */
|
|
ConnectionClosedByPeer,
|
|
/** A system error, check errno */
|
|
SystemError,
|
|
/** The connection timed out */
|
|
TimeoutError,
|
|
/** A network error, check errno */
|
|
NetworkError,
|
|
/** An application protocol error occurred */
|
|
NetworkProtocolError,
|
|
/** The requested username is connected already */
|
|
DuplicateUsername,
|
|
/** The remote group does not exist */
|
|
GroupDoesNotExist,
|
|
/** There are no inbox messages */
|
|
InboxUnderflow,
|
|
/** Too many inbox messages which are not processed */
|
|
InboxOverflow,
|
|
/** Too many unacknowledged outbox messages */
|
|
OutboxOverflow,
|
|
/** You can't subscribe again to groups you are already subscribed to */
|
|
AlreadySubscribed,
|
|
/** You can't unsubscribe from groups you are not subscribed to */
|
|
NotSubscribed,
|
|
/** Messages could not be encoded into a packet */
|
|
EncodingError,
|
|
/** Package could not be decoded into a message */
|
|
DecodingError,
|
|
/** Missing group, e.g. when sending data */
|
|
MissingGroup,
|
|
/** Invalid message type enumeration e.g. when sending messages/data */
|
|
InvalidMessageType,
|
|
/** Message too large */
|
|
MessageTooLarge,
|
|
/** Unspecified error */
|
|
Error
|
|
),
|
|
ENAMES(
|
|
"OK",
|
|
"InvalidURL",
|
|
"InvalidURLParameters",
|
|
"InvalidProtocol",
|
|
"ContentEncodingRequired",
|
|
"ContentTypeRequired",
|
|
"ContentEncodingUnknown",
|
|
"ContentTypeUnknown",
|
|
"AlreadyConnected",
|
|
"NotConnected",
|
|
"ConnectionClosedByPeer",
|
|
"SystemError",
|
|
"TimeoutError",
|
|
"NetworkError",
|
|
"NetworkProtocolError",
|
|
"DuplicateUsername",
|
|
"GroupDoesNotExist",
|
|
"InboxUnderflow",
|
|
"InboxOverflow",
|
|
"OutboxOverflow",
|
|
"AlreadySubscribed",
|
|
"NotSubscribed",
|
|
"EncodingError",
|
|
"DecodingError",
|
|
"MissingGroup",
|
|
"InvalidMessageType",
|
|
"MessageTooLarge",
|
|
"Error"
|
|
)
|
|
);
|
|
|
|
class Result : public ENUMWRAPPERCLASS(Result) {
|
|
public:
|
|
Result(Type value = Type(0)) : ENUMWRAPPERCLASS(Result)(value) {}
|
|
Type code() const { return _value; }
|
|
operator bool() const { return _value == OK; }
|
|
|
|
// Disable implicit casts to the enumeration and therefore to
|
|
// a bool value. A special bool operator is used that maps true to
|
|
// OK.
|
|
private:
|
|
operator Type() const { return ENUMWRAPPERCLASS(Result)::operator Type(); }
|
|
};
|
|
|
|
|
|
|
|
DEFINE_SMARTPOINTER(Protocol);
|
|
|
|
/**
|
|
* @brief The abstract class Protocol implements the low-level message
|
|
* transport protocol.
|
|
*
|
|
* The protocol handles one connection to a messaging broker and supports
|
|
* the publish/subscribe pattern. It must implement the methods to subscribe
|
|
* to a group, unsubscribe from a group and to receive and send messages.
|
|
* Messages are just binary blobs with associated metadata. The message
|
|
* content is not interpreted or parsed in any way.
|
|
*
|
|
* @code
|
|
* ProtocolPtr proto = Protocol::Create("scmp");
|
|
* if ( !proto ) exit(1);
|
|
* if ( proto->connect("localhost") ) {
|
|
* cerr << "Connection failed" << endl;
|
|
* exit(1);
|
|
* }
|
|
*
|
|
* cout << "Connected as " << proto->clientName() << endl;
|
|
* proto->subscribe("PICK");
|
|
*
|
|
* PacketPtr p;
|
|
* while ( (p = c->recv() ) {
|
|
* cout << "Packet from " << p->sender() << " to " << p->target << endl;
|
|
* }
|
|
*
|
|
* proto->disconnect();
|
|
* @endcode
|
|
*
|
|
* ## Message Types
|
|
* ### Regular
|
|
*
|
|
* Regular messages will be queued, receive a sequence number and their
|
|
* content will not be touched by the server unless certain processing
|
|
* profiles are enabled. That is the default mode of the messaging API.
|
|
*
|
|
* ### Transient
|
|
*
|
|
* Transient messages are like regular messages but they are not queued and
|
|
* will not be processed. A client that connects to continue with a sequence
|
|
* number will not receive such a message.
|
|
*
|
|
* ### Service
|
|
*
|
|
* Service messages are like transient messages but are evaluated by the
|
|
* server. They are mainly used to interact with the server. State-of-health
|
|
* messages and sync messages are typical service messages.
|
|
*/
|
|
class SC_SYSTEM_CLIENT_API Protocol : public Core::InterruptibleObject {
|
|
// ----------------------------------------------------------------------
|
|
// Public types
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
struct State {
|
|
State();
|
|
uint64_t localSequenceNumber;
|
|
OPT(uint64_t) sequenceNumber;
|
|
uint64_t receivedMessages;
|
|
uint64_t sentMessages;
|
|
uint64_t bytesSent;
|
|
uint64_t bytesReceived;
|
|
uint64_t bytesBuffered;
|
|
uint64_t maxBufferedBytes;
|
|
uint64_t maxInboxSize;
|
|
uint64_t maxOutboxSize;
|
|
uint64_t systemReadCalls;
|
|
uint64_t systemWriteCalls;
|
|
};
|
|
|
|
MAKEENUM(
|
|
ContentEncoding,
|
|
EVALUES(
|
|
Identity,
|
|
Deflate,
|
|
GZip,
|
|
LZ4
|
|
),
|
|
ENAMES(
|
|
"identity",
|
|
"deflate",
|
|
"gzip",
|
|
"lz4"
|
|
)
|
|
);
|
|
|
|
MAKEENUM(
|
|
ContentType,
|
|
EVALUES(
|
|
Binary,
|
|
JSON,
|
|
BSON,
|
|
XML,
|
|
IMPORTED_XML,
|
|
Text
|
|
),
|
|
ENAMES(
|
|
"application/x-sc-bin",
|
|
"text/json",
|
|
"application/x-sc-bson",
|
|
"application/x-sc-xml",
|
|
"text/xml",
|
|
"text/plain"
|
|
)
|
|
);
|
|
|
|
MAKEENUM(
|
|
MessageType,
|
|
EVALUES(
|
|
Regular,
|
|
Transient,
|
|
Status
|
|
),
|
|
ENAMES(
|
|
"regular",
|
|
"transient",
|
|
"status"
|
|
)
|
|
);
|
|
|
|
static const std::string STATUS_GROUP;
|
|
static const std::string LISTENER_GROUP;
|
|
static const std::string IMPORT_GROUP;
|
|
|
|
//! A list of group names
|
|
using Groups = std::set<std::string>;
|
|
using KeyValueStore = std::map<std::string, std::string>;
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// X'truction
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
Protocol();
|
|
virtual ~Protocol();
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Public interface
|
|
// ----------------------------------------------------------------------
|
|
public:
|
|
/**
|
|
* @brief Sets whether membership info of clients should be received.
|
|
* This information informs about when another client enters
|
|
* one of the groups this connection is subscribed to, when
|
|
* another clients leaves one of the groups this connection is
|
|
* subscribed to and when a client disconnects from the
|
|
* broker.
|
|
* This method has to be called prior to connect to have an
|
|
* affect.
|
|
* @param enable true if membership info should be received, false
|
|
* otherwise.
|
|
*/
|
|
void setMembershipInfo(bool enable);
|
|
|
|
/**
|
|
* @brief connect
|
|
* @param address The connection address, e.g. host:port/queue
|
|
* @param timeoutMs The timeout in milliseconds
|
|
* @param clientName The desirec client name. If it is nullptr then the
|
|
* server will choose a random client name.
|
|
* @return
|
|
*/
|
|
virtual Result connect(const char *address, unsigned int timeoutMs,
|
|
const char *clientName = nullptr) = 0;
|
|
|
|
Result connect(const std::string &address, unsigned int timeoutMs,
|
|
const std::string &clientName = std::string());
|
|
|
|
/**
|
|
* @brief Returns the schema version supported by the remote end.
|
|
* This requires a successfull connection to be valid.
|
|
* @return The version of the remote schema
|
|
*/
|
|
Core::Version schemaVersion() const;
|
|
|
|
/**
|
|
* @brief Returns configuration parameters as key-value store
|
|
* returned by the broker.
|
|
* Those parameters can be used or not, it is up to the user of
|
|
* the connection.
|
|
* @return The key-value store with additional parameters
|
|
*/
|
|
const KeyValueStore &extendedParameters() const;
|
|
|
|
/**
|
|
* @brief Returns the client name. Either the one given during connect
|
|
* or the one assigned by the server.
|
|
* @return The client name
|
|
*/
|
|
const std::string &clientName() const;
|
|
|
|
/**
|
|
* @brief Subscribes to a group which must exist
|
|
* @param group The group name
|
|
* @return Result code
|
|
*/
|
|
virtual Result subscribe(const std::string &group) = 0;
|
|
|
|
/**
|
|
* @brief Unsubscribes from a group where the client was subscribed to.
|
|
* @param group The group name
|
|
* @return Result code
|
|
*/
|
|
virtual Result unsubscribe(const std::string &group) = 0;
|
|
|
|
/**
|
|
* @brief Sends data with a particular content type.
|
|
* @param targetGroup The group name to send the message to
|
|
* @param data The data pointer to the octett stream.
|
|
* @param len Length in bytes of the octett stream.
|
|
* @param encoding The data content encoding.
|
|
* @param type The data content type.
|
|
* @return Result code
|
|
*/
|
|
virtual Result sendData(const std::string &targetGroup,
|
|
const char *data, size_t len,
|
|
MessageType type,
|
|
ContentEncoding contentEncoding,
|
|
ContentType contentType) = 0;
|
|
|
|
/**
|
|
* @brief Sends a message. A message is actually a binary stream
|
|
* of certain length. Optionally a content type can be
|
|
* associated with it.
|
|
* @param targetGroup The group name to send the message to
|
|
* @param msg The message pointer.
|
|
* @param contentType An optional content type which will be or
|
|
* won't be associated with the packet. This is
|
|
* implementation specific.
|
|
* @return Result code
|
|
*/
|
|
virtual Result sendMessage(const std::string &targetGroup,
|
|
const Core::Message *msg,
|
|
MessageType type = Regular,
|
|
OPT(ContentEncoding) contentEncoding = Core::None,
|
|
OPT(ContentType) contentType = Core::None) = 0;
|
|
|
|
/**
|
|
* @brief Receives a data packet.
|
|
* The call will block if the local inbox is empty.
|
|
* @param p The packet instance which will hold the packet
|
|
* information and the payload.
|
|
* @return Result code
|
|
*/
|
|
virtual Result recv(Packet &p) = 0;
|
|
|
|
/**
|
|
* @brief Receives a data packet.
|
|
* The call will block if the local inbox is empty.
|
|
* @param result An optional storage for the result code
|
|
* @return A packet pointer which must be handled by the caller.
|
|
*/
|
|
virtual Packet *recv(Result *result = nullptr) = 0;
|
|
|
|
/**
|
|
* @brief Waits for a new message to arrive so that a subsequent call
|
|
* to \ref recv will return immediately.
|
|
* @return Result code
|
|
*/
|
|
virtual Result fetchInbox() = 0;
|
|
|
|
/**
|
|
* @brief Synchronizes the outbox with the remote server.
|
|
* @details This means if this method returns successfully then all
|
|
* sent messages have been acknowledged by the remote end.
|
|
* This operation blocks until all messages have been
|
|
* acknowledged.
|
|
* Writing is not possible while the function is running even
|
|
* from another thread.
|
|
* Usually this function does not need to be called from
|
|
* user code. It is there for completeness to allow clients
|
|
* to be sure that all messages were handled by the server
|
|
* at a certain point in time without having to disconnect.
|
|
* @return Result code
|
|
*/
|
|
virtual Result syncOutbox() = 0;
|
|
|
|
/**
|
|
* @brief Disconnects gracefully from the broker. It sends a disconnect
|
|
* message and wait for the receipt. In contrast to close, this
|
|
* is the nice way to say 'good bye'.
|
|
* @return Result code
|
|
*/
|
|
virtual Result disconnect() = 0;
|
|
|
|
/**
|
|
* @brief Checks for an active connection.
|
|
* @return True if a connection is established, false otherwise
|
|
*/
|
|
virtual bool isConnected() = 0;
|
|
|
|
/**
|
|
* @brief Closes the underlying socket and resources. This is the
|
|
* hard way to close a connection.
|
|
* @return Result code
|
|
*/
|
|
virtual Result close() = 0;
|
|
|
|
/**
|
|
* @brief Sets the read timeout for receiving a message. If the
|
|
* timeout hits then TimeoutError is returned.
|
|
* @param milliseconds The timeout in milliseconds. Zero or a negative
|
|
* value disabled the timeout.
|
|
* @return Result code
|
|
*/
|
|
virtual Result setTimeout(int milliseconds) = 0;
|
|
|
|
/**
|
|
* @brief Returns the names of the available message groups.
|
|
* @details This call only returns valid content after a connection
|
|
* was established. If a reconnect occurred during the
|
|
* lifetime of the connection and the groups of the queue
|
|
* have changed then this will be reflected in the returned
|
|
* vector.
|
|
* @return The available groups
|
|
*/
|
|
const Groups &groups() const;
|
|
|
|
bool erroneous() const;
|
|
|
|
/**
|
|
* @brief Returns the error message corresponding to the last
|
|
* erroneous call.
|
|
* @return The message or empty.
|
|
*/
|
|
const std::string &lastErrorMessage() const;
|
|
|
|
const State &state() const;
|
|
|
|
/**
|
|
* @brief Queries the local message inbox size. It is safe to call this
|
|
* method from separate threads.
|
|
* @return The number of messages queued in the local inbox.
|
|
*/
|
|
size_t inboxSize() const;
|
|
|
|
/**
|
|
* @brief Queries the local message outbox size. If a message is still
|
|
* in the outbox does not mean that is hasn't been sent over the
|
|
* wire. It just means that the server hasn't acknowledged the
|
|
* message yet and that in case of a reconnect the outbox will
|
|
* be sent again.
|
|
* @return The number of unacknowledged messages.
|
|
*/
|
|
virtual size_t outboxSize() const = 0;
|
|
|
|
void setCertificate(const std::string &cert);
|
|
|
|
static Core::Message *decode(const std::string &blob,
|
|
ContentEncoding encoding,
|
|
ContentType type);
|
|
|
|
static Core::Message *decode(const char *blob, size_t blob_length,
|
|
ContentEncoding encoding,
|
|
ContentType type);
|
|
|
|
static bool encode(std::string &blob, const Core::Message *msg,
|
|
ContentEncoding encoding, ContentType type,
|
|
int schemaVersion);
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Interruptible interface
|
|
// ----------------------------------------------------------------------
|
|
protected:
|
|
virtual void handleInterrupt(int) override;
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Protected methods
|
|
// ----------------------------------------------------------------------
|
|
protected:
|
|
void queuePacket(Packet *p);
|
|
|
|
/**
|
|
* Clears all messages in the inbox. This method is not intended for
|
|
* public use. Note that it does not lock the read mutex.
|
|
*/
|
|
void clearInbox();
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Private types and members
|
|
// ----------------------------------------------------------------------
|
|
protected:
|
|
using PacketQueue = std::deque<Packet*>;
|
|
|
|
bool _wantMembershipInfo;
|
|
Groups _groups;
|
|
PacketQueue _inbox;
|
|
std::string _errorMessage;
|
|
State _state;
|
|
std::string _registeredClientName;
|
|
Core::Version _schemaVersion; //!< The schema version the
|
|
//!< server supports
|
|
KeyValueStore _extendedParameters;
|
|
std::string _certificate; //!< Optional client certificate
|
|
|
|
// Mutexes to synchronize read access from separate threads.
|
|
mutable boost::mutex _readMutex;
|
|
};
|
|
|
|
|
|
DEFINE_INTERFACE_FACTORY(Protocol);
|
|
|
|
#define REGISTER_CONNECTION_PROTOCOL(Class, Service) \
|
|
Seiscomp::Core::Generic::InterfaceFactory<Seiscomp::Client::Protocol, Class> __##Class##InterfaceFactory__(Service)
|
|
|
|
|
|
inline Core::Version Protocol::schemaVersion() const {
|
|
return _schemaVersion;
|
|
}
|
|
|
|
inline const Protocol::KeyValueStore &Protocol::extendedParameters() const {
|
|
return _extendedParameters;
|
|
}
|
|
|
|
inline const std::string &Protocol::clientName() const {
|
|
return _registeredClientName;
|
|
}
|
|
|
|
inline bool Protocol::erroneous() const {
|
|
return !_errorMessage.empty();
|
|
}
|
|
|
|
inline const std::string &Protocol::lastErrorMessage() const {
|
|
return _errorMessage;
|
|
}
|
|
|
|
inline const Protocol::State &Protocol::state() const {
|
|
return _state;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|