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

Generated on Mon Jan 12 10:51:13 2009 for Barry by  doxygen 1.5.7.1