2 /// \file controller.cc
3 /// High level Barry API class
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"
25 #include "protostructs.h"
33 #define __DEBUG_MODE__
43 // Controller constructor
45 /// Constructor for the Controller class. Requires a valid ProbeResult
46 /// object to find the USB device to talk to.
48 /// \param[in] device One of the ProbeResult objects from the
51 Controller::Controller(const ProbeResult
&device
)
52 : m_dev(device
.m_dev
),
55 m_socket(m_dev
, device
.m_ep
.write
, device
.m_ep
.read
, device
.m_zeroSocketSequence
),
60 if( !m_dev
.SetConfiguration(BLACKBERRY_CONFIGURATION
) )
61 throw 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
71 // a non-default socket has been opened, close it
74 catch( std::runtime_error
&re
) {
75 // do nothing... log it?
76 dout("Exception caught in ~Socket: " << re
.what());
79 // cleanup the interface
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
93 dout("Controller object destroyed in halfopen state, resetting device");
98 ///////////////////////////////////////////////////////////////////////////////
101 void Controller::SelectMode(ModeType mode
)
107 Protocol::Packet packet
;
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
;
119 strcpy(modeName
, "RIM Bypass");
123 strcpy(modeName
, "RIM Desktop");
127 strcpy(modeName
, "RIM_JavaLoader");
131 strcpy(modeName
, "RIM_UsbSerData");
135 throw std::logic_error("Controller: Invalid mode in SelectMode");
139 // send mode command before we open, as a default socket is socket 0
140 Data
command(&packet
, btohs(packet
.size
));
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
);
166 unsigned int Controller::GetCommand(CommandType ct
)
168 unsigned int cmd
= 0;
169 char *cmdName
= "Unknown";
174 cmdName
= "Database Access";
175 cmd
= m_commandTable
.GetCommand(cmdName
);
178 throw std::logic_error("Controller: unknown command type");
182 std::ostringstream oss
;
183 oss
<< "Controller: unable to get command code: " << cmdName
;
184 throw Error(oss
.str());
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
));
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
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
);
226 void Controller::LoadDBDB()
228 assert( m_mode
== Desktop
);
230 Data command
, response
;
231 DBPacket
packet(*this, command
, response
);
234 m_socket
.Packet(packet
);
236 while( packet
.Command() != SB_COMMAND_DB_DONE
) {
237 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
239 m_dbdb
.Parse(response
);
243 m_socket
.NextRecord(response
);
248 ///////////////////////////////////////////////////////////////////////////////
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
265 // FIXME - this needs a better error handler...
266 if( !m_dbdb
.GetDBNumber(name
, ID
) ) {
267 throw Error("Controller: database name not found: " + name
);
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.
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
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
) {
312 RetryPassword(password
);
319 /// Retry a failed password attempt from the first call to OpenMode().
320 /// Only call this function on Barry::BadPassword exceptions thrown
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");
342 m_socket
.Open(m_modeSocket
, password
);
351 // get database database
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
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
);
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
);
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());
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
,
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();
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
;
474 throw Error(oss
.str());
478 packet
.Parse(parser
);
480 // flush the command sequence
481 while( packet
.Command() != SB_COMMAND_DB_DONE
)
482 m_socket
.NextRecord(response
);
488 /// Overwrites a specific record in the device as identified by the
491 void Controller::SetRecord(unsigned int dbId
, unsigned int stateTableIndex
,
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());
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
);
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
);
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
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<>
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
612 packet
.Parse(parser
);
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
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
656 while( packet
.SetRecord(dbId
, builder
) ) {
657 dout("Database ID: " << dbId
);
659 m_socket
.Packet(packet
, first
? 60000 : -1);
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());
710 spack
->socket
= htobs(m_socket
.GetSocket());
711 spack
->size
= htobs(size
);
714 m_writeCache
.ReleaseBuffer(size
);
715 m_socket
.Send(m_writeCache
);
718 unsigned char buf[0x400];
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);
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);
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);