File indexing completed on 2024-04-14 04:00:28

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"