Skip to content

Commit

Permalink
Shell|libdeng2: Defining libshell's network protocol
Browse files Browse the repository at this point in the history
Starting out with minimal functionality: console commands and
log entries.
  • Loading branch information
skyjake committed Jan 27, 2013
1 parent 096df4f commit 6861201
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 20 deletions.
1 change: 1 addition & 0 deletions doomsday/libdeng2/include/de/net/protocol.h
Expand Up @@ -21,6 +21,7 @@
#define LIBDENG2_PROTOCOL_H

#include "../libdeng2.h"
#include <de/Reader>

#include <QList>

Expand Down
15 changes: 14 additions & 1 deletion doomsday/tools/shell/libshell/include/de/shell/link.h
Expand Up @@ -20,6 +20,7 @@
#define LIBSHELL_LINK_H

#include <de/Address>
#include <de/Socket>
#include <de/Time>
#include <de/Transmitter>
#include <de/shell/Protocol>
Expand Down Expand Up @@ -51,6 +52,13 @@ class DENG2_PUBLIC Link : public QObject, public Transmitter
*/
Link(Address const &address);

/**
* Takes over an existing socket.
*
* @param openSocket Socket. Link takes ownership.
*/
Link(Socket *openSocket);

virtual ~Link();

/**
Expand All @@ -68,6 +76,11 @@ class DENG2_PUBLIC Link : public QObject, public Transmitter
*/
Time connectedAt() const;

/**
* Shell protocol for constructing and interpreting packets.
*/
Protocol &protocol();

/**
* Returns the next received packet.
*
Expand All @@ -82,10 +95,10 @@ class DENG2_PUBLIC Link : public QObject, public Transmitter
protected slots:
void socketConnected();
void socketDisconnected();
void handleIncomingPackets();

signals:
void connected();
void packetsReady();
void disconnected();

private:
Expand Down
60 changes: 60 additions & 0 deletions doomsday/tools/shell/libshell/include/de/shell/protocol.h
Expand Up @@ -20,17 +20,77 @@
#define LIBSHELL_PROTOCOL_H

#include <de/Protocol>
#include <de/RecordPacket>
#include <QList>

namespace de {
namespace shell {

/**
* Packet with one or more log entries.
*/
class LogEntryPacket : public Packet
{
public:
LogEntryPacket();
~LogEntryPacket();

void clear();
void execute() const;

// Implements ISerializable.
void operator >> (Writer &to) const;
void operator << (Reader &from);

static Packet *fromBlock(Block const &block);

private:
QList<LogEntry *> _entries;
};

