- added ClearHalt() calls to probe and controller classes, just
[barry.git] / src / controller.cc
blob989b406f3160a07be7ae67463e41a35f6ffba726
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 unsigned char cfg;
61 if( !m_dev.GetConfiguration(cfg) )
62 throw Usb::Error(m_dev.GetLastError(),
63 "Controller: GetConfiguration failed");
65 if( cfg != BLACKBERRY_CONFIGURATION ) {
66 if( !m_dev.SetConfiguration(BLACKBERRY_CONFIGURATION) )
67 throw Usb::Error(m_dev.GetLastError(),
68 "Controller: SetConfiguration failed");
71 m_iface = new Usb::Interface(m_dev, device.m_interface);
73 m_dev.ClearHalt(device.m_ep.read);
74 m_dev.ClearHalt(device.m_ep.write);
77 Controller::~Controller()
79 // trap exceptions in the destructor
80 try {
81 // a non-default socket has been opened, close it
82 m_socket.Close();
84 catch( std::runtime_error &re ) {
85 // do nothing... log it?
86 dout("Exception caught in ~Socket: " << re.what());
89 // cleanup the interface
90 delete m_iface;
92 // this happens when for some reason the Desktop mode
93 // is not fully opened, but the device has already recommended
94 // a socket to open... in this case, reset the device
95 // in the hopes that on next open, it will be in a
96 // recognizable state.
98 // FIXME - this should not be necessary, and someday we
99 // we should figure out how to handle the "already open"
100 // response we get for the Desktop
102 if( m_halfOpen ) {
103 dout("Controller object destroyed in halfopen state, resetting device");
104 m_dev.Reset();
108 ///////////////////////////////////////////////////////////////////////////////
109 // protected members
111 void Controller::SelectMode(ModeType mode)
113 // start fresh
114 m_modeSocket = 0;
116 // select mode
117 Protocol::Packet packet;
118 packet.socket = 0;
119 packet.size = htobs(SB_MODE_PACKET_COMMAND_SIZE);
120 packet.command = SB_COMMAND_SELECT_MODE;
121 packet.u.socket.socket = htobs(SB_MODE_REQUEST_SOCKET);
122 packet.u.socket.sequence = 0; // updated by Socket::Send()
123 memset(packet.u.socket.u.mode.name, 0, sizeof(packet.u.socket.u.mode.name));
125 char *modeName = (char *) packet.u.socket.u.mode.name;
126 switch( mode )
128 case Bypass:
129 strcpy(modeName, "RIM Bypass");
130 break;
132 case Desktop:
133 strcpy(modeName, "RIM Desktop");
134 break;
136 case JavaLoader:
137 strcpy(modeName, "RIM_JavaLoader");
138 break;
140 case UsbSerData:
141 strcpy(modeName, "RIM_UsbSerData");
142 break;
144 default:
145 throw std::logic_error("Controller: Invalid mode in SelectMode");
146 break;
149 // send mode command before we open, as a default socket is socket 0
150 Data command(&packet, btohs(packet.size));
151 Data response;
153 try {
154 m_socket.Send(command, response);
156 // get the data socket number
157 // indicates the socket number that
158 // should be used below in the Open() call
159 Protocol::CheckSize(response, SB_MODE_PACKET_RESPONSE_SIZE);
160 MAKE_PACKET(modepack, response);
161 if( modepack->command != SB_COMMAND_MODE_SELECTED ) {
162 eeout(command, response);
163 throw Error("Controller: mode not selected");
166 // save the socket that the device is expecting us to use
167 m_modeSocket = btohs(modepack->u.socket.socket);
169 catch( Usb::Error & ) {
170 eout("Controller: error setting desktop mode");
171 eeout(command, response);
172 throw;
176 unsigned int Controller::GetCommand(CommandType ct)
178 unsigned int cmd = 0;
179 char *cmdName = "Unknown";
181 switch( ct )
183 case DatabaseAccess:
184 cmdName = "Database Access";
185 cmd = m_commandTable.GetCommand(cmdName);
186 break;
187 default:
188 throw std::logic_error("Controller: unknown command type");
191 if( cmd == 0 ) {
192 std::ostringstream oss;
193 oss << "Controller: unable to get command code: " << cmdName;
194 throw Error(oss.str());
197 return cmd;
200 void Controller::LoadCommandTable()
202 assert( m_mode == Desktop );
204 char rawCommand[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
205 *((uint16_t*) rawCommand) = htobs(m_socket.GetSocket());
207 Data command(rawCommand, sizeof(rawCommand));
208 Data response;
210 try {
211 m_socket.Packet(command, response);
213 MAKE_PACKET(rpack, response);
214 while( rpack->command != SB_COMMAND_DB_DONE ) {
215 m_socket.NextRecord(response);
217 rpack = (const Protocol::Packet *) response.GetData();
218 if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
219 // second packet is generally large, and contains
220 // the command table
221 m_commandTable.Clear();
222 m_commandTable.Parse(response, 6);
226 ddout(m_commandTable);
229 catch( Usb::Error & ) {
230 eout("Controller: error getting command table");
231 eeout(command, response);
232 throw;
236 void Controller::LoadDBDB()
238 assert( m_mode == Desktop );
240 Data command, response;
241 DBPacket packet(*this, command, response);
242 packet.GetDBDB();
244 m_socket.Packet(packet);
246 while( packet.Command() != SB_COMMAND_DB_DONE ) {
247 if( packet.Command() == SB_COMMAND_DB_DATA ) {
248 m_dbdb.Clear();
249 m_dbdb.Parse(response);
252 // advance!
253 m_socket.NextRecord(response);
258 ///////////////////////////////////////////////////////////////////////////////
259 // public API
262 // GetDBID
264 /// Get numeric database ID by name.
266 /// \param[in] name Name of database, which matches one of the
267 /// names listed in GetDBDB()
269 /// \exception Barry::Error
270 /// Thrown if name not found.
272 unsigned int Controller::GetDBID(const std::string &name) const
274 unsigned int ID = 0;
275 // FIXME - this needs a better error handler...
276 if( !m_dbdb.GetDBNumber(name, ID) ) {
277 throw Error("Controller: database name not found: " + name);
279 return ID;
283 // OpenMode
285 /// Select device mode. This is required before using any other mode-based
286 /// operations, such as GetDBDB() and LoadDatabase(). Currently only
287 /// Desktop mode is supported, but the following modes are available.
288 /// (See ModeType)
290 /// - Controller::Bypass
291 /// - Controller::Desktop
292 /// - Controller::JavaLoader
294 /// This function opens a socket to the device when in Desktop mode.
295 /// If the device requires it, specify the password with a conat char*
296 /// string in password. The password will not be stored in memory
297 /// inside this class, only a hash will be generated from it. After
298 /// using the hash, the hash memory will be set to 0. The application
299 /// is responsible for safely handling the raw password data.
301 /// You can retry the password by catching Barry::BadPassword and
302 /// calling RetryPassword() with the new password.
304 /// \exception Barry::Error
305 /// Thrown on protocol error.
307 /// \exception std::logic_error()
308 /// Thrown if unsupported mode is requested, or if socket
309 /// already open.
311 /// \exception Barry::BadPassword
312 /// Thrown when password is invalid or if not enough retries
313 /// left in the device.
315 void Controller::OpenMode(ModeType mode, const char *password)
317 if( m_mode != mode ) {
318 m_socket.Close();
319 SelectMode(mode);
320 m_mode = mode;
322 RetryPassword(password);
327 // RetryPassword
329 /// Retry a failed password attempt from the first call to OpenMode().
330 /// Only call this function on Barry::BadPassword exceptions thrown
331 /// from OpenMode().
333 /// \exception Barry::Error
334 /// Thrown on protocol error.
336 /// \exception std::logic_error()
337 /// Thrown if in unsupported mode, or if socket already open.
339 /// \exception Barry::BadPassword
340 /// Thrown when password is invalid or if not enough retries
341 /// left in the device.
343 void Controller::RetryPassword(const char *password)
345 if( m_mode != Desktop )
346 throw std::logic_error("Wrong mode in RetryPassword");
348 if( m_socket.GetSocket() != 0 )
349 throw std::logic_error("Socket alreay open in RetryPassword");
351 m_halfOpen = true;
352 m_socket.Open(m_modeSocket, password);
353 m_halfOpen = false;
355 switch( m_mode )
357 case Desktop:
358 // get command table
359 LoadCommandTable();
361 // get database database
362 LoadDBDB();
363 break;
365 case UsbSerData:
366 // nothing to do
367 break;
369 default:
370 throw std::logic_error("Mode not implemented");
375 // GetRecordStateTable
377 /// Retrieve the record state table from the handheld device, using the given
378 /// database ID. Results will be stored in result, which will be cleared
379 /// before adding.
381 void Controller::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
383 if( m_mode != Desktop )
384 throw std::logic_error("Wrong mode in GetRecordStateTable");
386 dout("Database ID: " << dbId);
388 // start fresh
389 result.Clear();
391 Data command, response;
392 DBPacket packet(*this, command, response);
393 packet.GetRecordStateTable(dbId);
395 m_socket.Packet(packet);
396 result.Parse(response);
398 // flush the command sequence
399 while( packet.Command() != SB_COMMAND_DB_DONE )
400 m_socket.NextRecord(response);
404 // AddRecord
406 /// Adds a record to the specified database. RecordId is
407 /// retrieved from build, and duplicate IDs are allowed by the device
408 /// (i.e. you can have two records with the same ID)
409 /// but *not* recommended!
411 void Controller::AddRecord(unsigned int dbId, Builder &build)
413 if( m_mode != Desktop )
414 throw std::logic_error("Wrong mode in GetRecord");
416 dout("Database ID: " << dbId);
418 Data command, response;
419 DBPacket packet(*this, command, response);
421 if( packet.SetRecord(dbId, build) ) {
423 std::ostringstream oss;
425 m_socket.Packet(packet);
427 // successful packet transfer, so check the network return code
428 if( packet.Command() != SB_COMMAND_DB_DONE ) {
429 oss << "Controller: device responded with unexpected packet command code: "
430 << "0x" << std::hex << packet.Command();
431 throw Error(oss.str());
434 if( packet.ReturnCode() != 0 ) {
435 oss << "Controller: device responded with error code (command: "
436 << packet.Command() << ", code: "
437 << packet.ReturnCode() << ")";
438 throw Error(oss.str());
444 // GetRecord
446 /// Retrieves a specific record from the specified database.
447 /// The stateTableIndex comes from the GetRecordStateTable()
448 /// function. GetRecord() does not clear the dirty flag.
450 void Controller::GetRecord(unsigned int dbId,
451 unsigned int stateTableIndex,
452 Parser &parser)
454 if( m_mode != Desktop )
455 throw std::logic_error("Wrong mode in GetRecord");
457 dout("Database ID: " << dbId);
459 Data command, response;
460 DBPacket packet(*this, command, response);
461 packet.GetRecordByIndex(dbId, stateTableIndex);
463 m_socket.Packet(packet);
465 // perform copious packet checks
466 if( response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
467 eeout(command, response);
469 std::ostringstream oss;
470 oss << "Controller: invalid response packet size of "
471 << std::dec << response.GetSize();
472 eout(oss.str());
473 throw Error(oss.str());
475 if( packet.Command() != SB_COMMAND_DB_DATA ) {
476 eeout(command, response);
478 std::ostringstream oss;
479 oss << "Controller: unexpected command of 0x"
480 << std::setbase(16) << packet.Command()
481 << " instead of expected 0x"
482 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
483 eout(oss.str());
484 throw Error(oss.str());
487 // grab that data
488 packet.Parse(parser);
490 // flush the command sequence
491 while( packet.Command() != SB_COMMAND_DB_DONE )
492 m_socket.NextRecord(response);
496 // SetRecord
498 /// Overwrites a specific record in the device as identified by the
499 /// stateTableIndex.
501 void Controller::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
502 Builder &build)
504 if( m_mode != Desktop )
505 throw std::logic_error("Wrong mode in SetRecord");
507 dout("Database ID: " << dbId << " Index: " << stateTableIndex);
509 Data command, response;
510 DBPacket packet(*this, command, response);
512 // loop until builder object has no more data
513 if( !packet.SetRecordByIndex(dbId, stateTableIndex, build) ) {
514 throw std::logic_error("Controller: no data available in SetRecord");
517 m_socket.Packet(packet);
519 std::ostringstream oss;
521 // successful packet transfer, so check the network return code
522 if( packet.Command() != SB_COMMAND_DB_DONE ) {
523 oss << "Controller: device responded with unexpected packet command code: "
524 << "0x" << std::hex << packet.Command();
525 throw Error(oss.str());
528 if( packet.ReturnCode() != 0 ) {
529 oss << "Controller: device responded with error code (command: "
530 << packet.Command() << ", code: "
531 << packet.ReturnCode() << ")";
532 throw Error(oss.str());
537 // ClearDirty
539 /// Clears the dirty flag on the specified record in the specified database.
541 void Controller::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
543 if( m_mode != Desktop )
544 throw std::logic_error("Wrong mode in ClearDirty");
546 dout("Database ID: " << dbId);
548 Data command, response;
549 DBPacket packet(*this, command, response);
550 packet.SetRecordFlags(dbId, stateTableIndex, 0);
552 m_socket.Packet(packet);
554 // flush the command sequence
555 while( packet.Command() != SB_COMMAND_DB_DONE )
556 m_socket.NextRecord(response);
560 // DeleteRecord
562 /// Deletes the specified record in the specified database.
564 void Controller::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
566 if( m_mode != Desktop )
567 throw std::logic_error("Wrong mode in DeleteRecord");
569 dout("Database ID: " << dbId);
571 Data command, response;
572 DBPacket packet(*this, command, response);
573 packet.DeleteRecordByIndex(dbId, stateTableIndex);
575 m_socket.Packet(packet);
577 // flush the command sequence
578 while( packet.Command() != SB_COMMAND_DB_DONE )
579 m_socket.NextRecord(response);
583 // LoadDatabase
585 /// Retrieve a database from the handheld device, using the given parser
586 /// to parse the resulting data, and optionally store it.
588 /// See the RecordParser<> template to create a parser object. The
589 /// RecordParser<> template allows custom storage based on the type of
590 /// database record retrieved. The database ID and the parser Record
591 /// type must match.
593 /// \param[in] dbId Database Database ID - use GetDBID()
594 /// \param[out] parser Parser object which parses the resulting
595 /// protocol data, and optionally stores it in
596 /// a custom fashion. See the RecordParser<>
597 /// template.
599 /// \exception Barry::Error
600 /// Thrown on protocol error.
602 /// \exception std::logic_error
603 /// Thrown if not in Desktop mode.
605 void Controller::LoadDatabase(unsigned int dbId, Parser &parser)
607 if( m_mode != Desktop )
608 throw std::logic_error("Wrong mode in LoadDatabase");
610 dout("Database ID: " << dbId);
612 Data command, response;
613 DBPacket packet(*this, command, response);
614 packet.GetRecords(dbId);
616 m_socket.Packet(packet);
618 while( packet.Command() != SB_COMMAND_DB_DONE ) {
619 if( packet.Command() == SB_COMMAND_DB_DATA ) {
620 // this size is the old header size, since using
621 // old command above
622 packet.Parse(parser);
625 // advance!
626 m_socket.NextRecord(response);
630 void Controller::SaveDatabase(unsigned int dbId, Builder &builder)
632 if( m_mode != Desktop )
633 throw std::logic_error("Wrong mode in SaveDatabase");
635 dout("Database ID: " << dbId);
637 // Protocol note: so far in testing, this CLEAR_DATABASE operation is
638 // required, since every record sent via SET_RECORD
639 // is treated like a hypothetical "ADD_RECORD" (perhaps
640 // SET_RECORD should be renamed)... I don't know if
641 // there is a real SET_RECORD... all I know is from
642 // the Windows USB captures, which uses this same
643 // technique.
644 Data command, response;
645 DBPacket packet(*this, command, response);
646 packet.ClearDatabase(dbId);
648 // wait up to a minute here for old, slower devices with lots of data
649 m_socket.Packet(packet, 60000);
650 if( packet.ReturnCode() != 0 ) {
651 std::ostringstream oss;
652 oss << "Controller: could not clear database: (command: "
653 << "0x" << std::hex << packet.Command() << ", code: "
654 << "0x" << std::hex << packet.ReturnCode() << ")";
655 throw Error(oss.str());
658 // check response to clear command was successful
659 if( packet.Command() != SB_COMMAND_DB_DONE ) {
660 eeout(command, response);
661 throw Error("Controller: error clearing database, bad response");
664 // loop until builder object has no more data
665 bool first = true;
666 while( packet.SetRecord(dbId, builder) ) {
667 dout("Database ID: " << dbId);
669 m_socket.Packet(packet, first ? 60000 : -1);
670 first = false;
672 std::ostringstream oss;
673 // successful packet transfer, so check the network return code
674 if( packet.Command() != SB_COMMAND_DB_DONE ) {
675 oss << "Controller: device responded with unexpected packet command code: "
676 << "0x" << std::hex << packet.Command();
677 throw Error(oss.str());
680 if( packet.ReturnCode() != 0 ) {
681 oss << "Controller: device responded with error code (command: "
682 << packet.Command() << ", code: "
683 << packet.ReturnCode() << ")";
684 throw Error(oss.str());
691 //////////////////////////////////////////////////////////////////////////////
692 // UsbSerData mode - modem specific
694 // can be called from separate thread
695 void Controller::SerialRead(Data &data, int timeout)
697 if( m_mode != UsbSerData )
698 throw std::logic_error("Wrong mode in SerialRead");
700 m_socket.Receive(data, timeout);
703 // based on Rick Scott's XmBlackBerry's serdata.c
704 void Controller::SerialWrite(const Data &data)
706 if( m_mode != UsbSerData )
707 throw std::logic_error("Wrong mode in SerialWrite");
709 if( data.GetSize() <= 0 )
710 return; // nothing to do
712 int size = data.GetSize() + 4;
713 unsigned char *buf = m_writeCache.GetBuffer(size);
714 MAKE_PACKETPTR_BUF(spack, buf);
716 // copy data over to cache packet
717 memcpy(&buf[4], data.GetData(), data.GetSize());
719 // setup header
720 spack->socket = htobs(m_socket.GetSocket());
721 spack->size = htobs(size);
723 // release and send
724 m_writeCache.ReleaseBuffer(size);
725 m_socket.Send(m_writeCache);
728 unsigned char buf[0x400];
729 int num_read;
730 int i;
733 // This is pretty ugly, but I have to put the HDLC flags into
734 // the packets. RIM seems to need flags around every frame, and
735 // a flag _cannot_ be an end and a start flag.
737 for (i = 0; i < num_read; i++) {
738 BufferAdd(&serdata->data, &buf[i], 1);
739 if (BufferData(&serdata->data)[0] == 0x7e && buf[i] == 0x7e) {
740 if (BufferLen(&serdata->data) > 1 &&
741 BufferData(&serdata->data)[0] == 0x7e &&
742 BufferData(&serdata->data)[1] == 0x7e)
744 BufferPullHead(&serdata->data, 1);
746 else
749 if (BufferLen(&serdata->data) > 2)
751 if ((BufferLen(&serdata->data) + 4) % 16 == 0)
753 BufferAdd(&serdata->data, (unsigned char *)"\0", 1);
755 send_packet(serdata, BufferData(&serdata->data), BufferLen(&serdata->data));
756 BufferEmpty(&serdata->data);
757 BufferAdd(&serdata->data, (unsigned char *)"\176", 1);
759 if (BufferLen(&serdata->data) == 2)
761 BufferPullTail(&serdata->data, 1);
763 else
767 else
771 if (BufferData(&serdata->data)[0] == 0x7e &&
772 memcmp(&BufferData(&serdata->data)[1], "AT", 2) == 0)
774 BufferPullHead(&serdata->data, 1);
776 if (BufferData(&serdata->data)[0] != 0x7e)
778 debug(9, "%s:%s(%d) - %i\n",
779 __FILE__, __FUNCTION__, __LINE__,
780 BufferLen(&serdata->data));
781 send_packet(serdata, BufferData(&serdata->data), BufferLen(&serdata->data));
782 BufferEmpty(&serdata->data);
788 } // namespace Barry