Release tarball for barry-0.9
[barry.git] / src / controller.cc
blob9cc087fbcaddd074118b20f1c141691730abeea2
1 ///
2 /// \file controller.cc
3 /// High level Barry API class
4 ///
6 /*
7 Copyright (C) 2005-2007, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "controller.h"
23 #include "common.h"
24 #include "protocol.h"
25 #include "protostructs.h"
26 #include "error.h"
27 #include "data.h"
28 #include "parser.h"
29 #include "builder.h"
30 #include "endian.h"
31 #include "packet.h"
33 #define __DEBUG_MODE__
34 #include "debug.h"
36 #include <sstream>
38 #include <iomanip>
40 namespace Barry {
43 // Controller constructor
45 /// Constructor for the Controller class. Requires a valid ProbeResult
46 /// object to find the USB device to talk to.
47 ///
48 /// \param[in] device One of the ProbeResult objects from the
49 /// Probe class.
50 ///
51 Controller::Controller(const ProbeResult &device)
52 : m_dev(device.m_dev),
53 m_iface(0),
54 m_pin(device.m_pin),
55 m_socket(m_dev, device.m_ep.write, device.m_ep.read, device.m_zeroSocketSequence),
56 m_mode(Unspecified),
57 m_modeSocket(0),
58 m_halfOpen(false)
60 if( !m_dev.SetConfiguration(BLACKBERRY_CONFIGURATION) )
61 throw Usb::Error(m_dev.GetLastError(),
62 "Controller: SetConfiguration failed");
64 m_iface = new Usb::Interface(m_dev, device.m_interface);
67 Controller::~Controller()
69 // trap exceptions in the destructor
70 try {
71 // a non-default socket has been opened, close it
72 m_socket.Close();
74 catch( std::runtime_error &re ) {
75 // do nothing... log it?
76 dout("Exception caught in ~Socket: " << re.what());
79 // cleanup the interface
80 delete m_iface;
82 // this happens when for some reason the Desktop mode
83 // is not fully opened, but the device has already recommended
84 // a socket to open... in this case, reset the device
85 // in the hopes that on next open, it will be in a
86 // recognizable state.
88 // FIXME - this should not be necessary, and someday we
89 // we should figure out how to handle the "already open"
90 // response we get for the Desktop
92 if( m_halfOpen ) {
93 dout("Controller object destroyed in halfopen state, resetting device");
94 m_dev.Reset();
98 ///////////////////////////////////////////////////////////////////////////////
99 // protected members
101 void Controller::SelectMode(ModeType mode)
103 // start fresh
104 m_modeSocket = 0;
106 // select mode
107 Protocol::Packet packet;
108 packet.socket = 0;
109 packet.size = htobs(SB_MODE_PACKET_COMMAND_SIZE);
110 packet.command = SB_COMMAND_SELECT_MODE;
111 packet.u.socket.socket = htobs(SB_MODE_REQUEST_SOCKET);
112 packet.u.socket.sequence = 0; // updated by Socket::Send()
113 memset(packet.u.socket.u.mode.name, 0, sizeof(packet.u.socket.u.mode.name));
115 char *modeName = (char *) packet.u.socket.u.mode.name;
116 switch( mode )
118 case Bypass:
119 strcpy(modeName, "RIM Bypass");
120 break;
122 case Desktop:
123 strcpy(modeName, "RIM Desktop");
124 break;
126 case JavaLoader:
127 strcpy(modeName, "RIM_JavaLoader");
128 break;
130 case UsbSerData:
131 strcpy(modeName, "RIM_UsbSerData");
132 break;
134 default:
135 throw std::logic_error("Controller: Invalid mode in SelectMode");
136 break;
139 // send mode command before we open, as a default socket is socket 0
140 Data command(&packet, btohs(packet.size));
141 Data response;
143 try {
144 m_socket.Send(command, response);
146 // get the data socket number
147 // indicates the socket number that
148 // should be used below in the Open() call
149 Protocol::CheckSize(response, SB_MODE_PACKET_RESPONSE_SIZE);
150 MAKE_PACKET(modepack, response);
151 if( modepack->command != SB_COMMAND_MODE_SELECTED ) {
152 eeout(command, response);
153 throw Error("Controller: mode not selected");
156 // save the socket that the device is expecting us to use
157 m_modeSocket = btohs(modepack->u.socket.socket);
159 catch( Usb::Error & ) {
160 eout("Controller: error setting desktop mode");
161 eeout(command, response);
162 throw;
166 unsigned int Controller::GetCommand(CommandType ct)
168 unsigned int cmd = 0;
169 char *cmdName = "Unknown";
171 switch( ct )
173 case DatabaseAccess:
174 cmdName = "Database Access";
175 cmd = m_commandTable.GetCommand(cmdName);
176 break;
177 default:
178 throw std::logic_error("Controller: unknown command type");
181 if( cmd == 0 ) {
182 std::ostringstream oss;
183 oss << "Controller: unable to get command code: " << cmdName;
184 throw Error(oss.str());
187 return cmd;
190 void Controller::LoadCommandTable()
192 assert( m_mode == Desktop );
194 char rawCommand[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
195 *((uint16_t*) rawCommand) = htobs(m_socket.GetSocket());
197 Data command(rawCommand, sizeof(rawCommand));
198 Data response;
200 try {
201 m_socket.Packet(command, response);
203 MAKE_PACKET(rpack, response);
204 while( rpack->command != SB_COMMAND_DB_DONE ) {
205 m_socket.NextRecord(response);
207 rpack = (const Protocol::Packet *) response.GetData();
208 if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
209 // second packet is generally large, and contains
210 // the command table
211 m_commandTable.Clear();
212 m_commandTable.Parse(response, 6);
216 ddout(m_commandTable);
219 catch( Usb::Error & ) {
220 eout("Controller: error getting command table");
221 eeout(command, response);
222 throw;
226 void Controller::LoadDBDB()
228 assert( m_mode == Desktop );
230 Data command, response;
231 DBPacket packet(*this, command, response);
232 packet.GetDBDB();
234 m_socket.Packet(packet);
236 while( packet.Command() != SB_COMMAND_DB_DONE ) {
237 if( packet.Command() == SB_COMMAND_DB_DATA ) {
238 m_dbdb.Clear();
239 m_dbdb.Parse(response);
242 // advance!
243 m_socket.NextRecord(response);
248 ///////////////////////////////////////////////////////////////////////////////
249 // public API
252 // GetDBID
254 /// Get numeric database ID by name.
256 /// \param[in] name Name of database, which matches one of the
257 /// names listed in GetDBDB()
259 /// \exception Barry::Error
260 /// Thrown if name not found.
262 unsigned int Controller::GetDBID(const std::string &name) const
264 unsigned int ID = 0;
265 // FIXME - this needs a better error handler...
266 if( !m_dbdb.GetDBNumber(name, ID) ) {
267 throw Error("Controller: database name not found: " + name);
269 return ID;
273 // OpenMode
275 /// Select device mode. This is required before using any other mode-based
276 /// operations, such as GetDBDB() and LoadDatabase(). Currently only
277 /// Desktop mode is supported, but the following modes are available.
278 /// (See ModeType)
280 /// - Controller::Bypass
281 /// - Controller::Desktop
282 /// - Controller::JavaLoader
284 /// This function opens a socket to the device when in Desktop mode.
285 /// If the device requires it, specify the password with a conat char*
286 /// string in password. The password will not be stored in memory
287 /// inside this class, only a hash will be generated from it. After
288 /// using the hash, the hash memory will be set to 0. The application
289 /// is responsible for safely handling the raw password data.
291 /// You can retry the password by catching Barry::BadPassword and
292 /// calling RetryPassword() with the new password.
294 /// \exception Barry::Error
295 /// Thrown on protocol error.
297 /// \exception std::logic_error()
298 /// Thrown if unsupported mode is requested, or if socket
299 /// already open.
301 /// \exception Barry::BadPassword
302 /// Thrown when password is invalid or if not enough retries
303 /// left in the device.
305 void Controller::OpenMode(ModeType mode, const char *password)
307 if( m_mode != mode ) {
308 m_socket.Close();
309 SelectMode(mode);
310 m_mode = mode;
312 RetryPassword(password);
317 // RetryPassword
319 /// Retry a failed password attempt from the first call to OpenMode().
320 /// Only call this function on Barry::BadPassword exceptions thrown
321 /// from OpenMode().
323 /// \exception Barry::Error
324 /// Thrown on protocol error.
326 /// \exception std::logic_error()
327 /// Thrown if in unsupported mode, or if socket already open.
329 /// \exception Barry::BadPassword
330 /// Thrown when password is invalid or if not enough retries
331 /// left in the device.
333 void Controller::RetryPassword(const char *password)
335 if( m_mode != Desktop )
336 throw std::logic_error("Wrong mode in RetryPassword");
338 if( m_socket.GetSocket() != 0 )
339 throw std::logic_error("Socket alreay open in RetryPassword");
341 m_halfOpen = true;
342 m_socket.Open(m_modeSocket, password);
343 m_halfOpen = false;
345 switch( m_mode )
347 case Desktop:
348 // get command table
349 LoadCommandTable();
351 // get database database
352 LoadDBDB();
353 break;
355 case UsbSerData:
356 // nothing to do
357 break;
359 default:
360 throw std::logic_error("Mode not implemented");
365 // GetRecordStateTable
367 /// Retrieve the record state table from the handheld device, using the given
368 /// database ID. Results will be stored in result, which will be cleared
369 /// before adding.
371 void Controller::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
373 if( m_mode != Desktop )
374 throw std::logic_error("Wrong mode in GetRecordStateTable");
376 dout("Database ID: " << dbId);
378 // start fresh
379 result.Clear();
381 Data command, response;
382 DBPacket packet(*this, command, response);
383 packet.GetRecordStateTable(dbId);
385 m_socket.Packet(packet);
386 result.Parse(response);
388 // flush the command sequence
389 while( packet.Command() != SB_COMMAND_DB_DONE )
390 m_socket.NextRecord(response);
394 // AddRecord
396 /// Adds a record to the specified database. RecordId is
397 /// retrieved from build, and duplicate IDs are allowed by the device
398 /// (i.e. you can have two records with the same ID)
399 /// but *not* recommended!
401 void Controller::AddRecord(unsigned int dbId, Builder &build)
403 if( m_mode != Desktop )
404 throw std::logic_error("Wrong mode in GetRecord");
406 dout("Database ID: " << dbId);
408 Data command, response;
409 DBPacket packet(*this, command, response);
411 if( packet.SetRecord(dbId, build) ) {
413 std::ostringstream oss;
415 m_socket.Packet(packet);
417 // successful packet transfer, so check the network return code
418 if( packet.Command() != SB_COMMAND_DB_DONE ) {
419 oss << "Controller: device responded with unexpected packet command code: "
420 << "0x" << std::hex << packet.Command();
421 throw Error(oss.str());
424 if( packet.ReturnCode() != 0 ) {
425 oss << "Controller: device responded with error code (command: "
426 << packet.Command() << ", code: "
427 << packet.ReturnCode() << ")";
428 throw Error(oss.str());
434 // GetRecord
436 /// Retrieves a specific record from the specified database.
437 /// The stateTableIndex comes from the GetRecordStateTable()
438 /// function. GetRecord() does not clear the dirty flag.
440 void Controller::GetRecord(unsigned int dbId,
441 unsigned int stateTableIndex,
442 Parser &parser)
444 if( m_mode != Desktop )
445 throw std::logic_error("Wrong mode in GetRecord");
447 dout("Database ID: " << dbId);
449 Data command, response;
450 DBPacket packet(*this, command, response);
451 packet.GetRecordByIndex(dbId, stateTableIndex);
453 m_socket.Packet(packet);
455 // perform copious packet checks
456 if( response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
457 eeout(command, response);
459 std::ostringstream oss;
460 oss << "Controller: invalid response packet size of "
461 << std::dec << response.GetSize();
462 eout(oss.str());
463 throw Error(oss.str());
465 if( packet.Command() != SB_COMMAND_DB_DATA ) {
466 eeout(command, response);
468 std::ostringstream oss;
469 oss << "Controller: unexpected command of 0x"
470 << std::setbase(16) << packet.Command()
471 << " instead of expected 0x"
472 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
473 eout(oss.str());
474 throw Error(oss.str());
477 // grab that data
478 packet.Parse(parser);
480 // flush the command sequence
481 while( packet.Command() != SB_COMMAND_DB_DONE )
482 m_socket.NextRecord(response);
486 // SetRecord
488 /// Overwrites a specific record in the device as identified by the
489 /// stateTableIndex.
491 void Controller::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
492 Builder &build)
494 if( m_mode != Desktop )
495 throw std::logic_error("Wrong mode in SetRecord");
497 dout("Database ID: " << dbId << " Index: " << stateTableIndex);
499 Data command, response;
500 DBPacket packet(*this, command, response);
502 // loop until builder object has no more data
503 if( !packet.SetRecordByIndex(dbId, stateTableIndex, build) ) {
504 throw std::logic_error("Controller: no data available in SetRecord");
507 m_socket.Packet(packet);
509 std::ostringstream oss;
511 // successful packet transfer, so check the network return code
512 if( packet.Command() != SB_COMMAND_DB_DONE ) {
513 oss << "Controller: device responded with unexpected packet command code: "
514 << "0x" << std::hex << packet.Command();
515 throw Error(oss.str());
518 if( packet.ReturnCode() != 0 ) {
519 oss << "Controller: device responded with error code (command: "
520 << packet.Command() << ", code: "
521 << packet.ReturnCode() << ")";
522 throw Error(oss.str());
527 // ClearDirty
529 /// Clears the dirty flag on the specified record in the specified database.
531 void Controller::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
533 if( m_mode != Desktop )
534 throw std::logic_error("Wrong mode in ClearDirty");
536 dout("Database ID: " << dbId);
538 Data command, response;
539 DBPacket packet(*this, command, response);
540 packet.SetRecordFlags(dbId, stateTableIndex, 0);
542 m_socket.Packet(packet);
544 // flush the command sequence
545 while( packet.Command() != SB_COMMAND_DB_DONE )
546 m_socket.NextRecord(response);
550 // DeleteRecord
552 /// Deletes the specified record in the specified database.
554 void Controller::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
556 if( m_mode != Desktop )
557 throw std::logic_error("Wrong mode in DeleteRecord");
559 dout("Database ID: " << dbId);
561 Data command, response;
562 DBPacket packet(*this, command, response);
563 packet.DeleteRecordByIndex(dbId, stateTableIndex);
565 m_socket.Packet(packet);
567 // flush the command sequence
568 while( packet.Command() != SB_COMMAND_DB_DONE )
569 m_socket.NextRecord(response);
573 // LoadDatabase
575 /// Retrieve a database from the handheld device, using the given parser
576 /// to parse the resulting data, and optionally store it.
578 /// See the RecordParser<> template to create a parser object. The
579 /// RecordParser<> template allows custom storage based on the type of
580 /// database record retrieved. The database ID and the parser Record
581 /// type must match.
583 /// \param[in] dbId Database Database ID - use GetDBID()
584 /// \param[out] parser Parser object which parses the resulting
585 /// protocol data, and optionally stores it in
586 /// a custom fashion. See the RecordParser<>
587 /// template.
589 /// \exception Barry::Error
590 /// Thrown on protocol error.
592 /// \exception std::logic_error
593 /// Thrown if not in Desktop mode.
595 void Controller::LoadDatabase(unsigned int dbId, Parser &parser)
597 if( m_mode != Desktop )
598 throw std::logic_error("Wrong mode in LoadDatabase");
600 dout("Database ID: " << dbId);
602 Data command, response;
603 DBPacket packet(*this, command, response);
604 packet.GetRecords(dbId);
606 m_socket.Packet(packet);
608 while( packet.Command() != SB_COMMAND_DB_DONE ) {
609 if( packet.Command() == SB_COMMAND_DB_DATA ) {
610 // this size is the old header size, since using
611 // old command above
612 packet.Parse(parser);
615 // advance!
616 m_socket.NextRecord(response);
620 void Controller::SaveDatabase(unsigned int dbId, Builder &builder)
622 if( m_mode != Desktop )
623 throw std::logic_error("Wrong mode in SaveDatabase");
625 dout("Database ID: " << dbId);
627 // Protocol note: so far in testing, this CLEAR_DATABASE operation is
628 // required, since every record sent via SET_RECORD
629 // is treated like a hypothetical "ADD_RECORD" (perhaps
630 // SET_RECORD should be renamed)... I don't know if
631 // there is a real SET_RECORD... all I know is from
632 // the Windows USB captures, which uses this same
633 // technique.
634 Data command, response;
635 DBPacket packet(*this, command, response);
636 packet.ClearDatabase(dbId);
638 // wait up to a minute here for old, slower devices with lots of data
639 m_socket.Packet(packet, 60000);
640 if( packet.ReturnCode() != 0 ) {
641 std::ostringstream oss;
642 oss << "Controller: could not clear database: (command: "
643 << "0x" << std::hex << packet.Command() << ", code: "
644 << "0x" << std::hex << packet.ReturnCode() << ")";
645 throw Error(oss.str());
648 // check response to clear command was successful
649 if( packet.Command() != SB_COMMAND_DB_DONE ) {
650 eeout(command, response);
651 throw Error("Controller: error clearing database, bad response");
654 // loop until builder object has no more data
655 bool first = true;
656 while( packet.SetRecord(dbId, builder) ) {
657 dout("Database ID: " << dbId);
659 m_socket.Packet(packet, first ? 60000 : -1);
660 first = false;
662 std::ostringstream oss;
663 // successful packet transfer, so check the network return code
664 if( packet.Command() != SB_COMMAND_DB_DONE ) {
665 oss << "Controller: device responded with unexpected packet command code: "
666 << "0x" << std::hex << packet.Command();
667 throw Error(oss.str());
670 if( packet.ReturnCode() != 0 ) {
671 oss << "Controller: device responded with error code (command: "
672 << packet.Command() << ", code: "
673 << packet.ReturnCode() << ")";
674 throw Error(oss.str());
681 //////////////////////////////////////////////////////////////////////////////
682 // UsbSerData mode - modem specific
684 // can be called from separate thread
685 void Controller::SerialRead(Data &data, int timeout)
687 if( m_mode != UsbSerData )
688 throw std::logic_error("Wrong mode in SerialRead");
690 m_socket.Receive(data, timeout);
693 // based on Rick Scott's XmBlackBerry's serdata.c
694 void Controller::SerialWrite(const Data &data)
696 if( m_mode != UsbSerData )
697 throw std::logic_error("Wrong mode in SerialWrite");
699 if( data.GetSize() <= 0 )
700 return; // nothing to do
702 int size = data.GetSize() + 4;
703 unsigned char *buf = m_writeCache.GetBuffer(size);
704 MAKE_PACKETPTR_BUF(spack, buf);
706 // copy data over to cache packet
707 memcpy(&buf[4], data.GetData(), data.GetSize());
709 // setup header
710 spack->socket = htobs(m_socket.GetSocket());
711 spack->size = htobs(size);
713 // release and send
714 m_writeCache.ReleaseBuffer(size);
715 m_socket.Send(m_writeCache);
718 unsigned char buf[0x400];
719 int num_read;
720 int i;
723 // This is pretty ugly, but I have to put the HDLC flags into
724 // the packets. RIM seems to need flags around every frame, and
725 // a flag _cannot_ be an end and a start flag.
727 for (i = 0; i < num_read; i++) {
728 BufferAdd(&serdata->data, &buf[i], 1);
729 if (BufferData(&serdata->data)[0] == 0x7e && buf[i] == 0x7e) {
730 if (BufferLen(&serdata->data) > 1 &&
731 BufferData(&serdata->data)[0] == 0x7e &&
732 BufferData(&serdata->data)[1] == 0x7e)
734 BufferPullHead(&serdata->data, 1);
736 else
739 if (BufferLen(&serdata->data) > 2)
741 if ((BufferLen(&serdata->data) + 4) % 16 == 0)
743 BufferAdd(&serdata->data, (unsigned char *)"\0", 1);
745 send_packet(serdata, BufferData(&serdata->data), BufferLen(&serdata->data));
746 BufferEmpty(&serdata->data);
747 BufferAdd(&serdata->data, (unsigned char *)"\176", 1);
749 if (BufferLen(&serdata->data) == 2)
751 BufferPullTail(&serdata->data, 1);
753 else
757 else
761 if (BufferData(&serdata->data)[0] == 0x7e &&
762 memcmp(&BufferData(&serdata->data)[1], "AT", 2) == 0)
764 BufferPullHead(&serdata->data, 1);
766 if (BufferData(&serdata->data)[0] != 0x7e)
768 debug(9, "%s:%s(%d) - %i\n",
769 __FILE__, __FUNCTION__, __LINE__,
770 BufferLen(&serdata->data));
771 send_packet(serdata, BufferData(&serdata->data), BufferLen(&serdata->data));
772 BufferEmpty(&serdata->data);
778 } // namespace Barry