lib: fixed parsing of recurring VEVENTS: DAILY and interval support
[barry/progweb.git] / src / probe.cc
blob9b3eda89581c18eeebecb34c95e5a494ee1b2469
1 ///
2 /// \file probe.cc
3 /// USB Blackberry detection routines
4 ///
6 /*
7 Copyright (C) 2005-2012, 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 "common.h"
23 #include "probe.h"
24 #include "usbwrap.h"
25 #include "data.h"
26 #include "endian.h"
27 #include "error.h"
28 #include "debug.h"
29 #include "packet.h"
30 #include "socket.h"
31 #include "protocol.h"
32 #include "protostructs.h"
33 #include "record-internal.h"
34 #include "strnlen.h"
35 #include "configfile.h"
36 #include "platform.h"
37 #include <iomanip>
38 #include <sstream>
39 #include <errno.h>
40 #include <string.h>
41 #include "ios_state.h"
43 using namespace Usb;
45 namespace Barry {
47 unsigned char Intro_Sends[][32] = {
48 // packet #1
49 { 0x00, 0x00, 0x10, 0x00, 0x01, 0xff, 0x00, 0x00,
50 0xa8, 0x18, 0xda, 0x8d, 0x6c, 0x02, 0x00, 0x00 }
54 unsigned char Intro_Receives[][32] = {
55 // response to packet #1
56 { 0x00, 0x00, 0x10, 0x00, 0x02, 0xff, 0x00, 0x00,
57 0xa8, 0x18, 0xda, 0x8d, 0x6c, 0x02, 0x00, 0x00 }
60 namespace {
62 unsigned int GetSize(const unsigned char *packet)
64 const Protocol::Packet *pack = (const Protocol::Packet*) packet;
65 return btohs(pack->size);
68 bool Intro(int IntroIndex, const EndpointPair &ep, Device &dev, Data &response)
70 dev.BulkWrite(ep.write, Intro_Sends[IntroIndex],
71 GetSize(Intro_Sends[IntroIndex]));
72 try {
73 dev.BulkRead(ep.read, response, 500);
75 catch( Usb::Timeout &to ) {
76 ddout("BulkRead: " << to.what());
77 return false;
79 ddout("BulkRead (" << (unsigned int)ep.read << "):\n" << response);
80 return true;
83 } // anonymous namespace
86 bool Probe::CheckSize(const Data &data, unsigned int required)
88 const unsigned char *pd = data.GetData();
90 if( GetSize(pd) != (unsigned int) data.GetSize() ||
91 data.GetSize() < required ||
92 pd[4] != SB_COMMAND_FETCHED_ATTRIBUTE )
94 dout("Probe: Parse data failure: GetSize(pd): " << GetSize(pd)
95 << ", data.GetSize(): " << data.GetSize()
96 << ", pd[4]: " << (unsigned int) pd[4]);
97 return false;
100 return true;
103 bool Probe::ParsePIN(const Data &data, uint32_t &pin)
105 // validate response data
106 const unsigned char *pd = data.GetData();
108 if( !CheckSize(data, 0x14) )
109 return false;
111 // capture the PIN
112 memcpy(&pin, &pd[16], sizeof(pin));
113 pin = btohl(pin);
115 return true;
118 bool Probe::ParseDesc(const Data &data, std::string &desc)
120 if( !CheckSize(data, 29) )
121 return false;
123 // capture the description
124 const char *d = (const char*) &data.GetData()[28];
125 int maxlen = data.GetSize() - 28;
126 desc.assign(d, strnlen(d, maxlen));
128 return true;
131 Probe::Probe(const char *busname, const char *devname,
132 const Usb::EndpointPair *epp,
133 unsigned int log_exceptions,
134 bool auto_dump_log)
135 : m_log_exceptions(log_exceptions)
136 , m_fail_count(0)
137 , m_epp_override(epp)
139 if( m_epp_override ) {
140 m_epp = *epp;
143 // let the programmer pass in "" as well as 0
144 if( busname && !strlen(busname) )
145 busname = 0;
146 if( devname && !strlen(devname) )
147 devname = 0;
149 // Search for standard product ID first
150 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_BLACKBERRY, busname, devname);
152 // Search for Pearl devices second
154 // productID 6 devices (PRODUCT_RIM_PEARL) do not expose
155 // the USB class 255 interface we need, but only the
156 // Mass Storage one. Here we search for PRODUCT_RIM_PEARL_DUAL,
157 // (ID 4) which has both enabled.
158 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_PEARL_DUAL, busname, devname);
159 // And a special case, which behaves similar to the PEARL_DUAL,
160 // but with a unique Product ID.
161 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_PEARL_8120, busname, devname);
162 // And one more! The Pearl Flip
163 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_PEARL_FLIP, busname, devname);
165 // And one more time, for the Blackberry Storm
166 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_STORM, busname, devname);
168 // now dump all logged error messages
169 if( auto_dump_log && m_fail_msgs.size() ) {
170 eout("Probe logged " << m_fail_msgs.size() << " exception messages:");
171 for( std::vector<std::string>::const_iterator b = m_fail_msgs.begin();
172 b != m_fail_msgs.end();
173 ++b )
175 eout(*b);
180 void Probe::ProbeMatching(int vendor, int product,
181 const char *busname, const char *devname)
183 Usb::DeviceID devid;
185 Match match(m_devices, vendor, product, busname, devname);
186 while( match.next_device(devid) ) try {
187 ProbeDevice(devid);
189 catch( Usb::Error &e ) {
190 using namespace std;
192 dout("Usb::Error exception caught: " << e.what());
193 if( ((m_log_exceptions & LOG_BUSY) && e.system_errcode() == -EBUSY) ||
194 ((m_log_exceptions & LOG_ACCESS) && e.system_errcode() == -EACCES) ||
195 ((m_log_exceptions & LOG_PERM) && e.system_errcode() == -EPERM) )
197 m_fail_count++;
198 string msg = "Device " + devid.GetUsbName() + ": ";
199 if( e.system_errcode() == -EBUSY )
200 msg += "(EBUSY) ";
201 else if( e.system_errcode() == -EACCES )
202 msg += "(EACCES) ";
203 else if( e.system_errcode() == -EPERM )
204 msg += "(EPERM) ";
205 msg += e.what();
207 m_fail_msgs.push_back(msg);
209 else {
210 throw;
215 void Probe::ProbeDevice(Usb::DeviceID& devid)
217 // skip if we can't properly discover device config
218 DeviceDescriptor desc(devid);
219 ConfigDescriptor* config = desc[BLACKBERRY_CONFIGURATION];
220 if( !config ) {
221 dout("Probe: No device descriptor for BlackBerry config (config id: "
222 << BLACKBERRY_CONFIGURATION << ")");
223 return; // not found
226 // search for interface class
227 ConfigDescriptor::base_type::iterator idi = config->begin();
228 for( ; idi != config->end(); idi++ ) {
229 if( idi->second->GetClass() == BLACKBERRY_DB_CLASS )
230 break;
232 if( idi == config->end() ) {
233 dout("Probe: Interface with BLACKBERRY_DB_CLASS ("
234 << BLACKBERRY_DB_CLASS << ") not found.");
235 return; // not found
238 unsigned char InterfaceNumber = idi->second->GetNumber();
239 unsigned char InterfaceAltSetting = idi->second->GetAltSetting();
240 dout("Probe: using InterfaceNumber: " << (unsigned int) InterfaceNumber <<
241 " AltSetting: " << (unsigned int) InterfaceAltSetting);
243 // check endpoint validity
244 EndpointPairings ep(*(*config)[InterfaceNumber]);
245 if( !ep.IsValid() || ep.size() == 0 ) {
246 dout("Probe: endpoint invalid. ep.IsValid() == "
247 << (ep.IsValid() ? "true" : "false")
248 << ", ep.size() == "
249 << ep.size());
250 return;
253 ProbeResult result;
254 result.m_dev = devid;
255 result.m_interface = InterfaceNumber;
256 result.m_altsetting = InterfaceAltSetting;
257 result.m_zeroSocketSequence = 0;
259 // open device
260 Device dev(devid);
261 // dev.Reset();
262 // sleep(5);
264 // make sure we're talking to the right config
265 unsigned char cfg;
266 if( !dev.GetConfiguration(cfg) )
267 throw Usb::Error(dev.GetLastError(),
268 "Probe: GetConfiguration failed");
269 if( cfg != BLACKBERRY_CONFIGURATION || MUST_SET_CONFIGURATION ) {
270 if( !dev.SetConfiguration(BLACKBERRY_CONFIGURATION) )
271 throw Usb::Error(dev.GetLastError(),
272 "Probe: SetConfiguration failed");
275 // open interface
276 Interface iface(dev, InterfaceNumber);
278 // Try the initial probing of endpoints
279 ProbeDeviceEndpoints(dev, ep, result);
281 if( !result.m_ep.IsComplete() ) {
282 // Probing of end-points failed, so try reprobing
283 // after calling usb_set_altinterface().
285 // Calling usb_set_altinterface() should be harmless
286 // and can help the host and device to synchronize the
287 // USB state, especially on FreeBSD and Mac OS X.
288 // However it can cause usb-storage URBs to be lost
289 // on some devices, so is only used if necessary.
290 dout("Probe: probing endpoints failed, retrying after setting alternate interface");
292 iface.SetAltInterface(InterfaceAltSetting);
293 result.m_needSetAltInterface = true;
294 ProbeDeviceEndpoints(dev, ep, result);
297 // add to list
298 if( result.m_ep.IsComplete() ) {
299 // before adding to list, try to load the device's
300 // friendly name from the configfile... but don't
301 // fail if we can't do it
302 try {
303 ConfigFile cfg(result.m_pin);
304 result.m_cfgDeviceName = cfg.GetDeviceName();
306 catch( Barry::ConfigFileError & ) {
307 // ignore...
310 m_results.push_back(result);
311 ddout("Using ReadEndpoint: " << (unsigned int)result.m_ep.read);
312 ddout(" WriteEndpoint: " << (unsigned int)result.m_ep.write);
314 else {
315 ddout("Unable to discover endpoint pair for one device.");
319 void Probe::ProbeDeviceEndpoints(Device &dev, EndpointPairings &ed, ProbeResult &result)
321 if( m_epp_override ) {
322 // user has given us endpoints to try... so try them
323 uint32_t pin;
324 uint8_t zeroSocketSequence;
325 std::string desc;
326 bool needClearHalt;
327 if( ProbePair(dev, m_epp, pin, desc, zeroSocketSequence, needClearHalt) ) {
328 // looks good, finish filling out the result
329 result.m_ep = m_epp;
330 result.m_pin = pin;
331 result.m_description = desc;
332 result.m_zeroSocketSequence = zeroSocketSequence;
333 result.m_needClearHalt = needClearHalt;
336 else {
337 // find the first bulk read/write endpoint pair that answers
338 // to our probe commands
339 // Start with second pair, since evidence indicates the later pairs
340 // are the ones we need.
341 size_t i;
342 for(i = ed.size() > 1 ? 1 : 0;
343 i < ed.size();
344 i++ )
346 const EndpointPair &ep = ed[i];
347 if( ep.type == Usb::EndpointDescriptor::BulkType ) {
349 uint32_t pin;
350 uint8_t zeroSocketSequence;
351 std::string desc;
352 bool needClearHalt;
353 if( ProbePair(dev, ep, pin, desc, zeroSocketSequence, needClearHalt) ) {
354 result.m_ep = ep;
355 result.m_pin = pin;
356 result.m_description = desc;
357 result.m_zeroSocketSequence = zeroSocketSequence;
358 result.m_needClearHalt = needClearHalt;
359 break;
362 else {
363 dout("Probe: Skipping non-bulk endpoint pair (offset: "
364 << i-1 << ") ");
368 // check for ip modem endpoints
369 i++;
370 if( i < ed.size() ) {
371 const EndpointPair &ep = ed[i];
372 if( ProbeModem(dev, ep) ) {
373 result.m_epModem = ep;
379 bool Probe::ProbePair(Usb::Device &dev,
380 const Usb::EndpointPair &ep,
381 uint32_t &pin,
382 std::string &desc,
383 uint8_t &zeroSocketSequence,
384 bool &needClearHalt)
386 // Initially assume that clear halt isn't needed as it causes some
387 // devices to drop packets. The suspicion is that the toggle bits
388 // get out of sync, but this hasn't been confirmed with hardware
389 // tracing.
391 // It is possible to always use clear halt, as long as SET
392 // INTERFACE has been sent before, via usb_set_altinterface().
393 // However this has the side affect that any outstanding URBs
394 // on other interfaces (i.e. usb-storage) timeout and lose
395 // their data. This is not a good thing as it can corrupt the
396 // file system exposed over usb-storage. This also has the
397 // side-affect that usb-storage issues a port reset after the
398 // 30 second timeout, which kills any current Barry
399 // connection.
401 // To further complicate matters some devices, such as the
402 // 8830, always need clear halt before they will respond to
403 // probes.
405 // So to work with all these device quirks the probe is first
406 // attempted without calling clear halt. If that probe fails
407 // then a clear halt is issued followed by a retry on the
408 // probing.
409 needClearHalt = false;
411 Data data;
412 dev.BulkDrain(ep.read);
413 if( !Intro(0, ep, dev, data) ) {
414 // Try clearing halt and then reprobing
415 dout("Probe: Intro(0) failed, retrying after clearing halt");
416 dev.ClearHalt(ep.read);
417 dev.ClearHalt(ep.write);
418 needClearHalt = true;
419 // Retry
420 dev.BulkDrain(ep.read);
421 if( !Intro(0, ep, dev, data) ) {
422 // Still no response so fail the probe
423 dout("Probe: Intro(0) still failed after clearing halt");
424 return false;
428 SocketZero socket(dev, ep.write, ep.read);
430 Data send, receive;
431 ZeroPacket packet(send, receive);
433 // unknown attribute: 0x14 / 0x01
434 packet.GetAttribute(SB_OBJECT_INITIAL_UNKNOWN,
435 SB_ATTR_INITIAL_UNKNOWN);
436 socket.Send(packet);
438 // fetch PIN
439 packet.GetAttribute(SB_OBJECT_PROFILE, SB_ATTR_PROFILE_PIN);
440 socket.Send(packet);
441 if( packet.ObjectID() != SB_OBJECT_PROFILE ||
442 packet.AttributeID() != SB_ATTR_PROFILE_PIN ||
443 !ParsePIN(receive, pin) )
445 dout("Probe: unable to fetch PIN");
446 return false;
449 // fetch Description
450 packet.GetAttribute(SB_OBJECT_PROFILE, SB_ATTR_PROFILE_DESC);
451 socket.Send(packet);
452 // response ObjectID does not match request... :-/
453 if( // packet.ObjectID() != SB_OBJECT_PROFILE ||
454 packet.AttributeID() != SB_ATTR_PROFILE_DESC ||
455 !ParseDesc(receive, desc) )
457 dout("Probe: unable to fetch description");
460 // more unknowns:
461 for( uint16_t attr = 5; attr < 9; attr++ ) {
462 packet.GetAttribute(SB_OBJECT_SOCKET_UNKNOWN, attr);
463 socket.Send(packet);
464 // FIXME parse these responses, if they turn
465 // out to be important
468 // all info obtained!
469 zeroSocketSequence = socket.GetZeroSocketSequence();
470 return true;
473 bool Probe::ProbeModem(Usb::Device &dev, const Usb::EndpointPair &ep)
476 // This check is not needed for all devices. Some devices,
477 // like the 8700 have both the RIM_UsbSerData mode and IpModem mode.
479 // If this function is called, then we have extra endpoints,
480 // so might as well try them.
482 // FIXME - someday, we might wish to confirm that the endpoints
483 // work as a modem, and return true/false based on that test.
485 return true;
488 // Thanks to Rick Scott (XmBlackBerry:bb_usb.c) for reverse engineering this
489 // int num_read;
490 // char data[255];
491 // int local_errno;
493 // num_read = usb_control_msg(dev.GetHandle(),
494 // /* bmRequestType */ USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
495 // /* bRequest */ 0xa5,
496 // /* wValue */ 0,
497 // /* wIndex */ 1,
498 // /* data */ data,
499 // /* wLength */ sizeof(data),
500 // /* timeout */ 2000);
501 // local_errno = errno;
502 // if( num_read > 1 ) {
503 // if( data[0] == 0x02 ) {
504 // return true;
505 // }
506 // }
507 // return false;
510 int Probe::FindActive(Barry::Pin pin) const
512 return FindActive(m_results, pin);
515 int Probe::FindActive(const Barry::Probe::Results &results, Barry::Pin pin)
517 int i = Find(results, pin);
519 if( i == -1 && pin == 0 ) {
520 // can we default to a single device?
521 if( results.size() == 1 )
522 return 0; // yes!
525 return i;
528 int Probe::Find(const Results &results, Barry::Pin pin)
530 Barry::Probe::Results::const_iterator ci = results.begin();
531 for( int i = 0; ci != results.end(); i++, ++ci ) {
532 if( ci->m_pin == pin )
533 return i;
535 // PIN not found
536 return -1;
539 void ProbeResult::DumpAll(std::ostream &os) const
541 ios_format_state state(os);
543 os << *this
544 << ", Interface: 0x" << std::hex << (unsigned int) m_interface
545 << ", Endpoints: (read: 0x" << std::hex << (unsigned int) m_ep.read
546 << ", write: 0x" << std::hex << (unsigned int) m_ep.write
547 << ", type: 0x" << std::hex << (unsigned int) m_ep.type
548 << ", ZeroSocketSequence: 0x" << std::hex << (unsigned int) m_zeroSocketSequence;
551 std::string ProbeResult::GetDisplayName() const
553 std::ostringstream oss;
554 oss << m_pin.Str();
555 if( m_cfgDeviceName.size() )
556 oss << " (" << m_cfgDeviceName << ")";
557 return oss.str();
560 std::ostream& operator<< (std::ostream &os, const ProbeResult &pr)
562 ios_format_state state(os);
564 os << "Device ID: " << pr.m_dev.m_impl.get()
565 << ". PIN: " << pr.m_pin.Str()
566 << ", Description: " << pr.m_description;
567 if( pr.m_cfgDeviceName.size() )
568 os << ", Name: " << pr.m_cfgDeviceName;
569 return os;
572 } // namespace Barry