protocol.cpp

Go to the documentation of this file.
00001 /*
00002  * protocol.cpp
00003  *
00004  * Copyright (C) 2007,2009  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  *
00031  * Base protocol objects.
00032  */
00033 
00034 // includes --------------------------------------------------------------------
00035 #include "protocol.h"           // always include our own header first!
00036 
00037 #include <sstream>
00038 
00039 #include "common/wave_ex.h"
00040 #include "datahash/datahash_text.h"
00041 #include "datahash/datahash_util.h"
00042 #include "netlib/netlib.h"
00043 #include "perf/perf.h"
00044 
00045 
00046 namespace aesop {
00047 
00048 
00049 
00050 ////////////////////////////////////////////////////////////////////////////////
00051 //
00052 //      static helper methods
00053 //
00054 ////////////////////////////////////////////////////////////////////////////////
00055 
00056 static void
00057 addHeader
00058 (
00059 IO netlib::MessageBuffer * buffer,
00060 IN const char * namespace_,
00061 IN const char * command
00062 )
00063 {
00064         ASSERT(buffer, "null");
00065         ASSERT(namespace_, "null");
00066         ASSERT(command, "null");
00067 
00068         buffer->appendToken("header {\nnamespace ");
00069         buffer->appendToken(namespace_);
00070         buffer->appendToken("\ncommand ");
00071         buffer->appendToken(command);
00072         buffer->appendToken("\n}\n");
00073 }
00074 
00075 
00076 
00077 static smart_ptr<netlib::MessageBuffer>
00078 createMessageFromString
00079 (
00080 IN const char * namespace_,
00081 IN const char * command,
00082 IN const char * data
00083 )
00084 {
00085         perf::Timer timer("aesop::createMessageFromString");
00086         ASSERT(namespace_, "null");
00087         ASSERT(command, "null");
00088         ASSERT(data, "null");
00089 
00090         // even an empty message must at least contain a line return!
00091         static const long warnBytes = 1024;     // bigger than 1K is bad...
00092         long nBytes = strlen(data);
00093         ASSERT(nBytes > 0, "empty data string for message?");
00094         ASSERT('\n' == data[nBytes - 1],
00095             "data string must end in line return!\n%sEND", data);
00096         if (nBytes > warnBytes) {
00097                 DPRINTF("WARNING!  large message (%ld bytes)", nBytes);
00098                 DPRINTF("  namespace='%s'   command='%s'", namespace_, command);
00099         }
00100 
00101         // create buffer
00102         smart_ptr<netlib::MessageBuffer> buffer =
00103             netlib::MessageBuffer::create();
00104         ASSERT(buffer, "failed to create message buffer");
00105 
00106         // add payload (header + data)
00107         addHeader(buffer, namespace_, command);
00108         buffer->appendToken("data {\n");
00109         buffer->appendData(data, nBytes);
00110         buffer->appendToken("}\n");
00111         buffer->close();
00112 
00113         // DPRINTF("Message:\n%s", buffer->getData());
00114         return buffer;
00115 }
00116 
00117 
00118 
00119 static smart_ptr<netlib::MessageBuffer>
00120 createMessageFromSStream
00121 (
00122 IN const char * namespace_,
00123 IN const char * command,
00124 IN const std::ostringstream& oss
00125 )
00126 {
00127         ASSERT(namespace_, "null");
00128         ASSERT(command, "null");
00129 
00130         return createMessageFromString(namespace_, command, oss.str().c_str());
00131 }
00132 
00133 
00134 
00135 /*
00136 static smart_ptr<netlib::MessageBuffer>
00137 createMessageFromDatahash
00138 (
00139 IN const char * namespace_,
00140 IN const char * command,
00141 IN const Datahash * data
00142 )
00143 {
00144         ASSERT(namespace_, "null");
00145         ASSERT(command, "null");
00146 
00147         std::ostringstream oss;
00148         writeHashToStream(data, oss);
00149         oss << "\n";    // force termination in newline
00150 
00151         return createMessageFromString(namespace_, command, oss.str().c_str());
00152 }
00153 */
00154 
00155 
00156 
00157 ////////////////////////////////////////////////////////////////////////////////
00158 //
00159 //      public API
00160 //
00161 ////////////////////////////////////////////////////////////////////////////////
00162 
00163 
00164 
00165 ////////////////////////////////////////////////////////////////////////////////
00166 //
00167 //      Message Constructors
00168 //
00169 ////////////////////////////////////////////////////////////////////////////////
00170 
00171 /// \ingroup aesop_proto
00172 /*@{*/
00173 
00174 /// \defgroup aesop_messages AESOP Protocol Messages
00175 ///
00176 /// \n
00177 /// All TCP AESOP protocol messages have the same basic format.  They are
00178 /// plain text (low-bit ASCII only), following the Datahash format (see the
00179 /// wavepacket-lib documentation for Datahashes at
00180 /// http://wavepacket-lib.sourceforge.net/group__datahash.html)
00181 ///
00182 /// At the top level, any TCP message consists of only two elements: a
00183 /// header subhash with only two elements, and a data hash containing
00184 /// arbitrary (message-dependent) data.
00185 ///
00186 ///\code
00187 ///     header {
00188 ///             namespace       client | server
00189 ///             command         XXXXXXX
00190 ///     }
00191 ///     data {
00192 ///             [data depends on the command/message being sent]
00193 ///     }
00194 ///\endcode
00195 ///
00196 /// The header section contains only two fields:
00197 ///  - \b namespace This specifies the destination.  Only "client" and
00198 ///     "server" are supported for now.  As an example, for messages that
00199 ///     are sent from the client to the server, the namespace is "server".
00200 ///  - \b command This is the command being sent.
00201 ///
00202 /// Both the namespace and command fields can occur only once, and have only
00203 /// a simple string value.
00204 ///
00205 /// The data section is a recursive Datahash object.  The exact data depends
00206 /// on the message being sent.  Wire formats are listed below for all messages.
00207 ///
00208 /*@{*/
00209 
00210 
00211 /// \b Message: TcpConnect \n
00212 /// \b From: Client \n
00213 /// \b To: Server \n
00214 /// Clients must send one of these whenever they set up a TCP connection with
00215 /// the server.  The client must provide the token that the server gives in
00216 /// the connection response (UDP handshake).
00217 ///
00218 /// Wire Format:
00219 ///\code
00220 ///     header {
00221 ///             namespace       server
00222 ///             command         tcp
00223 ///     }
00224 ///     data {
00225 ///             token           <long>
00226 ///     }
00227 ///\endcode
00228 smart_ptr<netlib::MessageBuffer>
00229 createTcpConnectMessage
00230 (
00231 IN long token
00232 )
00233 {
00234         ASSERT(token, "null");
00235 
00236         std::ostringstream oss;
00237         oss << "token " << token << "\n";
00238 
00239         return createMessageFromSStream("server", "tcp", oss);
00240 }
00241 
00242 
00243 
00244 /// \b Message: Notify Error \n
00245 /// \b From: Server \n
00246 /// \b To: Client \n
00247 /// General error message from client to server.  This could be as a result of
00248 /// a bad message from the client, or because the server has other reasons.
00249 ///
00250 /// Wire Format:
00251 ///\code
00252 ///     header {
00253 ///             namespace       client
00254 ///             command         error
00255 ///     }
00256 ///     data {
00257 ///             errorCode       <long>
00258 ///             message         <message>
00259 ///     }
00260 ///\endcode
00261 smart_ptr<netlib::MessageBuffer>
00262 createNotifyErrorMessage
00263 (
00264 IN eErrorCode errorCode,
00265 IN const char * message
00266 )
00267 {
00268         // ASSERT(errorCode) -- don't care
00269         ASSERT(message, "null");
00270 
00271         std::ostringstream oss;
00272         oss << "errorCode " << errorCode << "\n";
00273         oss << "message " << message << "\n";
00274 
00275         return createMessageFromSStream("client", "error", oss);
00276 }
00277 
00278 
00279 
00280 /// \b Message: Conversation Dialog \n
00281 /// \b From: Server \n
00282 /// \b To: Client \n
00283 ///
00284 /// This message is sent from the server to the client when the server believes
00285 /// that a particular player needs to be shown a dialog as part of an ongoing
00286 /// conversation.  This is also the first message sent when a conversation is
00287 /// initiated, and is also sent if the client requests that the server refresh
00288 /// a current conversation (see createRefreshConversationMessage()).
00289 ///
00290 /// When it receives this message, the client should recognize that the given
00291 /// conversation is occurring (remember this conversation GUID), and present the
00292 /// given dialog to the player specified.  The client should also keep track of
00293 /// what dialog is being shown to the player as part of each conversation.
00294 ///
00295 /// In general, a player may have multiple conversations going at any one time!
00296 /// However, a given conversation will only ever have a single active dialog.
00297 ///
00298 /// Wire format:
00299 ///\code
00300 ///     header {
00301 ///             namespace               client
00302 ///             command                 converse-dialog
00303 ///     }
00304 ///     data {
00305 ///             playerId                [local player ID on client]
00306 ///             conversationGuid        XXXXXXXXXX
00307 ///             dialogId                YYYYYYYYYY
00308 ///             dialog {
00309 ///                     ...
00310 ///             }
00311 ///     }
00312 ///\endcode
00313 ///
00314 /// See also \ref conversation.
00315 smart_ptr<netlib::MessageBuffer>
00316 createConversationDialogMessage
00317 (
00318 IN int localPlayerId,
00319 IN const char * conversationGuid,
00320 IN int dialogId,
00321 IN const char * dialogData
00322 )
00323 {
00324         ASSERT(localPlayerId > 0, "Bad player ID: %d", localPlayerId);
00325         ASSERT(conversationGuid, "null");
00326         ASSERT(dialogId > 0, "Bad dialog id: %d", dialogId);
00327         ASSERT(dialogData, "null");
00328 
00329         std::ostringstream oss;
00330         oss << "playerId " << localPlayerId << "\n";
00331         oss << "conversationGuid " << conversationGuid << "\n";
00332         oss << "dialogId " << dialogId << "\n";
00333         oss << "dialog {\n";
00334         oss << dialogData;
00335         oss << "}\n";
00336 
00337         return createMessageFromSStream("client", "converse-dialog", oss);
00338 }
00339 
00340 
00341 
00342 /// \b Message: Conversation Reply \n
00343 /// \b From: Client \n
00344 /// \b To: Server \n
00345 ///
00346 /// This message is sent when a player has selected or otherwise completed a
00347 /// dialog as part of a conversation (this includes escaping or quitting the
00348 /// dialog).  See \ref dialog for details on dialogs and replies.
00349 ///
00350 /// Wire format:
00351 ///\code
00352 ///     header {
00353 ///             namespace               server
00354 ///             command                 converse-reply
00355 ///     }
00356 ///     data {
00357 ///             playerId                [local player ID on client]
00358 ///             conversationGuid        XXXXXXXX
00359 ///             dialogId                YYYYYYYY
00360 ///             reply {
00361 ///                     ...
00362 ///             }
00363 ///     }
00364 ///\endcode
00365 ///
00366 /// See also \ref conversation.
00367 smart_ptr<netlib::MessageBuffer>
00368 createConversationReplyMessage
00369 (
00370 IN int playerId,
00371 IN const char * conversationGuid,
00372 IN int dialogId,
00373 IN const char * reply
00374 )
00375 {
00376         ASSERT(playerId > 0, "Bad player id: %d", playerId);
00377         ASSERT(conversationGuid, "null");
00378         ASSERT(dialogId > 0, "Bad dialog id: %d", dialogId);
00379         ASSERT(reply, "null");
00380 
00381         std::ostringstream oss;
00382         oss << "playerId " << playerId << "\n";
00383         oss << "conversationGuid " << conversationGuid << "\n";
00384         oss << "dialogId " << dialogId << "\n";
00385         oss << "reply {\n";
00386         oss << reply << "\n";
00387         oss << "}\n";
00388 
00389         return createMessageFromSStream("server", "converse-reply", oss);
00390 }
00391 
00392 
00393 
00394 /// \b Message: Refresh Conversation \n
00395 /// \b From: Client \n
00396 /// \b To: Server \n
00397 ///
00398 /// Typically this is sent when a client believes it may have an out-of-date
00399 /// view of a particular conversation.  The server will look at the requested
00400 /// conversation ID, and send back either a Conversation Dialog message (see
00401 /// createConversationDialogMessage()) or a Terminate Conversation message (see
00402 /// createTerminateConversationMessage()).
00403 ///
00404 /// Wire format:
00405 ///\code
00406 ///     header {
00407 ///             namespace               server
00408 ///             command                 converse-refresh
00409 ///     }
00410 ///     data {
00411 ///             playerId                [local player ID on client]
00412 ///             conversationGuid        XXXXXXXX
00413 ///     }
00414 ///\endcode
00415 ///
00416 /// See also \ref conversation.
00417 smart_ptr<netlib::MessageBuffer>
00418 createRefreshConversationMessage
00419 (
00420 IN int playerId,
00421 IN const char * conversationGuid
00422 )
00423 {
00424         ASSERT(conversationGuid, "null");
00425 
00426         std::ostringstream oss;
00427         oss << "playerId " << playerId << "\n";
00428         oss << "conversationGuid " << conversationGuid << "\n";
00429 
00430         return createMessageFromSStream("server", "converse-resend", oss);
00431 }
00432 
00433 
00434 
00435 /// \b Message: Terminate Conversation \n
00436 /// \b From: Server \n
00437 /// \b To: Client \n
00438 ///
00439 /// Sent by the server to tell the client that the given conversation has now
00440 /// ended.  This is also sent if the client calls Refresh Conversation on a
00441 /// conversation that has since ended (see createRefreshConversationMessage()).
00442 ///
00443 /// See \ref createConversationDialogMessage() for more info.
00444 ///
00445 /// Wire format:
00446 ///\code
00447 ///     header {
00448 ///             namespace               client
00449 ///             command                 converse-end
00450 ///     }
00451 ///     data {
00452 ///             playerId                [local player ID on client]
00453 ///             conversationGuid        XXXXXXXX
00454 ///     }
00455 ///\endcode
00456 ///
00457 /// See also \ref conversation.
00458 smart_ptr<netlib::MessageBuffer>
00459 createTerminateConversationMessage
00460 (
00461 IN int playerId,
00462 IN const char * conversationGuid
00463 )
00464 {
00465         ASSERT(playerId > 0, "Bad player id: %d", playerId);
00466         ASSERT(conversationGuid, "null");
00467 
00468         std::ostringstream oss;
00469         oss << "playerId " << playerId << "\n";
00470         oss << "conversationGuid " << conversationGuid << "\n";
00471 
00472         return createMessageFromSStream("client", "converse-end", oss);
00473 }
00474 
00475 
00476 
00477 /// \b Message: New Game \n
00478 /// \b From: Client \n
00479 /// \b To: Server \n
00480 ///
00481 /// Sent by the client to tell the server to start a new game. \b WARNING:
00482 /// this will nuke any currently running games and start a new one!  Make
00483 /// sure there are warning dialogs etc. before you let a user send this
00484 /// message.
00485 ///
00486 /// Wire format:
00487 ///\code
00488 ///     header {
00489 ///             namespace               server
00490 ///             command                 new-game
00491 ///     }
00492 ///     data {
00493 ///             playerId                [local player ID on client]
00494 ///     }
00495 ///\endcode
00496 smart_ptr<netlib::MessageBuffer>
00497 createNewGameMessage
00498 (
00499 IN int playerId
00500 )
00501 {
00502         ASSERT(playerId > 0, "Bad player id: %d", playerId);
00503 
00504         std::ostringstream oss;
00505         oss << "playerId " << playerId << "\n";
00506 
00507         return createMessageFromSStream("server", "new-game", oss);
00508 }
00509 
00510 
00511 };      // aesop namespace
00512