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
),
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
81 // a non-default socket has been opened, close it
84 catch( std::runtime_error
&re
) {
85 // do nothing... log it?
86 dout("Exception caught in ~Socket: " << re
.what());
89 // cleanup the interface
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
103 dout("Controller object destroyed in halfopen state, resetting device");
108 ///////////////////////////////////////////////////////////////////////////////
111 void Controller::SelectMode(ModeType mode
)
117 Protocol::Packet packet
;
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
;
129 strcpy(modeName
, "RIM Bypass");
133 strcpy(modeName
, "RIM Desktop");
137 strcpy(modeName
, "RIM_JavaLoader");
141 strcpy(modeName
, "RIM_UsbSerData");
145 throw std::logic_error("Controller: Invalid mode in SelectMode");
149 // send mode command before we open, as a default socket is socket 0
150 Data
command(&packet
, btohs(packet
.size
));
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
);
176 unsigned int Controller::GetCommand(CommandType ct
)
178 unsigned int cmd
= 0;
179 char *cmdName
= "Unknown";
184 cmdName
= "Database Access";
185 cmd
= m_commandTable
.GetCommand(cmdName
);
188 throw std::logic_error("Controller: unknown command type");
192 std::ostringstream oss
;
193 oss
<< "Controller: unable to get command code: " << cmdName
;
194 throw Error(oss
.str());
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
));
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
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
);
236 void Controller::LoadDBDB()
238 assert( m_mode
== Desktop
);
240 Data command
, response
;
241 DBPacket
packet(*this, command
, response
);
244 m_socket
.Packet(packet
);
246 while( packet
.Command() != SB_COMMAND_DB_DONE
) {
247 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
249 m_dbdb
.Parse(response
);
253 m_socket
.NextRecord(response
);
258 ///////////////////////////////////////////////////////////////////////////////
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
275 // FIXME - this needs a better error handler...
276 if( !m_dbdb
.GetDBNumber(name
, ID
) ) {
277 throw Error("Controller: database name not found: " + name
);
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.
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
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
) {
322 RetryPassword(password
);
329 /// Retry a failed password attempt from the first call to OpenMode().
330 /// Only call this function on Barry::BadPassword exceptions thrown
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");
352 m_socket
.Open(m_modeSocket
, password
);
361 // get database database
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
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
);
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
);
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());
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
,
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();
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
;
484 throw Error(oss
.str());
488 packet
.Parse(parser
);
490 // flush the command sequence
491 while( packet
.Command() != SB_COMMAND_DB_DONE
)
492 m_socket
.NextRecord(response
);
498 /// Overwrites a specific record in the device as identified by the
501 void Controller::SetRecord(unsigned int dbId
, unsigned int stateTableIndex
,
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());
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
);
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
);
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
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<>
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
622 packet
.Parse(parser
);
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
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
666 while( packet
.SetRecord(dbId
, builder
) ) {
667 dout("Database ID: " << dbId
);
669 m_socket
.Packet(packet
, first
? 60000 : -1);
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());
720 spack
->socket
= htobs(m_socket
.GetSocket());
721 spack
->size
= htobs(size
);
724 m_writeCache
.ReleaseBuffer(size
);
725 m_socket
.Send(m_writeCache
);
728 unsigned char buf[0x400];
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);
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);
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);