/**
* Network protocol for communicating with a server.
*/
class Protocol : public de::Protocol
{
public:
/// Type of provided packet is incorrect. @ingroup errors
DENG2_ERROR(TypeError);

enum PacketType
{
Unknown,
Command, ///< Console command (only to server).
LogEntries, ///< Log entries.
ConsoleLexicon, ///< Known words for command line completion.
GameState, ///< Current state of the game (mode, map).
Leaderboard, ///< Frags leaderboard.
MapOutline, ///< Sectors of the map for visual overview.
PlayerPositions ///< Current player positions.
};

public:
Protocol();

/**
* Detects the type of a packet.
*
* @param packet Any packet.
*
* @return Type of the packet.
*/
PacketType recognize(Packet const *packet);

/**
* Constructs a console command packet.
*
* @param command Command to execute on the server.
*
* @return Packet. Caller gets ownership.
*/
RecordPacket *newCommand(String const &command);

String command(Packet const &commandPacket);
};

} // namespace shell
Expand Down
73 changes: 56 additions & 17 deletions doomsday/tools/shell/libshell/src/link.cpp
Expand Up @@ -29,42 +29,59 @@ namespace shell {
struct Link::Instance
{
Link &self;
Address serverAddress;
Socket socket;
Address peerAddress;
Socket *socket;
Protocol protocol;
Status status;
Time connectedAt;

Instance(Link &i)
: self(i),
socket(0),
status(Disconnected),
connectedAt(Time::invalidTime()) {}

~Instance() {}
~Instance()
{
delete socket;
}
};

Link::Link(Address const &address) : d(new Instance(*this))
{
d->serverAddress = address;
d->peerAddress = address;
d->socket = new Socket;

connect(&d->socket, SIGNAL(connected()), this, SLOT(socketConnected()));
connect(&d->socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(&d->socket, SIGNAL(messagesReady()), this, SIGNAL(packetsReady()));
connect(d->socket, SIGNAL(connected()), this, SLOT(socketConnected()));
connect(d->socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(d->socket, SIGNAL(messagesReady()), this, SLOT(handleIncomingPackets()));

d->socket.connect(address);
d->socket->connect(address);

d->status = Connecting;
}

Link::Link(Socket *openSocket) : d(new Instance(*this))
{
d->peerAddress = openSocket->peerAddress();
d->socket = openSocket;

connect(d->socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(d->socket, SIGNAL(messagesReady()), this, SLOT(handleIncomingPackets()));

d->status = Connected;
d->connectedAt = Time();
}

Link::~Link()
{
delete d;
}

Address Link::address() const
{
if(d->socket.isOpen()) return d->socket.peerAddress();
return d->serverAddress;
if(d->socket->isOpen()) return d->socket->peerAddress();
return d->peerAddress;
}

Link::Status Link::status() const
Expand All @@ -77,23 +94,37 @@ Time Link::connectedAt() const
return d->connectedAt;
}

Packet *Link::nextPacket()
Protocol &Link::protocol()
{
if(!d->socket.hasIncoming()) return 0;
return d->protocol;
}

std::auto_ptr<Message> data(d->socket.receive());
return d->protocol.interpret(*data.get());
Packet *Link::nextPacket()
{
if(!d->socket->hasIncoming()) return 0;

std::auto_ptr<Message> data(d->socket->receive());
Packet *packet = d->protocol.interpret(*data.get());
try
{
packet->setFrom(d->socket->peerAddress());
}
catch(Socket::PeerError const &)
{
// Socket must already be closed.
}
return packet;
}

void Link::send(IByteArray const &data)
{
d->socket.send(data);
d->socket->send(data);
}

void Link::socketConnected()
{
LOG_AS("Link");
LOG_VERBOSE("Successfully connected to %s") << d->socket.peerAddress();
LOG_VERBOSE("Successfully connected to %s") << d->socket->peerAddress();

d->status = Connected;
d->connectedAt = Time();
Expand All @@ -104,7 +135,7 @@ void Link::socketConnected()
void Link::socketDisconnected()
{
LOG_AS("Link");
LOG_INFO("Disconnected from %s") << d->serverAddress;
LOG_INFO("Disconnected from %s") << d->peerAddress;

d->status = Disconnected;

Expand All @@ -115,5 +146,13 @@ void Link::socketDisconnected()
d->connectedAt = Time::invalidTime();
}

void Link::handleIncomingPackets()
{
while(Packet *ptr = nextPacket())
{
QScopedPointer<Packet> packet(ptr);
}
}

} // namespace shell
} // namespace de
83 changes: 81 additions & 2 deletions doomsday/tools/shell/libshell/src/protocol.cpp
Expand Up @@ -17,14 +17,93 @@
*/

#include "de/shell/Protocol"
#include <de/LogBuffer>
#include <de/Reader>
#include <de/Writer>

namespace de {
namespace shell {

static String const PT_COMMAND = "shell.command";

// LogEntryPacket ------------------------------------------------------------

static char const *LOG_ENTRY_PACKET_TYPE = "LOGE";

LogEntryPacket::LogEntryPacket() : Packet(LOG_ENTRY_PACKET_TYPE)
{}

LogEntryPacket::~LogEntryPacket()
{
clear();
}

void LogEntryPacket::clear()
{
foreach(LogEntry *e, _entries) delete e;
_entries.clear();
}

void LogEntryPacket::execute() const
{
// Copies of all entries in the packet are added to the LogBuffer.
LogBuffer &buf = LogBuffer::appBuffer();
foreach(LogEntry *e, _entries)
{
buf.add(new LogEntry(*e));
}
}

void LogEntryPacket::operator >> (Writer &to) const
{
Packet::operator >> (to);
to.writeObjects(_entries);
}

void LogEntryPacket::operator << (Reader &from)
{
_entries.clear();

Packet::operator << (from);
from.readObjects<LogEntry>(_entries);
}

Packet *LogEntryPacket::fromBlock(Block const &block)
{
return constructFromBlock<LogEntryPacket>(block, LOG_ENTRY_PACKET_TYPE);
}

// Protocol ------------------------------------------------------------------

Protocol::Protocol()
{}

Protocol::PacketType Protocol::recognize(Packet const *packet)
{
// Register all the known packet types.
//define();
RecordPacket const *rec = dynamic_cast<RecordPacket const *>(packet);
if(rec)
{
if(rec->name() == PT_COMMAND)
return Command;
}
return Unknown;
}

RecordPacket *Protocol::newCommand(String const &command)
{
RecordPacket *cmd = new RecordPacket(PT_COMMAND);
cmd->record().addText("cmd", command);
return cmd;
}

String Protocol::command(Packet const &commandPacket)
{
DENG2_ASSERT(recognize(&commandPacket) == Command);

RecordPacket const *rec = dynamic_cast<RecordPacket const *>(&commandPacket);
DENG2_ASSERT(rec != 0);

return (*rec)["cmd"].value().asText();
}

} // namespace shell
Expand Down

0 comments on commit 6861201

Please sign in to comment.