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