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