1 /*
2  * Stellarium Remote Sync plugin
3  * Copyright (C) 2015 Florian Schaukowitsch
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 #ifndef SYNCPROTOCOL_HPP
21 #define SYNCPROTOCOL_HPP
22 
23 #include <QByteArray>
24 #include <QDataStream>
25 #include <QAbstractSocket>
26 #include <QUuid>
27 
28 //! Contains sync protocol data definitions shared between client and server
29 namespace SyncProtocol
30 {
31 
32 //Important: All data should use the sized typedefs provided by Qt (i.e. qint32 instead of 4 byte int on x86)
33 
34 //! Should be changed with every breaking change
35 const quint8 SYNC_PROTOCOL_VERSION = 2;
36 const QDataStream::Version SYNC_DATASTREAM_VERSION = QDataStream::Qt_5_0;
37 //! Magic value for protocol used during connection. Should NEVER change.
38 const QByteArray SYNC_MAGIC_VALUE = "StellariumSyncPluginProtocol";
39 
40 typedef quint16 tPayloadSize;
41 
42 //! All messages are preceded by this
43 struct SyncHeader
44 {
45 	quint8 msgType; //The SyncMessageType of the data
46 	SyncProtocol::tPayloadSize dataSize; //The size of the data part
47 };
48 
49 //! Write a SyncHeader to a DataStream
50 QDataStream& operator<<(QDataStream& out, const SyncHeader& header);
51 //! Read a SyncHeader from a DataStream
52 QDataStream& operator>>(QDataStream& in, SyncHeader& header);
53 
54 const qint64 SYNC_HEADER_SIZE = sizeof(quint8) + sizeof(tPayloadSize); //3 byte
55 const qint64 SYNC_MAX_PAYLOAD_SIZE = (2<<15) - 1; // 65535
56 const qint64 SYNC_MAX_MESSAGE_SIZE = SYNC_HEADER_SIZE + SYNC_MAX_PAYLOAD_SIZE;
57 
58 //! Contains the possible message types. The enum value is used as an ID to identify the message type over the network.
59 //! The classes handling these messages are defined in SyncMessages.hpp
60 enum SyncMessageType
61 {
62 	ERROR, //sent to the other party on protocol/auth error with a message, connection will be dropped afterwards
63 	SERVER_CHALLENGE, //sent as a challenge to the client on establishment of connection
64 	CLIENT_CHALLENGE_RESPONSE, //sent as a reply to the challenge
65 	SERVER_CHALLENGERESPONSEVALID, //sent from the server to the client after valid client hello was received.
66 	ALIVE, //sent from a peer after no data was sent for about 5 seconds to indicate it is still alive
67 
68 	//all messages below here can only be sent from authenticated peers
69 	TIME, //time jumps + time scale updates
70 	LOCATION, //location changes
71 	SELECTION, //current selection changed
72 	STELPROPERTY, //stelproperty updates
73 	VIEW, //view change
74 	FOV, //fov change
75 
76 	MSGTYPE_MAX = FOV,
77 	MSGTYPE_SIZE = MSGTYPE_MAX+1
78 };
79 
operator <<(QDebug & deb,SyncMessageType msg)80 inline QDebug& operator<<(QDebug& deb, SyncMessageType msg)
81 {
82 	switch (msg) {
83 		case SyncProtocol::ERROR:
84 			deb<<"ERROR";
85 			break;
86 		case SyncProtocol::SERVER_CHALLENGE:
87 			deb<<"SERVER_CHALLENGE";
88 			break;
89 		case SyncProtocol::CLIENT_CHALLENGE_RESPONSE:
90 			deb<<"CLIENT_CHALLENGE_RESPONSE";
91 			break;
92 		case SyncProtocol::SERVER_CHALLENGERESPONSEVALID:
93 			deb<<"SERVER_CHALLENGERESPONSEVALID";
94 			break;
95 		case SyncProtocol::TIME:
96 			deb<<"TIME";
97 			break;
98 		case SyncProtocol::LOCATION:
99 			deb<<"LOCATION";
100 			break;
101 		case SyncProtocol::SELECTION:
102 			deb<<"SELECTION";
103 			break;
104 		case SyncProtocol::STELPROPERTY:
105 			deb<<"STELPROPERTY";
106 			break;
107 		case SyncProtocol::VIEW:
108 			deb<<"VIEW";
109 			break;
110 		case SyncProtocol::FOV:
111 			deb<<"FOV";
112 			break;
113 		case SyncProtocol::ALIVE:
114 			deb<<"ALIVE";
115 			break;
116 		default:
117 			deb<<"UNKNOWN("<<int(msg)<<')';
118 			break;
119 	}
120 	return deb;
121 }
122 
123 //! Base interface for the messages themselves, allowing to serialize/deserialize them
124 class SyncMessage
125 {
126 public:
~SyncMessage()127 	virtual ~SyncMessage() {}
128 
129 	//! Subclasses must return the message type this message represents
130 	virtual SyncProtocol::SyncMessageType getMessageType() const = 0;
131 
132 	//! Writes a full message (with header) to the specified byte array.
133 	//! Returns the total message size. If zero, the payload is too large for a SyncMessage.
134 	qint64 createFullMessage(QByteArray& target) const;
135 
136 	//! Subclasses should override this to serialize their contents to the data stream.
137 	//! The default implementation writes nothing.
138 	virtual void serialize(QDataStream& stream) const;
139 	//! Subclasses should override this to load their contents from the data stream.
140 	//! The default implementation expects a zero dataSize, and reads nothing.
141 	virtual bool deserialize(QDataStream& stream, SyncProtocol::tPayloadSize dataSize);
142 
143 	//! Subclasses can override this to provide proper debug output.
144 	//! The default just prints the message type.
debugOutput(QDebug dbg) const145 	virtual QDebug debugOutput(QDebug dbg) const
146 	{
147 		return dbg;
148 	}
149 
operator <<(QDebug dbg,const SyncMessage & msg)150 	friend QDebug operator<<(QDebug dbg, const SyncMessage& msg)
151 	{
152 		dbg = dbg<<msg.getMessageType()<<'[';
153 		dbg = msg.debugOutput(dbg);
154 		return dbg<<']';
155 	}
156 
157 protected:
158 	static void writeString(QDataStream& stream, const QString& str);
159 	static QString readString(QDataStream& stream);
160 };
161 
162 }
163 
164 class SyncMessageHandler;
165 
166 //! Handling the connection to a remote peer (i.e. all clients on the server, and the server on the client)
167 class SyncRemotePeer : public QObject
168 {
169 	Q_OBJECT
170 public:
171 	SyncRemotePeer(QAbstractSocket* socket, bool isServer, const QVector<SyncMessageHandler*>& handlerList);
172 	~SyncRemotePeer();
173 
174 
175 
176 	//! Sends a message to this peer
177 	void writeMessage(const SyncProtocol::SyncMessage& msg);
178 	//! Writes this data packet to the socket without processing
179 	void writeData(const QByteArray& data, qint64 size=-1);
180 	//! Can be used to write an error message to the peer and drop the connection
181 	void writeError(const QString& err);
182 
183 	//! Log a message for this peer
184 	void peerLog(const QString& msg) const;
185 	QDebug peerLog() const;
186 
isAuthenticated() const187 	bool isAuthenticated() const { return authenticated; }
getID() const188 	QUuid getID() const { return id; }
189 
190 	void checkTimeout();
191 	void disconnectPeer();
192 
getError() const193 	QString getError() const { return errorString; }
194 signals:
195 	void disconnected(bool cleanDisconnect);
196 private slots:
197 	void sockDisconnected();
198 	void sockError(QAbstractSocket::SocketError err);
199 	void sockStateChanged(QAbstractSocket::SocketState state);
200 
201 	//! Call this to try to read message data from the socket.
202 	//! If a fully formed message is currently buffered, it is processed.
203 	void receiveMessage();
204 private:
205 	QAbstractSocket* sock; // The socket for communication with this peer
206 	QDataStream stream;
207 	QString errorString;
208 	bool expectDisconnect;
209 	bool isPeerAServer; // True if this identifies a server
210 	QUuid id; // An ID value, currently not used for anything else than auth. The server always has a NULL UUID.
211 	bool authenticated; // True if the peer ran through the HELLO process and can receive/send all message types
212 	bool authResponseSent; //only for client use, tracks if the client has sent a resonse to the server challenge
213 	bool waitingForBody; //True if waiting for full message body (after header was received)
214 	SyncProtocol::SyncHeader msgHeader; //the last message header read/currently being processed
215 	qint64 lastReceiveTime; // The time the last data of this peer was received
216 	qint64 lastSendTime; //The time the last data was written to this peer
217 	QVector<SyncMessageHandler*> handlerList;
218 	QByteArray msgWriteBuffer; //Byte array used to construct messages before writing them
219 
220 	friend class ServerAuthHandler;
221 	friend class ClientAuthHandler;
222 };
223 
224 //! Base interface for message handlers, i.e. reacting to messages
225 class SyncMessageHandler
226 {
227 public:
~SyncMessageHandler()228 	virtual ~SyncMessageHandler() {}
229 
230 	//! Read a message directly from the stream. SyncMessage::deserialize of the correct class should be used to deserialize the message.
231 	//! @param stream The stream to be used to read data. The current position is after the header.
232         //! @param dataSize The data size from the message header
233 	//! @param peer The remote peer this message originated from. Can be used to send replies through SyncRemotePeer::writeMessage
234 	//! @return return false if the message is found to be invalid. The connection to the client/server will be dropped.
235 	virtual bool handleMessage(QDataStream& stream, SyncProtocol::tPayloadSize dataSize,  SyncRemotePeer& peer) = 0;
236 };
237 
238 //! Skips the message, and does nothing else.
239 class DummyMessageHandler : public SyncMessageHandler
240 {
241 public:
handleMessage(QDataStream & stream,SyncProtocol::tPayloadSize dataSize,SyncRemotePeer & peer)242 	virtual bool handleMessage(QDataStream &stream, SyncProtocol::tPayloadSize dataSize, SyncRemotePeer &peer)
243 	{
244 		Q_UNUSED(peer)
245 		stream.skipRawData(dataSize);
246 		return !stream.status();
247 	}
248 };
249 
250 Q_DECLARE_INTERFACE(SyncMessageHandler,"Stellarium/RemoteSync/SyncMessageHandler/1.0")
251 
252 
253 #endif
254