m_desktop.cc

Go to the documentation of this file.
00001 ///
00002 /// \file       m_desktop.cc
00003 ///             Mode class for the Desktop mode
00004 ///
00005 
00006 /*
00007     Copyright (C) 2005-2007, Net Direct Inc. (http://www.netdirect.ca/)
00008 
00009     This program is free software; you can redistribute it and/or modify
00010     it under the terms of the GNU General Public License as published by
00011     the Free Software Foundation; either version 2 of the License, or
00012     (at your option) any later version.
00013 
00014     This program is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018     See the GNU General Public License in the COPYING file at the
00019     root directory of this project for more details.
00020 */
00021 
00022 #include "m_desktop.h"
00023 #include "data.h"
00024 #include "protocol.h"
00025 #include "protostructs.h"
00026 #include "packet.h"
00027 #include "endian.h"
00028 #include "error.h"
00029 #include "usbwrap.h"
00030 #include "controller.h"
00031 #include <stdexcept>
00032 #include <sstream>
00033 
00034 #include "debug.h"
00035 
00036 namespace Barry { namespace Mode {
00037 
00038 
00039 ///////////////////////////////////////////////////////////////////////////////
00040 // Desktop Mode class
00041 
00042 Desktop::Desktop(Controller &con)
00043         : m_con(con)
00044         , m_ModeSocket(0)
00045 {
00046 }
00047 
00048 Desktop::~Desktop()
00049 {
00050 }
00051 
00052 ///////////////////////////////////////////////////////////////////////////////
00053 // protected members
00054 
00055 void Desktop::LoadCommandTable()
00056 {
00057         char rawCommand[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
00058         *((uint16_t*) rawCommand) = htobs(m_socket->GetSocket());
00059 
00060         Data command(rawCommand, sizeof(rawCommand));
00061         Data response;
00062 
00063         try {
00064                 m_socket->Packet(command, response);
00065 
00066                 MAKE_PACKET(rpack, response);
00067                 while( rpack->command != SB_COMMAND_DB_DONE ) {
00068                         m_socket->NextRecord(response);
00069 
00070                         rpack = (const Protocol::Packet *) response.GetData();
00071                         if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
00072                                 // second packet is generally large, and contains
00073                                 // the command table
00074                                 m_commandTable.Clear();
00075                                 m_commandTable.Parse(response, 6);
00076                         }
00077                 }
00078 
00079                 ddout(m_commandTable);
00080 
00081         }
00082         catch( Usb::Error & ) {
00083                 eout("Desktop: error getting command table");
00084                 eeout(command, response);
00085                 throw;
00086         }
00087 }
00088 
00089 void Desktop::LoadDBDB()
00090 {
00091         Data command, response;
00092         DBPacket packet(*this, command, response);
00093         packet.GetDBDB();
00094 
00095         m_socket->Packet(packet);
00096 
00097         while( packet.Command() != SB_COMMAND_DB_DONE ) {
00098                 if( packet.Command() == SB_COMMAND_DB_DATA ) {
00099                         m_dbdb.Clear();
00100                         m_dbdb.Parse(response);
00101                 }
00102 
00103                 // advance!
00104                 m_socket->NextRecord(response);
00105         }
00106 }
00107 
00108 
00109 
00110 ///////////////////////////////////////////////////////////////////////////////
00111 // public API
00112 
00113 //
00114 // Open
00115 //
00116 /// Select device mode.  This is required before using any other mode-based
00117 /// operations, such as GetDBDB() and LoadDatabase().
00118 ///
00119 /// This function opens a socket to the device for communicating in Desktop
00120 /// mode.  If the device requires it, specify the password with a const char*
00121 /// string in password.  The password will not be stored in memory
00122 /// inside this class, only a hash will be generated from it.  After
00123 /// using the hash, the hash memory will be set to 0.  The application
00124 /// is responsible for safely handling the raw password data.
00125 ///
00126 /// You can retry the password by catching Barry::BadPassword and
00127 /// calling RetryPassword() with the new password.
00128 ///
00129 /// \exception  Barry::Error
00130 ///             Thrown on protocol error.
00131 ///
00132 /// \exception  std::logic_error()
00133 ///             Thrown if unsupported mode is requested, or if socket
00134 ///             already open.
00135 ///
00136 /// \exception  Barry::BadPassword
00137 ///             Thrown when password is invalid or if not enough retries
00138 ///             left in the device.
00139 ///
00140 void Desktop::Open(const char *password)
00141 {
00142         if( m_ModeSocket ) {
00143                 m_socket->Close();
00144                 m_socket.reset();
00145                 m_ModeSocket = 0;
00146         }
00147 
00148         m_ModeSocket = m_con.SelectMode(Controller::Desktop);
00149         RetryPassword(password);
00150 }
00151 
00152 //
00153 // RetryPassword
00154 //
00155 /// Retry a failed password attempt from the first call to Open().
00156 /// Only call this function in response to Barry::BadPassword exceptions
00157 /// that are thrown from Open().
00158 ///
00159 /// \exception  Barry::Error
00160 ///             Thrown on protocol error.
00161 ///
00162 /// \exception  std::logic_error()
00163 ///             Thrown if in unsupported mode, or if socket already open.
00164 ///
00165 /// \exception  Barry::BadPassword
00166 ///             Thrown when password is invalid or if not enough retries
00167 ///             left in the device.
00168 ///
00169 void Desktop::RetryPassword(const char *password)
00170 {
00171         if( m_socket.get() != 0 )
00172                 throw std::logic_error("Socket alreay open in RetryPassword");
00173 
00174         m_socket = m_con.m_zero.Open(m_ModeSocket, password);
00175 
00176         // get command table and database database
00177         LoadCommandTable();
00178         LoadDBDB();
00179 }
00180 
00181 //
00182 // GetDBID
00183 //
00184 /// Get numeric database ID by name.
00185 ///
00186 /// \param[in]  name            Name of database, which matches one of the
00187 ///                             names listed in GetDBDB()
00188 ///
00189 /// \exception  Barry::Error
00190 ///             Thrown if name not found.
00191 ///
00192 unsigned int Desktop::GetDBID(const std::string &name) const
00193 {
00194         unsigned int ID = 0;
00195         // FIXME - this needs a better error handler...
00196         if( !m_dbdb.GetDBNumber(name, ID) ) {
00197                 throw Error("Desktop: database name not found: " + name);
00198         }
00199         return ID;
00200 }
00201 
00202 //
00203 // GetDBCommand
00204 //
00205 /// Get database command from command table.  Must call Open()
00206 /// before this.
00207 ///
00208 unsigned int Desktop::GetDBCommand(CommandType ct)
00209 {
00210         unsigned int cmd = 0;
00211         const char *cmdName = "Unknown";
00212 
00213         switch( ct )
00214         {
00215         case DatabaseAccess:
00216                 cmdName = "Database Access";
00217                 cmd = m_commandTable.GetCommand(cmdName);
00218                 break;
00219         default:
00220                 throw std::logic_error("Desktop: unknown command type");
00221         }
00222 
00223         if( cmd == 0 ) {
00224                 std::ostringstream oss;
00225                 oss << "Desktop: unable to get command code: " << cmdName;
00226                 throw Error(oss.str());
00227         }
00228 
00229         return cmd;
00230 }
00231 
00232 //
00233 // GetRecordStateTable
00234 //
00235 /// Retrieve the record state table from the handheld device, using the given
00236 /// database ID.  Results will be stored in result, which will be cleared
00237 /// before adding.
00238 ///
00239 void Desktop::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
00240 {
00241         dout("Database ID: " << dbId);
00242 
00243         // start fresh
00244         result.Clear();
00245 
00246         Data command, response;
00247         DBPacket packet(*this, command, response);
00248         packet.GetRecordStateTable(dbId);
00249 
00250         m_socket->Packet(packet);
00251         result.Parse(response);
00252 
00253         // flush the command sequence
00254         while( packet.Command() != SB_COMMAND_DB_DONE )
00255                 m_socket->NextRecord(response);
00256 }
00257 
00258 //
00259 // AddRecord
00260 //
00261 /// Adds a record to the specified database.  RecordId is
00262 /// retrieved from build, and duplicate IDs are allowed by the device
00263 /// (i.e. you can have two records with the same ID) 
00264 /// but *not* recommended!
00265 //
00266 void Desktop::AddRecord(unsigned int dbId, Builder &build)
00267 {
00268         dout("Database ID: " << dbId);
00269 
00270         Data command, response;
00271         DBPacket packet(*this, command, response);
00272 
00273         if( packet.SetRecord(dbId, build) ) {
00274 
00275                 std::ostringstream oss;
00276 
00277                 m_socket->Packet(packet);
00278 
00279                 // successful packet transfer, so check the network return code
00280                 if( packet.Command() != SB_COMMAND_DB_DONE ) {
00281                         oss << "Desktop: device responded with unexpected packet command code: "
00282                             << "0x" << std::hex << packet.Command();
00283                         throw Error(oss.str());
00284                 }
00285 
00286                 if( packet.ReturnCode() != 0 ) {
00287                         oss << "Desktop: device responded with error code (command: "
00288                             << packet.Command() << ", code: "
00289                             << packet.ReturnCode() << ")";
00290                         throw Error(oss.str());
00291                 }
00292         }
00293 }
00294 
00295 //
00296 // GetRecord
00297 //
00298 /// Retrieves a specific record from the specified database.
00299 /// The stateTableIndex comes from the GetRecordStateTable()
00300 /// function.  GetRecord() does not clear the dirty flag.
00301 ///
00302 void Desktop::GetRecord(unsigned int dbId,
00303                            unsigned int stateTableIndex,
00304                            Parser &parser)
00305 {
00306         dout("Database ID: " << dbId);
00307 
00308         Data command, response;
00309         DBPacket packet(*this, command, response);
00310         packet.GetRecordByIndex(dbId, stateTableIndex);
00311 
00312         m_socket->Packet(packet);
00313 
00314         // perform copious packet checks
00315         if( response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
00316                 eeout(command, response);
00317 
00318                 std::ostringstream oss;
00319                 oss << "Desktop: invalid response packet size of "
00320                     << std::dec << response.GetSize();
00321                 eout(oss.str());
00322                 throw Error(oss.str());
00323         }
00324         if( packet.Command() != SB_COMMAND_DB_DATA ) {
00325                 eeout(command, response);
00326 
00327                 std::ostringstream oss;
00328                 oss << "Desktop: unexpected command of 0x"
00329                     << std::setbase(16) << packet.Command()
00330                     << " instead of expected 0x"
00331                     << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
00332                 eout(oss.str());
00333                 throw Error(oss.str());
00334         }
00335 
00336         // grab that data
00337         packet.Parse(parser);
00338 
00339         // flush the command sequence
00340         while( packet.Command() != SB_COMMAND_DB_DONE )
00341                 m_socket->NextRecord(response);
00342 }
00343 
00344 //
00345 // SetRecord
00346 //
00347 /// Overwrites a specific record in the device as identified by the
00348 /// stateTableIndex.
00349 ///
00350 void Desktop::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
00351                            Builder &build)
00352 {
00353         dout("Database ID: " << dbId << " Index: " << stateTableIndex);
00354 
00355         Data command, response;
00356         DBPacket packet(*this, command, response);
00357 
00358         // loop until builder object has no more data
00359         if( !packet.SetRecordByIndex(dbId, stateTableIndex, build) ) {
00360                 throw std::logic_error("Desktop: no data available in SetRecord");
00361         }
00362 
00363         m_socket->Packet(packet);
00364 
00365         std::ostringstream oss;
00366 
00367         // successful packet transfer, so check the network return code
00368         if( packet.Command() != SB_COMMAND_DB_DONE ) {
00369                 oss << "Desktop: device responded with unexpected packet command code: "
00370                     << "0x" << std::hex << packet.Command();
00371                 throw Error(oss.str());
00372         }
00373 
00374         if( packet.ReturnCode() != 0 ) {
00375                 oss << "Desktop: device responded with error code (command: "
00376                     << packet.Command() << ", code: "
00377                     << packet.ReturnCode() << ")";
00378                 throw Error(oss.str());
00379         }
00380 }
00381 
00382 //
00383 // ClearDirty
00384 //
00385 /// Clears the dirty flag on the specified record in the specified database.
00386 ///
00387 void Desktop::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
00388 {
00389         dout("Database ID: " << dbId);
00390 
00391         Data command, response;
00392         DBPacket packet(*this, command, response);
00393         packet.SetRecordFlags(dbId, stateTableIndex, 0);
00394 
00395         m_socket->Packet(packet);
00396 
00397         // flush the command sequence
00398         while( packet.Command() != SB_COMMAND_DB_DONE )
00399                 m_socket->NextRecord(response);
00400 }
00401 
00402 //
00403 // DeleteRecord
00404 //
00405 /// Deletes the specified record in the specified database.
00406 ///
00407 void Desktop::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
00408 {
00409         dout("Database ID: " << dbId);
00410 
00411         Data command, response;
00412         DBPacket packet(*this, command, response);
00413         packet.DeleteRecordByIndex(dbId, stateTableIndex);
00414 
00415         m_socket->Packet(packet);
00416 
00417         // flush the command sequence
00418         while( packet.Command() != SB_COMMAND_DB_DONE )
00419                 m_socket->NextRecord(response);
00420 }
00421 
00422 //
00423 // LoadDatabase
00424 //
00425 /// Retrieve a database from the handheld device, using the given parser
00426 /// to parse the resulting data, and optionally store it.
00427 ///
00428 /// See the RecordParser<> template to create a parser object.  The
00429 /// RecordParser<> template allows custom storage based on the type of
00430 /// database record retrieved.  The database ID and the parser Record
00431 /// type must match.
00432 ///
00433 /// \param[in]  dbId            Database Database ID - use GetDBID()
00434 /// \param[out] parser          Parser object which parses the resulting
00435 ///                             protocol data, and optionally stores it in
00436 ///                             a custom fashion.  See the RecordParser<>
00437 ///                             template.
00438 ///
00439 /// \exception  Barry::Error
00440 ///             Thrown on protocol error.
00441 ///
00442 /// \exception  std::logic_error
00443 ///             Thrown if not in Desktop mode.
00444 ///
00445 void Desktop::LoadDatabase(unsigned int dbId, Parser &parser)
00446 {
00447         dout("Database ID: " << dbId);
00448 
00449         Data command, response;
00450         DBPacket packet(*this, command, response);
00451         packet.GetRecords(dbId);
00452 
00453         m_socket->Packet(packet);
00454 
00455         while( packet.Command() != SB_COMMAND_DB_DONE ) {
00456                 if( packet.Command() == SB_COMMAND_DB_DATA ) {
00457                         // this size is the old header size, since using
00458                         // old command above
00459                         packet.Parse(parser);
00460                 }
00461 
00462                 // advance!
00463                 m_socket->NextRecord(response);
00464         }
00465 }
00466 
00467 void Desktop::SaveDatabase(unsigned int dbId, Builder &builder)
00468 {
00469         dout("Database ID: " << dbId);
00470 
00471         // Protocol note: so far in testing, this CLEAR_DATABASE operation is
00472         //                required, since every record sent via SET_RECORD
00473         //                is treated like a hypothetical "ADD_RECORD" (perhaps
00474         //                SET_RECORD should be renamed)... I don't know if
00475         //                there is a real SET_RECORD... all I know is from
00476         //                the Windows USB captures, which uses this same
00477         //                technique.
00478         Data command, response;
00479         DBPacket packet(*this, command, response);
00480         packet.ClearDatabase(dbId);
00481 
00482         // wait up to a minute here for old, slower devices with lots of data
00483         m_socket->Packet(packet, 60000);
00484         if( packet.ReturnCode() != 0 ) {
00485                 std::ostringstream oss;
00486                 oss << "Desktop: could not clear database: (command: "
00487                     << "0x" << std::hex << packet.Command() << ", code: "
00488                     << "0x" << std::hex << packet.ReturnCode() << ")";
00489                 throw Error(oss.str());
00490         }
00491 
00492         // check response to clear command was successful
00493         if( packet.Command() != SB_COMMAND_DB_DONE ) {
00494                 eeout(command, response);
00495                 throw Error("Desktop: error clearing database, bad response");
00496         }
00497 
00498         // loop until builder object has no more data
00499         bool first = true;
00500         while( packet.SetRecord(dbId, builder) ) {
00501                 dout("Database ID: " << dbId);
00502 
00503                 m_socket->Packet(packet, first ? 60000 : -1);
00504                 first = false;
00505 
00506                 std::ostringstream oss;
00507                 // successful packet transfer, so check the network return code
00508                 if( packet.Command() != SB_COMMAND_DB_DONE ) {
00509                         oss << "Desktop: device responded with unexpected packet command code: "
00510                             << "0x" << std::hex << packet.Command();
00511                         throw Error(oss.str());
00512                 }
00513 
00514                 if( packet.ReturnCode() != 0 ) {
00515                         oss << "Desktop: device responded with error code (command: "
00516                             << packet.Command() << ", code: "
00517                             << packet.ReturnCode() << ")";
00518                         throw Error(oss.str());
00519                 }
00520         }
00521 }
00522 
00523 }} // namespace Barry::Mode
00524 

Generated on Wed Sep 24 21:27:32 2008 for Barry by  doxygen 1.5.1