File indexing completed on 2023-12-03 07:53:51

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 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0235                     int size = element.tagName().midRef(4).toInt();
0236 #else
0237                     int size = QStringView(element.tagName()).mid(4).toInt();
0238 #endif
0239                     QStringList data = element.text().split(QLatin1Char(' '));
0240                     if (data.size() != 3) {
0241                         continue;
0242                     }
0243                     Coord pos(data[0].toInt(), data[1].toInt());
0244                     Ship::Direction direction = data[2] == QLatin1Char('0')
0245                         ? Ship::TOP_DOWN 
0246                         : Ship::LEFT_TO_RIGHT;
0247                     msg->addShip(pos, size, direction);
0248                 }
0249             }
0250             
0251             return MessagePtr(msg);
0252         }
0253     case RestartMessage::MSGTYPE:
0254         return MessagePtr(new RestartMessage());
0255     case ChatMessage::MSGTYPE:
0256         {
0257             DEF_ELEMENT(nickname);
0258             DEF_ELEMENT(chat);
0259             return MessagePtr(new ChatMessage(nickname, chat));
0260         }
0261     case GameOptionsMessage::MSGTYPE:
0262         {
0263             // get values from the xml message
0264             DEF_ELEMENT(enabledAdjacentShips);
0265             bool adjacentShips = enabledAdjacentShips==QLatin1String("true");
0266             QDomElement oneOrElement=main.namedItem(QStringLiteral("oneOrSeveralShips")).toElement();
0267             QString oneOrSeveralShips = oneOrElement.text();
0268             bool severalShips = oneOrSeveralShips==QLatin1String("true");
0269             unsigned int longestShip=0;
0270             // if the node oneOrSeveralShips does not have the attribute, then it is the single ships configuration.
0271             if ( !oneOrElement.hasAttribute(QStringLiteral("longestShip")) ) {
0272                 return MessagePtr(new GameOptionsMessage(adjacentShips, severalShips, BattleShipsConfiguration::defaultSingleShipsConfiguration(adjacentShips, true)));
0273             }
0274             else {
0275                 longestShip = oneOrElement.attribute(QStringLiteral("longestShip")).toUInt();
0276             }
0277             DEF_ELEMENT(boardWidth);
0278             DEF_ELEMENT(boardHeight);
0279             unsigned int width=boardWidth.toUInt();
0280             unsigned int height=boardHeight.toUInt();
0281             // and get the ships configuration
0282             QDomNodeList nodes = main.childNodes();
0283             BattleShipsConfiguration battleShipsConfiguration(longestShip, adjacentShips, width, height, true);
0284             for (int i = 0; i < nodes.count(); i++) {
0285                 QDomElement element = nodes.item(i).toElement();
0286                 if (!element.isNull() && element.tagName()==QLatin1String("ships")) {
0287                     QString name=element.attribute(QStringLiteral("name"));
0288                     QString pluralName=element.attribute(QStringLiteral("pluralName"));
0289                     unsigned int size=element.attribute(QStringLiteral("size")).toUInt();
0290                     unsigned int number=element.attribute(QStringLiteral("number")).toUInt();
0291                     battleShipsConfiguration.addShips(size,number,name,pluralName);
0292                 }
0293             }
0294             if ( !battleShipsConfiguration.isAValidConfiguration() )
0295             {
0296                return MessagePtr(new GameOptionsMessage(adjacentShips, severalShips, BattleShipsConfiguration::defaultSingleShipsConfiguration(adjacentShips, true)));
0297             }
0298             return MessagePtr(new GameOptionsMessage(adjacentShips, severalShips, battleShipsConfiguration));
0299         }
0300     default:
0301         Q_EMIT parseError(QStringLiteral("Unknown message type"));
0302         return MessagePtr();
0303     }
0304 }
0305 #undef DEF_COORD
0306 #undef DEF_ELEMENT
0307 
0308 void Protocol::send(const MessagePtr& msg)
0309 {
0310     m_message_queue.enqueue(msg);
0311 }
0312 
0313 void Protocol::sendNext()
0314 {
0315     if (!m_message_queue.isEmpty())
0316     {
0317         MessageSender sender;
0318         m_message_queue.dequeue()->accept(sender);
0319         
0320         QTextStream stream(m_device);
0321         stream << sender.document().toString() << QLatin1Char('\n');
0322         stream.flush();
0323         
0324         qCDebug(KNAVALBATTLE_LOG) << "sending:" << sender.document().toString();
0325     }
0326 }
0327 
0328 void Protocol::processDisconnection()
0329 {
0330     m_timer.stop();
0331     Q_EMIT disconnected();
0332 }
0333 
0334 #include "moc_protocol.cpp"