File indexing completed on 2024-09-15 03:44:39
0001 /* 0002 SPDX-FileCopyrightText: 2000-2001 Nikolas Zimmermann <wildfox@kde.org> 0003 SPDX-FileCopyrightText: 2000-2001 Daniel Molkentin <molkentin@kde.org> 0004 SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "protocol.h" 0010 0011 #include <QStringList> 0012 #include <QDomElement> 0013 #include <QDomNode> 0014 #include <QTcpSocket> 0015 0016 #define ADD_FIELD(msg, field) addField(QStringLiteral(#field), msg.field()) 0017 class MessageSender : public MessageVisitor 0018 { 0019 QDomDocument m_doc; 0020 QDomElement m_main; 0021 0022 QDomElement addField(const QString& key, const QString& value) 0023 { 0024 QDomElement element = m_doc.createElement(key); 0025 QDomText text = m_doc.createTextNode(value); 0026 element.appendChild(text); 0027 m_main.appendChild(element); 0028 return element; 0029 } 0030 0031 template <typename Msg> 0032 void setType(const Msg&) 0033 { 0034 QDomElement element = addField(QStringLiteral("msgtype"), QString::number(Msg::MSGTYPE)); 0035 // only useful for debugging, just add the name of the message type 0036 element.setAttribute(QLatin1String("type"), Msg::messageType()); 0037 } 0038 public: 0039 MessageSender() 0040 : m_doc(QStringLiteral("kmessage")) 0041 { 0042 m_main = m_doc.createElement(QStringLiteral("kmessage")); 0043 m_doc.appendChild(m_main); 0044 } 0045 0046 QDomDocument document() const { return m_doc; } 0047 0048 void visit(const HeaderMessage& msg) override 0049 { 0050 setType(msg); 0051 ADD_FIELD(msg, protocolVersion); 0052 ADD_FIELD(msg, clientName); 0053 ADD_FIELD(msg, clientVersion); 0054 ADD_FIELD(msg, clientDescription); 0055 } 0056 0057 void visit(const RejectMessage& msg) override { setType(msg); } 0058 0059 void visit(const NickMessage& msg) override 0060 { 0061 setType(msg); 0062 ADD_FIELD(msg, nickname); 0063 } 0064 0065 void visit(const BeginMessage& msg) override { setType(msg); } 0066 0067 void visit(const MoveMessage& msg) override 0068 { 0069 setType(msg); 0070 addField(QStringLiteral("fieldx"), QString::number(msg.move().x)); 0071 addField(QStringLiteral("fieldy"), QString::number(msg.move().y)); 0072 } 0073 0074 void visit(const NotificationMessage& msg) override 0075 { 0076 setType(msg); 0077 addField(QStringLiteral("fieldx"), QString::number(msg.move().x)); 0078 addField(QStringLiteral("fieldy"), QString::number(msg.move().y)); 0079 addField(QStringLiteral("fieldstate"), msg.hit() ? QStringLiteral("1") : QStringLiteral("99")); 0080 if (msg.death()) { 0081 addField(QStringLiteral("death"), QStringLiteral("true")); 0082 addField(QStringLiteral("xstart"), QString::number(msg.start().x)); 0083 addField(QStringLiteral("xstop"), QString::number(msg.stop().x)); 0084 addField(QStringLiteral("ystart"), QString::number(msg.start().y)); 0085 addField(QStringLiteral("ystop"), QString::number(msg.stop().y)); 0086 } 0087 } 0088 0089 void visit(const GameOverMessage& msg) override 0090 { 0091 setType(msg); 0092 for (const GameOverMessage::ShipInfo &ship : msg.ships()) { 0093 QStringList data; 0094 data << QString::number(ship.pos.x) 0095 << QString::number(ship.pos.y) 0096 << (ship.direction == Ship::TOP_DOWN ? QStringLiteral("0") : QStringLiteral("1")); 0097 addField(QStringLiteral("ship") + QString::number(ship.size), data.join( QLatin1String( " " ))); 0098 } 0099 } 0100 0101 void visit(const RestartMessage& msg) override 0102 { 0103 setType(msg); 0104 } 0105 0106 void visit(const ChatMessage& msg) override 0107 { 0108 setType(msg); 0109 ADD_FIELD(msg, chat); 0110 ADD_FIELD(msg, nickname); 0111 } 0112 0113 void visit(const GameOptionsMessage& msg) override 0114 { 0115 // create the message XML contents 0116 setType(msg); 0117 ADD_FIELD(msg, enabledAdjacentShips); 0118 QDomElement oneOrElement=addField (QStringLiteral("oneOrSeveralShips"), QString(msg.oneOrSeveralShips())); 0119 oneOrElement.setAttribute(QStringLiteral("longestShip"),QString::number(msg.shipsConfiguration()->longestShip())); 0120 addField(QStringLiteral("boardWidth"), QString::number(msg.gridWidth())); 0121 addField(QStringLiteral("boardHeight"), QString::number(msg.gridHeight())); 0122 for (unsigned int i=1; i<=msg.shipsConfiguration()->longestShip(); i++) { 0123 QDomElement element=addField(QStringLiteral("ships"), QLatin1String( "" )); 0124 element.setAttribute(QStringLiteral("size"),QString::number(i)); 0125 element.setAttribute(QStringLiteral("number"),QString::number(msg.shipsConfiguration()->numberOfShipsOfSize(i))); 0126 element.setAttribute(QStringLiteral("name"),msg.shipsConfiguration()->nameOfShipsOfSize(i)); 0127 element.setAttribute(QStringLiteral("pluralName"),msg.shipsConfiguration()->pluralNameOfShipsOfSize(i)); 0128 } 0129 } 0130 }; 0131 0132 0133 0134 Protocol::Protocol(QTcpSocket* device) 0135 : m_device(device) 0136 { 0137 m_device->setParent(this); 0138 m_timer.start(100); 0139 connect(m_device, &QTcpSocket::disconnected, this, &Protocol::processDisconnection); 0140 connect(m_device, &QTcpSocket::readyRead, this, &Protocol::readMore); 0141 connect(&m_timer, &QTimer::timeout, this, &Protocol::sendNext); 0142 } 0143 0144 void Protocol::readMore() 0145 { 0146 QByteArray data = m_device->read(4096); 0147 m_buffer += QString::fromUtf8(data.constData()); 0148 0149 int pos; 0150 while ((pos = m_buffer.indexOf(QLatin1String("</kmessage>"))) >= 0) { 0151 pos += 11; // Length of "</kmessage>" 0152 MessagePtr msg = parseMessage(m_buffer.left(pos)); 0153 m_buffer.remove(0, pos); 0154 0155 Q_EMIT received(msg); 0156 } 0157 } 0158 0159 #define DEF_ELEMENT(var) QString var = main.namedItem(QStringLiteral(#var)).toElement().text() 0160 #define DEF_COORD(var, varx, vary) DEF_ELEMENT(varx); DEF_ELEMENT(vary); Coord var(varx.toInt(), vary.toInt()); 0161 MessagePtr Protocol::parseMessage(const QString& xmlMessage) 0162 { 0163 qCDebug(KNAVALBATTLE_LOG) << "received:" << xmlMessage; 0164 0165 QDomDocument doc; 0166 doc.setContent(xmlMessage); 0167 0168 QDomElement main = doc.documentElement(); 0169 if (main.tagName() != QLatin1String("kmessage")) 0170 { 0171 Q_EMIT parseError(QStringLiteral("Invalid parent tag")); 0172 return MessagePtr(); 0173 } 0174 0175 QDomElement msgtype = main.namedItem(QStringLiteral("msgtype")).toElement(); 0176 if (msgtype.isNull()) 0177 { 0178 Q_EMIT parseError(QStringLiteral("No message type")); 0179 return MessagePtr(); 0180 } 0181 0182 int type = msgtype.text().toInt(); 0183 switch (type) { 0184 case HeaderMessage::MSGTYPE: 0185 { 0186 DEF_ELEMENT(protocolVersion); 0187 DEF_ELEMENT(clientName); 0188 DEF_ELEMENT(clientVersion); 0189 DEF_ELEMENT(clientDescription); 0190 return MessagePtr(new HeaderMessage(protocolVersion, clientName, 0191 clientVersion, clientDescription)); 0192 } 0193 case RejectMessage::MSGTYPE: 0194 { 0195 DEF_ELEMENT(kmversion); 0196 DEF_ELEMENT(reason); 0197 return MessagePtr(new RejectMessage(kmversion == QLatin1String("true"), reason)); 0198 } 0199 case NickMessage::MSGTYPE: 0200 { 0201 DEF_ELEMENT(nickname); 0202 return MessagePtr(new NickMessage(nickname)); 0203 } 0204 case BeginMessage::MSGTYPE: 0205 return MessagePtr(new BeginMessage); 0206 case MoveMessage::MSGTYPE: 0207 { 0208 DEF_COORD(field, fieldx, fieldy); 0209 return MessagePtr(new MoveMessage(field)); 0210 } 0211 case NotificationMessage::MSGTYPE: 0212 { 0213 DEF_COORD(field, fieldx, fieldy); 0214 DEF_ELEMENT(fieldstate); 0215 bool hit = fieldstate != QLatin1String("99"); 0216 DEF_ELEMENT(death); 0217 bool destroyed = death == QLatin1String("true"); 0218 if (destroyed) { 0219 DEF_COORD(start, xstart, ystart); 0220 DEF_COORD(stop, xstop, ystop); 0221 return MessagePtr(new NotificationMessage(field, hit, destroyed, start, stop)); 0222 } 0223 else { 0224 return MessagePtr(new NotificationMessage(field, hit, destroyed)); 0225 } 0226 } 0227 case GameOverMessage::MSGTYPE: 0228 { 0229 GameOverMessage* msg = new GameOverMessage(); 0230 QDomNodeList nodes = main.childNodes(); 0231 for (int i = 0; i < nodes.count(); i++) { 0232 QDomElement element = nodes.item(i).toElement(); 0233 if (!element.isNull() && element.tagName().startsWith(QLatin1String("ship"))) { 0234 int size = QStringView(element.tagName()).mid(4).toInt(); 0235 QStringList data = element.text().split(QLatin1Char(' ')); 0236 if (data.size() != 3) { 0237 continue; 0238 } 0239 Coord pos(data[0].toInt(), data[1].toInt()); 0240 Ship::Direction direction = data[2] == QLatin1Char('0') 0241 ? Ship::TOP_DOWN 0242 : Ship::LEFT_TO_RIGHT; 0243 msg->addShip(pos, size, direction); 0244 } 0245 } 0246 0247 return MessagePtr(msg); 0248 } 0249 case RestartMessage::MSGTYPE: 0250 return MessagePtr(new RestartMessage()); 0251 case ChatMessage::MSGTYPE: 0252 { 0253 DEF_ELEMENT(nickname); 0254 DEF_ELEMENT(chat); 0255 return MessagePtr(new ChatMessage(nickname, chat)); 0256 } 0257 case GameOptionsMessage::MSGTYPE: 0258 { 0259 // get values from the xml message 0260 DEF_ELEMENT(enabledAdjacentShips); 0261 bool adjacentShips = enabledAdjacentShips==QLatin1String("true"); 0262 QDomElement oneOrElement=main.namedItem(QStringLiteral("oneOrSeveralShips")).toElement(); 0263 QString oneOrSeveralShips = oneOrElement.text(); 0264 bool severalShips = oneOrSeveralShips==QLatin1String("true"); 0265 unsigned int longestShip=0; 0266 // if the node oneOrSeveralShips does not have the attribute, then it is the single ships configuration. 0267 if ( !oneOrElement.hasAttribute(QStringLiteral("longestShip")) ) { 0268 return MessagePtr(new GameOptionsMessage(adjacentShips, severalShips, BattleShipsConfiguration::defaultSingleShipsConfiguration(adjacentShips, true))); 0269 } 0270 else { 0271 longestShip = oneOrElement.attribute(QStringLiteral("longestShip")).toUInt(); 0272 } 0273 DEF_ELEMENT(boardWidth); 0274 DEF_ELEMENT(boardHeight); 0275 unsigned int width=boardWidth.toUInt(); 0276 unsigned int height=boardHeight.toUInt(); 0277 // and get the ships configuration 0278 QDomNodeList nodes = main.childNodes(); 0279 BattleShipsConfiguration battleShipsConfiguration(longestShip, adjacentShips, width, height, true); 0280 for (int i = 0; i < nodes.count(); i++) { 0281 QDomElement element = nodes.item(i).toElement(); 0282 if (!element.isNull() && element.tagName()==QLatin1String("ships")) { 0283 QString name=element.attribute(QStringLiteral("name")); 0284 QString pluralName=element.attribute(QStringLiteral("pluralName")); 0285 unsigned int size=element.attribute(QStringLiteral("size")).toUInt(); 0286 unsigned int number=element.attribute(QStringLiteral("number")).toUInt(); 0287 battleShipsConfiguration.addShips(size,number,name,pluralName); 0288 } 0289 } 0290 if ( !battleShipsConfiguration.isAValidConfiguration() ) 0291 { 0292 return MessagePtr(new GameOptionsMessage(adjacentShips, severalShips, BattleShipsConfiguration::defaultSingleShipsConfiguration(adjacentShips, true))); 0293 } 0294 return MessagePtr(new GameOptionsMessage(adjacentShips, severalShips, battleShipsConfiguration)); 0295 } 0296 default: 0297 Q_EMIT parseError(QStringLiteral("Unknown message type")); 0298 return MessagePtr(); 0299 } 0300 } 0301 #undef DEF_COORD 0302 #undef DEF_ELEMENT 0303 0304 void Protocol::send(const MessagePtr& msg) 0305 { 0306 m_message_queue.enqueue(msg); 0307 } 0308 0309 void Protocol::sendNext() 0310 { 0311 if (!m_message_queue.isEmpty()) 0312 { 0313 MessageSender sender; 0314 m_message_queue.dequeue()->accept(sender); 0315 0316 QTextStream stream(m_device); 0317 stream << sender.document().toString() << QLatin1Char('\n'); 0318 stream.flush(); 0319 0320 qCDebug(KNAVALBATTLE_LOG) << "sending:" << sender.document().toString(); 0321 } 0322 } 0323 0324 void Protocol::processDisconnection() 0325 { 0326 m_timer.stop(); 0327 Q_EMIT disconnected(); 0328 } 0329 0330 #include "moc_protocol.cpp"