1 /** @file libshell/src/protocol.cpp  Network protocol for communicating with a server.
2  *
3  * @authors Copyright © 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  *
5  * @par License
6  * LGPL: http://www.gnu.org/licenses/lgpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14  * General Public License for more details. You should have received a copy of
15  * the GNU Lesser General Public License along with this program; if not, see:
16  * http://www.gnu.org/licenses</small>
17  */
18 
19 #include "de/shell/Protocol"
20 #include <de/LogBuffer>
21 #include <de/ArrayValue>
22 #include <de/TextValue>
23 #include <de/Reader>
24 #include <de/Writer>
25 #include <QCryptographicHash>
26 #include <QList>
27 
28 namespace de { namespace shell {
29 
30 static String const PT_COMMAND    = "shell.command";
31 static String const PT_LEXICON    = "shell.lexicon";
32 static String const PT_GAME_STATE = "shell.game.state";
33 
34 // ChallengePacket -----------------------------------------------------------
35 
36 static Packet::Type const CHALLENGE_PACKET_TYPE = Packet::typeFromString("Psw?");
37 
ChallengePacket()38 ChallengePacket::ChallengePacket() : Packet(CHALLENGE_PACKET_TYPE)
39 {}
40 
fromBlock(Block const & block)41 Packet *ChallengePacket::fromBlock(Block const &block)
42 {
43     return constructFromBlock<ChallengePacket>(block, CHALLENGE_PACKET_TYPE);
44 }
45 
46 // LogEntryPacket ------------------------------------------------------------
47 
48 static Packet::Type const LOG_ENTRY_PACKET_TYPE = Packet::typeFromString("LgEn");
49 
LogEntryPacket()50 LogEntryPacket::LogEntryPacket() : Packet(LOG_ENTRY_PACKET_TYPE)
51 {}
52 
~LogEntryPacket()53 LogEntryPacket::~LogEntryPacket()
54 {
55     clear();
56 }
57 
clear()58 void LogEntryPacket::clear()
59 {
60     foreach (LogEntry *e, _entries) delete e;
61     _entries.clear();
62 }
63 
isEmpty() const64 bool LogEntryPacket::isEmpty() const
65 {
66     return _entries.isEmpty();
67 }
68 
add(LogEntry const & entry)69 void LogEntryPacket::add(LogEntry const &entry)
70 {
71     _entries.append(new LogEntry(entry));
72 }
73 
entries() const74 LogEntryPacket::Entries const &LogEntryPacket::entries() const
75 {
76     return _entries;
77 }
78 
execute() const79 void LogEntryPacket::execute() const
80 {
81     // Copies of all entries in the packet are added to the LogBuffer.
82     LogBuffer &buf = LogBuffer::get();
83     foreach (LogEntry *e, _entries)
84     {
85         buf.add(new LogEntry(*e, LogEntry::Remote));
86     }
87 }
88 
operator >>(Writer & to) const89 void LogEntryPacket::operator >> (Writer &to) const
90 {
91     Packet::operator >> (to);
92     to.writeObjects(_entries);
93 }
94 
operator <<(Reader & from)95 void LogEntryPacket::operator << (Reader &from)
96 {
97     _entries.clear();
98 
99     Packet::operator << (from);
100     from.readObjects<LogEntry>(_entries);
101 }
102 
fromBlock(Block const & block)103 Packet *LogEntryPacket::fromBlock(Block const &block)
104 {
105     return constructFromBlock<LogEntryPacket>(block, LOG_ENTRY_PACKET_TYPE);
106 }
107 
108 // PlayerInfoPacket ----------------------------------------------------------
109 
110 static Packet::Type const PLAYER_INFO_PACKET_TYPE = Packet::typeFromString("PlrI");
111 
DENG2_PIMPL_NOREF(PlayerInfoPacket)112 DENG2_PIMPL_NOREF(PlayerInfoPacket)
113 {
114     Players players;
115 };
116 
PlayerInfoPacket()117 PlayerInfoPacket::PlayerInfoPacket()
118     : Packet(PLAYER_INFO_PACKET_TYPE), d(new Impl)
119 {}
120 
add(Player const & player)121 void PlayerInfoPacket::add(Player const &player)
122 {
123     d->players.insert(player.number, player);
124 }
125 
count() const126 int PlayerInfoPacket::count() const
127 {
128     return d->players.size();
129 }
130 
player(int number) const131 PlayerInfoPacket::Player const &PlayerInfoPacket::player(int number) const
132 {
133     DENG2_ASSERT(d->players.contains(number));
134     return d->players[number];
135 }
136 
players() const137 PlayerInfoPacket::Players PlayerInfoPacket::players() const
138 {
139     return d->players;
140 }
141 
operator >>(Writer & to) const142 void PlayerInfoPacket::operator >> (Writer &to) const
143 {
144     Packet::operator >> (to);
145 
146     to << duint32(d->players.size());
147     foreach (Player const &p, d->players)
148     {
149         to << dbyte(p.number) << p.position << p.name << p.color;
150     }
151 }
152 
operator <<(Reader & from)153 void PlayerInfoPacket::operator << (Reader &from)
154 {
155     d->players.clear();
156 
157     Packet::operator << (from);
158 
159     duint32 count;
160     from >> count;
161     while (count-- > 0)
162     {
163         Player p;
164         from.readAs<dbyte>(p.number) >> p.position >> p.name >> p.color;
165         d->players.insert(p.number, p);
166     }
167 }
168 
fromBlock(Block const & block)169 Packet *PlayerInfoPacket::fromBlock(Block const &block)
170 {
171     return constructFromBlock<PlayerInfoPacket>(block, PLAYER_INFO_PACKET_TYPE);
172 }
173 
174 // MapOutlinePacket ----------------------------------------------------------
175 
176 static Packet::Type const MAP_OUTLINE_PACKET_TYPE = Packet::typeFromString("MpOL");
177 
DENG2_PIMPL_NOREF(MapOutlinePacket)178 DENG2_PIMPL_NOREF(MapOutlinePacket)
179 {
180     QList<Line> lines;
181 };
182 
MapOutlinePacket()183 MapOutlinePacket::MapOutlinePacket()
184     : Packet(MAP_OUTLINE_PACKET_TYPE), d(new Impl)
185 {}
186 
clear()187 void MapOutlinePacket::clear()
188 {
189     d->lines.clear();
190 }
191 
addLine(Vector2i const & vertex1,Vector2i const & vertex2,LineType type)192 void MapOutlinePacket::addLine(Vector2i const &vertex1, Vector2i const &vertex2, LineType type)
193 {
194     Line ln;
195     ln.start = vertex1;
196     ln.end   = vertex2;
197     ln.type  = type;
198     d->lines.append(ln);
199 }
200 
lineCount() const201 int MapOutlinePacket::lineCount() const
202 {
203     return d->lines.size();
204 }
205 
line(int index) const206 MapOutlinePacket::Line const &MapOutlinePacket::line(int index) const
207 {
208     DENG2_ASSERT(index >= 0 && index < d->lines.size());
209     return d->lines[index];
210 }
211 
operator >>(Writer & to) const212 void MapOutlinePacket::operator >> (Writer &to) const
213 {
214     Packet::operator >> (to);
215 
216     to << duint32(d->lines.size());
217     foreach (Line const &ln, d->lines)
218     {
219         to << ln.start << ln.end << dbyte(ln.type);
220     }
221 }
222 
operator <<(Reader & from)223 void MapOutlinePacket::operator << (Reader &from)
224 {
225     clear();
226 
227     Packet::operator << (from);
228 
229     duint32 count;
230     from >> count;
231     while (count-- > 0)
232     {
233         Line ln;
234         from >> ln.start >> ln.end;
235         from.readAs<dbyte>(ln.type);
236         d->lines.append(ln);
237     }
238 }
239 
fromBlock(Block const & block)240 Packet *MapOutlinePacket::fromBlock(Block const &block)
241 {
242     return constructFromBlock<MapOutlinePacket>(block, MAP_OUTLINE_PACKET_TYPE);
243 }
244 
245 // Protocol ------------------------------------------------------------------
246 
Protocol()247 Protocol::Protocol()
248 {
249     define(ChallengePacket::fromBlock);
250     define(LogEntryPacket::fromBlock);
251     define(MapOutlinePacket::fromBlock);
252     define(PlayerInfoPacket::fromBlock);
253 }
254 
recognize(Packet const * packet)255 Protocol::PacketType Protocol::recognize(Packet const *packet)
256 {
257     if (packet->type() == CHALLENGE_PACKET_TYPE)
258     {
259         DENG2_ASSERT(is<ChallengePacket>(packet));
260         return PasswordChallenge;
261     }
262 
263     if (packet->type() == LOG_ENTRY_PACKET_TYPE)
264     {
265         DENG2_ASSERT(is<LogEntryPacket>(packet));
266         return LogEntries;
267     }
268 
269     if (packet->type() == MAP_OUTLINE_PACKET_TYPE)
270     {
271         DENG2_ASSERT(is<MapOutlinePacket>(packet));
272         return MapOutline;
273     }
274 
275     if (packet->type() == PLAYER_INFO_PACKET_TYPE)
276     {
277         DENG2_ASSERT(is<PlayerInfoPacket>(packet));
278         return PlayerInfo;
279     }
280 
281     // One of the generic-format packets?
282     if (RecordPacket const *rec = maybeAs<RecordPacket>(packet))
283     {
284         if (rec->name() == PT_COMMAND)
285         {
286             return Command;
287         }
288         else if (rec->name() == PT_LEXICON)
289         {
290             return ConsoleLexicon;
291         }
292         else if (rec->name() == PT_GAME_STATE)
293         {
294             return GameState;
295         }
296     }
297     return Unknown;
298 }
299 
passwordResponse(String const & plainPassword)300 Block Protocol::passwordResponse(String const &plainPassword)
301 {
302     Block response;
303     response += "Shell";
304     response += QCryptographicHash::hash(plainPassword.toUtf8(),
305                                          QCryptographicHash::Sha1);
306     return response;
307 }
308 
newCommand(String const & command)309 RecordPacket *Protocol::newCommand(String const &command)
310 {
311     RecordPacket *cmd = new RecordPacket(PT_COMMAND);
312     cmd->record().addText("execute", command);
313     return cmd;
314 }
315 
asRecordPacket(Packet const & packet,Protocol::PacketType type)316 static RecordPacket const &asRecordPacket(Packet const &packet, Protocol::PacketType type)
317 {
318     RecordPacket const *rec = dynamic_cast<RecordPacket const *>(&packet);
319     DENG2_ASSERT(rec != 0);
320     DENG2_ASSERT(Protocol::recognize(&packet) == type);
321     DENG2_UNUSED(type);
322     return *rec;
323 }
324 
command(Packet const & commandPacket)325 String Protocol::command(Packet const &commandPacket)
326 {
327     RecordPacket const &rec = asRecordPacket(commandPacket, Command);
328     return rec["execute"].value().asText();
329 }
330 
newConsoleLexicon(Lexicon const & lexicon)331 RecordPacket *Protocol::newConsoleLexicon(Lexicon const &lexicon)
332 {
333     RecordPacket *lex = new RecordPacket(PT_LEXICON);
334     lex->record().addText("extraChars", lexicon.additionalWordChars());
335     ArrayValue &arr = lex->record().addArray("terms").array();
336     foreach (String const &term, lexicon.terms())
337     {
338         arr << TextValue(term);
339     }
340     return lex;
341 }
342 
lexicon(Packet const & consoleLexiconPacket)343 Lexicon Protocol::lexicon(Packet const &consoleLexiconPacket)
344 {
345     RecordPacket const &rec = asRecordPacket(consoleLexiconPacket, ConsoleLexicon);
346     Lexicon lexicon;
347     DENG2_FOR_EACH_CONST(ArrayValue::Elements, i, rec["terms"].array().elements())
348     {
349         lexicon.addTerm((*i)->asText());
350     }
351     lexicon.setAdditionalWordChars(rec.valueAsText("extraChars"));
352     return lexicon;
353 }
354 
newGameState(String const & mode,String const & rules,String const & mapId,String const & mapTitle)355 RecordPacket *Protocol::newGameState(String const &mode,
356                                      String const &rules,
357                                      String const &mapId,
358                                      String const &mapTitle)
359 {
360     RecordPacket *gs = new RecordPacket(PT_GAME_STATE);
361     Record &r = gs->record();
362     r.addText("mode", mode);
363     r.addText("rules", rules);
364     r.addText("mapId", mapId);
365     r.addText("mapTitle", mapTitle);
366     return gs;
367 }
368 
369 }} // namespace de::shell
370