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>
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()); \
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");
43 _socket
->setParent(this);
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()
61 _socket
->deleteLater();
62 QCoreApplication::sendPostedEvents(_socket
, 0);
65 void StreamSocket::changeState(ProtocolState state
)
69 if (state
== Connecting
) {
70 emit
statusChanged(YELLOW_TEXT(tr("resolving hostname...")));
71 } else if (state
== Connected
) {
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);
79 } else if (state
== Closing
) {
81 } else if (state
== Closed
) {
82 emit
statusChanged(RED_TEXT(tr("not connected")));
86 StreamSocket::ProtocolState
StreamSocket::state() const
91 QString
StreamSocket::stateString() const
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()) {
118 return _buffer
.takeFirst();
122 void StreamSocket::sendChatMessage(QString sender
, QString msg
)
127 if (sender
.isEmpty()) {
128 if (_localPlayerName
.isEmpty()) {
129 WARN(tr("refusing to send <chatMessage> with empty sender"));
132 sender
= _localPlayerName
;
135 writeStartElement("chatMessage");
136 writeAttribute("from", sender
);
137 writeCharacters(msg
);
141 void StreamSocket::sendMove(Move 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()
162 if (_socket
->state() == QAbstractSocket::ConnectedState
) {
165 _socket
->disconnectFromHost();
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()));
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()) {
220 QString methodName
= "parse_" + elementName
;
221 if (QMetaObject::invokeMethod(this, methodName
.toUtf8().constData(), Qt::DirectConnection
)) {
224 if (elementName
!= "foo") {
225 WARN(tr("unknown protocol message %1").arg("<" + elementName
+ ">"));
231 void StreamSocket::parseData()
234 if (parse(_currentHandler
)) {
238 QXmlStreamReader::TokenType token
= readNext();
242 LOG("document start");
246 parse(name().toString());
258 LOG("unable to handle unexpected token {" + tokenString() + "}");
265 case PrematureEndOfDocumentError
:
269 emit
protocolError(errorString());
274 BEGIN_TERMINAL_HANDLER(stream
)
275 QString protocolVersion
= attributes().value("version").toString();
277 if (protocolVersion
== _supportedProtocolVersion
) {
278 changeState(FullyOpened
);
280 changeState(Closing
);
285 HANDLER_SIGNATURE(playerJoined
)
286 if (isStartElement() && elementName
== "playerJoined") {
288 int id
= attributes().value("id").toString().toInt(&ok
);
290 WARN(tr("invalid value for attribute 'id' in <playerJoined> : %1").arg(attributes().value("id").toString()));
293 setProperty("playerJoined/id", id
);
297 elementName
= name().toString();
298 if (isStartElement()) {
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());
307 END_NONTERMINAL_HANDLER(playerJoined
)
309 BEGIN_TERMINAL_HANDLER(name
)
310 setProperty("playerJoined/name", QVariant(readElementText()));
311 RESTORE_HANDLER("playerJoined")
314 BEGIN_TERMINAL_HANDLER(color
)
315 LOG("ignoring <color> element in <playerJoined> message");
316 RESTORE_HANDLER("playerJoined")
319 BEGIN_TERMINAL_HANDLER(type
)
320 setProperty("playerJoined/type", QVariant(readElementText()));
321 RESTORE_HANDLER("playerJoined")
324 BEGIN_TERMINAL_HANDLER(playerLeft
)
326 int id
= readElementText().toInt(&ok
);
330 WARN(tr("invalid content in <playerLeft> : %1").arg(readElementText()));
335 BEGIN_TERMINAL_HANDLER(chatMessage
)
336 QString from
= attributes().value("from").toString();
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
);
348 LOG("dropping <chatMessage> (state = " + stateString() + ")");
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
) {
361 LOG("dropping <move> (state = " + stateString() + ")");
365 END_NONTERMINAL_HANDLER(move
)
367 BEGIN_TERMINAL_HANDLER(player
)
369 int id
= readElementText().toInt(&ok
);
371 WARN(tr("invalid <player> value in <move> : %1").arg(readElementText()));
374 setProperty("move/player", QVariant(id
));
375 RESTORE_HANDLER("move")
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")
385 END_NONTERMINAL_HANDLER(point
)
387 BEGIN_TERMINAL_HANDLER(x
)
389 int value
= readElementText().toInt(&ok
);
391 WARN(tr("invalid <x> value in <point> : %1").arg(readElementText()));
394 setProperty("move/point/x", QVariant(value
));
395 RESTORE_HANDLER("point")
398 BEGIN_TERMINAL_HANDLER(y
)
400 int value
= readElementText().toInt(&ok
);
402 WARN(tr("invalid <y> value in <point> : %1").arg(readElementText()));
405 setProperty("move/point/y", QVariant(value
));
406 RESTORE_HANDLER("point")
409 BEGIN_TERMINAL_HANDLER(z
)
411 int value
= readElementText().toInt(&ok
);
413 WARN(tr("invalid <z> value in <point> : %1").arg(readElementText()));
416 setProperty("move/point/z", QVariant(value
));
417 RESTORE_HANDLER("point")
420 BEGIN_TERMINAL_HANDLER(startGame
)
423 elementName
= name().toString();
424 if (isEndElement()) {
425 Q_ASSERT(elementName
== "startGame");
427 if (state() == AwaitingGameStart
) {
428 changeState(Playing
);
432 LOG("unexpected data in <startGame> - ignoring");
436 BEGIN_TERMINAL_HANDLER(ping
)
439 elementName
= name().toString();
440 if (isEndElement()) {
441 Q_ASSERT(elementName
== "ping");
445 LOG("unexpected data in <ping> - ignoring");
449 BEGIN_TERMINAL_HANDLER(pong
)
452 elementName
= name().toString();
453 if (isEndElement()) {
454 Q_ASSERT(elementName
== "pong");
458 LOG("unexpected data in <pong> - ignoring");