More checks in network ping-pong.
[GoMoku3D.git] / src / network / StreamSocket.cpp
blob424f7e9e8082b97ee9cd32c3ceff54a7ea3a94a5
1 /********************************************************************
3 * Copyright (C) 2008 Davide Pesavento
5 * This file is part of GoMoku3D.
7 * GoMoku3D is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * GoMoku3D is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with GoMoku3D. If not, see <http://www.gnu.org/licenses/>.
20 *******************************************************************/
22 #include <QCoreApplication>
23 #include <QVariant>
25 #include "StreamSocket.h"
27 #define HANDLER_SIGNATURE(token) \
28 void StreamSocket::parse_##token() { \
29 QString elementName = name().toString(); \
30 Q_ASSERT_X(elementName == #token, \
31 (QString("StreamSocket::parse_") + #token + "()").toUtf8().constData(), \
32 ("called while parsing " + elementName).toUtf8().constData()); \
33 SET_HANDLER(#token)
35 const QString StreamSocket::_supportedProtocolVersion = "1.0";
37 StreamSocket::StreamSocket(QTcpSocket *socket)
38 : QXmlStreamReader(socket), QXmlStreamWriter(socket)
40 Q_ASSERT_X(socket != 0, "StreamSocket::StreamSocket()", "socket must not be null");
42 _socket = socket;
43 _socket->setParent(this);
44 _state = Unconnected;
45 _pingTimer.setInterval(20000);
46 _pongTimer.setInterval(10000);
47 _pongTimer.setSingleShot(true);
49 connect(_socket, SIGNAL(readyRead()), this, SLOT(parseData()));
50 connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(handleError(QAbstractSocket::SocketError)));
51 connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
52 connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(resetTimer()));
53 connect(&_pongTimer, SIGNAL(timeout()), this, SLOT(timedOut()));
54 connect(this, SIGNAL(ping()), this, SLOT(sendPong()));
55 connect(this, SIGNAL(pong()), &_pongTimer, SLOT(stop()));
58 StreamSocket::~StreamSocket()
60 closeStream();
61 _socket->deleteLater();
62 QCoreApplication::sendPostedEvents(_socket, 0);
65 void StreamSocket::changeState(ProtocolState state)
67 _state = state;
69 if (state == Connecting) {
70 emit statusChanged(YELLOW_TEXT(tr("resolving hostname...")));
71 } else if (state == Connected) {
72 openStream();
73 } else if (state == OpeningStream) {
74 emit statusChanged(YELLOW_TEXT(tr("negotiating stream...")));
75 } else if (state == FullyOpened) {
76 emit statusChanged(GREEN_TEXT(tr("connected")));
77 emit handshakeCompleted(this);
78 _pingTimer.start();
79 } else if (state == Closing) {
80 closeStream();
81 } else if (state == Closed) {
82 emit statusChanged(RED_TEXT(tr("not connected")));
86 StreamSocket::ProtocolState StreamSocket::state() const
88 return _state;
91 QString StreamSocket::stateString() const
93 switch(_state) {
94 case Unconnected: return "Unconnected";
95 case Listening: return "Listening";
96 case Connecting: return "Connecting";
97 case Connected: return "Connected";
98 case OpeningStream: return "OpeningStream";
99 case FullyOpened: return "FullyOpened";
100 case Idle: return "Idle";
101 case AwaitingJoinRequest: return "AwaitingJoinRequest";
102 case AwaitingJoinAnswer: return "AwaitingJoinAnswer";
103 case AwaitingPlayers: return "AwaitingPlayers";
104 case AwaitingGameStart: return "AwaitingGameStart";
105 case Playing: return "Playing";
106 case AwaitingMove: return "AwaitingMove";
107 case Closing: return "Closing";
108 case Closed: return "Closed";
109 default: return "Unknown";
113 Move StreamSocket::takeFirstMove()
115 if (_buffer.isEmpty()) {
116 return Move();
117 } else {
118 return _buffer.takeFirst();
122 void StreamSocket::sendChatMessage(QString sender, QString msg)
124 if (msg.isEmpty()) {
125 return;
127 if (sender.isEmpty()) {
128 if (_localPlayerName.isEmpty()) {
129 WARN(tr("refusing to send <chatMessage> with empty sender"));
130 return;
132 sender = _localPlayerName;
135 writeStartElement("chatMessage");
136 writeAttribute("from", sender);
137 writeCharacters(msg);
138 writeEndElement();
141 void StreamSocket::sendMove(Move move)
143 serialize(move);
146 void StreamSocket::openStream()
148 changeState(OpeningStream);
150 writeStartDocument();
151 writeDefaultNamespace("http://www.itworks.org/GoMoku3D/NetworkProtocol");
152 writeStartElement("stream");
153 writeAttribute("version", _supportedProtocolVersion);
154 writeEmptyElement("foo");
157 void StreamSocket::closeStream()
159 _pingTimer.stop();
160 _pongTimer.stop();
162 if (_socket->state() == QAbstractSocket::ConnectedState) {
163 writeEndElement();
164 writeEndDocument();
165 _socket->disconnectFromHost();
168 changeState(Closed);
171 void StreamSocket::handleError(QAbstractSocket::SocketError)
173 emit protocolError(_socket->errorString());
176 void StreamSocket::timedOut()
178 emit protocolError(tr("Communication with the remote host timed out"));
181 void StreamSocket::serialize(Move m)
183 writeStartElement("move");
184 writeTextElement("player", QString::number(m.playerId()));
185 writeStartElement("point");
186 writeTextElement("x", QString::number(m.point().x()));
187 writeTextElement("y", QString::number(m.point().y()));
188 writeTextElement("z", QString::number(m.point().z()));
189 writeEndElement();
190 writeEndElement();
193 void StreamSocket::sendPing()
195 if (_socket->state() == QAbstractSocket::ConnectedState) {
196 writeEmptyElement("ping");
197 writeEmptyElement("ping");
201 void StreamSocket::sendPong()
203 if (_socket->state() == QAbstractSocket::ConnectedState) {
204 writeEmptyElement("pong");
205 writeEmptyElement("pong");
209 void StreamSocket::resetTimer()
211 _pongTimer.start(10000);
214 bool StreamSocket::parse(QString elementName)
216 if (elementName.isEmpty()) {
217 return false;
220 QString methodName = "parse_" + elementName;
221 if (QMetaObject::invokeMethod(this, methodName.toUtf8().constData(), Qt::DirectConnection)) {
222 return true;
223 } else {
224 if (elementName != "foo") {
225 WARN(tr("unknown protocol message %1").arg("<" + elementName + ">"));
227 return false;
231 void StreamSocket::parseData()
233 while (!atEnd()) {
234 if (parse(_currentHandler)) {
235 continue;
238 QXmlStreamReader::TokenType token = readNext();
240 switch (token) {
241 case StartDocument:
242 LOG("document start");
243 // do nothing
244 break;
245 case StartElement:
246 parse(name().toString());
247 break;
248 case EndDocument:
249 LOG("document end");
250 // do nothing
251 break;
252 case EndElement:
253 case Comment:
254 case Invalid:
255 // do nothing
256 break;
257 default:
258 LOG("unable to handle unexpected token {" + tokenString() + "}");
262 if (hasError()) {
263 switch (error()) {
264 case NoError:
265 case PrematureEndOfDocumentError:
266 // do nothing
267 break;
268 default:
269 emit protocolError(errorString());
274 BEGIN_TERMINAL_HANDLER(stream)
275 QString protocolVersion = attributes().value("version").toString();
276 if (atEnd()) return;
277 if (protocolVersion == _supportedProtocolVersion) {
278 changeState(FullyOpened);
279 } else {
280 changeState(Closing);
282 RESTORE_HANDLER("")
283 END_TERMINAL_HANDLER
285 HANDLER_SIGNATURE(playerJoined)
286 if (isStartElement() && elementName == "playerJoined") {
287 bool ok;
288 int id = attributes().value("id").toString().toInt(&ok);
289 if (!ok) {
290 WARN(tr("invalid value for attribute 'id' in <playerJoined> : %1").arg(attributes().value("id").toString()));
291 id = -1;
293 setProperty("playerJoined/id", id);
295 while (!atEnd()) {
296 readNext();
297 elementName = name().toString();
298 if (isStartElement()) {
299 parse(elementName);
300 } else if (isEndElement()) {
301 Q_ASSERT(elementName == "playerJoined");
302 emit playerJoined(property("playerJoined/id").toInt(),
303 property("playerJoined/name").toString(),
304 property("playerJoined/type").toString());
305 RESTORE_HANDLER("")
306 return;
307 END_NONTERMINAL_HANDLER(playerJoined)
309 BEGIN_TERMINAL_HANDLER(name)
310 setProperty("playerJoined/name", QVariant(readElementText()));
311 RESTORE_HANDLER("playerJoined")
312 END_TERMINAL_HANDLER
314 BEGIN_TERMINAL_HANDLER(color)
315 LOG("ignoring <color> element in <playerJoined> message");
316 RESTORE_HANDLER("playerJoined")
317 END_TERMINAL_HANDLER
319 BEGIN_TERMINAL_HANDLER(type)
320 setProperty("playerJoined/type", QVariant(readElementText()));
321 RESTORE_HANDLER("playerJoined")
322 END_TERMINAL_HANDLER
324 BEGIN_TERMINAL_HANDLER(playerLeft)
325 bool ok;
326 int id = readElementText().toInt(&ok);
327 if (ok) {
328 emit playerLeft(id);
329 } else {
330 WARN(tr("invalid content in <playerLeft> : %1").arg(readElementText()));
332 RESTORE_HANDLER("")
333 END_TERMINAL_HANDLER
335 BEGIN_TERMINAL_HANDLER(chatMessage)
336 QString from = attributes().value("from").toString();
337 if (atEnd()) return;
338 QString msg = readElementText();
339 if (from.isEmpty() && msg.isEmpty()) {
340 LOG("dropping empty <chatMessage>");
341 } else if (from.isEmpty()) {
342 LOG("dropping <chatMessage> without sender");
343 } else if (msg.isEmpty()) {
344 LOG("dropping <chatMessage> with empty message");
345 } else if (state() == Playing || state() == AwaitingMove) {
346 emit receivedChatMessage(from, msg);
347 } else {
348 LOG("dropping <chatMessage> (state = " + stateString() + ")");
350 RESTORE_HANDLER("")
351 END_TERMINAL_HANDLER
353 BEGIN_NONTERMINAL_HANDLER(move)
354 Move m(property("move/player").toInt(),
355 property("move/point").value<Point>());
356 if (state() == AwaitingMove) {
357 emit receivedMove(m);
358 } else if (state() == Playing) {
359 _buffer.append(m);
360 } else {
361 LOG("dropping <move> (state = " + stateString() + ")");
363 RESTORE_HANDLER("")
364 return;
365 END_NONTERMINAL_HANDLER(move)
367 BEGIN_TERMINAL_HANDLER(player)
368 bool ok;
369 int id = readElementText().toInt(&ok);
370 if (!ok) {
371 WARN(tr("invalid <player> value in <move> : %1").arg(readElementText()));
372 id = -1;
374 setProperty("move/player", QVariant(id));
375 RESTORE_HANDLER("move")
376 END_TERMINAL_HANDLER
378 BEGIN_NONTERMINAL_HANDLER(point)
379 Point p(property("move/point/x").toInt(),
380 property("move/point/y").toInt(),
381 property("move/point/z").toInt());
382 setProperty("move/point", QVariant::fromValue(p));
383 RESTORE_HANDLER("move")
384 return;
385 END_NONTERMINAL_HANDLER(point)
387 BEGIN_TERMINAL_HANDLER(x)
388 bool ok;
389 int value = readElementText().toInt(&ok);
390 if (!ok) {
391 WARN(tr("invalid <x> value in <point> : %1").arg(readElementText()));
392 value = -1;
394 setProperty("move/point/x", QVariant(value));
395 RESTORE_HANDLER("point")
396 END_TERMINAL_HANDLER
398 BEGIN_TERMINAL_HANDLER(y)
399 bool ok;
400 int value = readElementText().toInt(&ok);
401 if (!ok) {
402 WARN(tr("invalid <y> value in <point> : %1").arg(readElementText()));
403 value = -1;
405 setProperty("move/point/y", QVariant(value));
406 RESTORE_HANDLER("point")
407 END_TERMINAL_HANDLER
409 BEGIN_TERMINAL_HANDLER(z)
410 bool ok;
411 int value = readElementText().toInt(&ok);
412 if (!ok) {
413 WARN(tr("invalid <z> value in <point> : %1").arg(readElementText()));
414 value = -1;
416 setProperty("move/point/z", QVariant(value));
417 RESTORE_HANDLER("point")
418 END_TERMINAL_HANDLER
420 BEGIN_TERMINAL_HANDLER(startGame)
421 readNext();
422 if (atEnd()) return;
423 elementName = name().toString();
424 if (isEndElement()) {
425 Q_ASSERT(elementName == "startGame");
426 RESTORE_HANDLER("")
427 if (state() == AwaitingGameStart) {
428 changeState(Playing);
429 emit startGame();
431 } else {
432 LOG("unexpected data in <startGame> - ignoring");
434 END_TERMINAL_HANDLER
436 BEGIN_TERMINAL_HANDLER(ping)
437 readNext();
438 if (atEnd()) return;
439 elementName = name().toString();
440 if (isEndElement()) {
441 Q_ASSERT(elementName == "ping");
442 RESTORE_HANDLER("")
443 emit ping();
444 } else {
445 LOG("unexpected data in <ping> - ignoring");
447 END_TERMINAL_HANDLER
449 BEGIN_TERMINAL_HANDLER(pong)
450 readNext();
451 if (atEnd()) return;
452 elementName = name().toString();
453 if (isEndElement()) {
454 Q_ASSERT(elementName == "pong");
455 RESTORE_HANDLER("")
456 emit pong();
457 } else {
458 LOG("unexpected data in <pong> - ignoring");
460 END_TERMINAL_HANDLER