ppp: minor cleanups to match other chatscripts order and documentation
[barry.git] / src / probe.cc
blobab0843c1b41744c0d827d8e797e63b3412348aac
1 ///
2 /// \file probe.cc
3 /// USB Blackberry detection routines
4 ///
6 /*
7 Copyright (C) 2005-2010, 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 "record-internal.h"
33 #include "strnlen.h"
34 #include "configfile.h"
35 #include "platform.h"
36 #include <iomanip>
37 #include <errno.h>
38 #include <string.h>
40 using namespace Usb;
42 namespace Barry {
44 unsigned char Intro_Sends[][32] = {
45 // packet #1
46 { 0x00, 0x00, 0x10, 0x00, 0x01, 0xff, 0x00, 0x00,
47 0xa8, 0x18, 0xda, 0x8d, 0x6c, 0x02, 0x00, 0x00 }
51 unsigned char Intro_Receives[][32] = {
52 // response to packet #1
53 { 0x00, 0x00, 0x10, 0x00, 0x02, 0xff, 0x00, 0x00,
54 0xa8, 0x18, 0xda, 0x8d, 0x6c, 0x02, 0x00, 0x00 }
57 namespace {
59 unsigned int GetSize(const unsigned char *packet)
61 uint16_t size = *((uint16_t *)&packet[2]);
62 return btohs(size);
65 bool Intro(int IntroIndex, const EndpointPair &ep, Device &dev, Data &response)
67 dev.BulkWrite(ep.write, Intro_Sends[IntroIndex],
68 GetSize(Intro_Sends[IntroIndex]));
69 try {
70 dev.BulkRead(ep.read, response, 500);
72 catch( Usb::Timeout &to ) {
73 ddout("BulkRead: " << to.what());
74 return false;
76 ddout("BulkRead (" << (unsigned int)ep.read << "):\n" << response);
77 return true;
80 } // anonymous namespace
83 bool Probe::CheckSize(const Data &data, unsigned int required)
85 const unsigned char *pd = data.GetData();
87 if( GetSize(pd) != (unsigned int) data.GetSize() ||
88 data.GetSize() < required ||
89 pd[4] != SB_COMMAND_FETCHED_ATTRIBUTE )
91 dout("Probe: Parse data failure: GetSize(pd): " << GetSize(pd)
92 << ", data.GetSize(): " << data.GetSize()
93 << ", pd[4]: " << (unsigned int) pd[4]);
94 return false;
97 return true;
100 bool Probe::ParsePIN(const Data &data, uint32_t &pin)
102 // validate response data
103 const unsigned char *pd = data.GetData();
105 if( !CheckSize(data, 0x14) )
106 return false;
108 // capture the PIN
109 pin = btohl(*((uint32_t *) &pd[16]));
111 return true;
114 bool Probe::ParseDesc(const Data &data, std::string &desc)
116 if( !CheckSize(data, 29) )
117 return false;
119 // capture the description
120 const char *d = (const char*) &data.GetData()[28];
121 int maxlen = data.GetSize() - 28;
122 desc.assign(d, strnlen(d, maxlen));
124 return true;
127 Probe::Probe(const char *busname, const char *devname,
128 const Usb::EndpointPair *epp)
129 : m_fail_count(0)
130 , m_epp_override(epp)
132 if( m_epp_override ) {
133 m_epp = *epp;
136 // let the programmer pass in "" as well as 0
137 if( busname && !strlen(busname) )
138 busname = 0;
139 if( devname && !strlen(devname) )
140 devname = 0;
142 // Search for standard product ID first
143 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_BLACKBERRY, busname, devname);
145 // Search for Pearl devices second
147 // productID 6 devices (PRODUCT_RIM_PEARL) do not expose
148 // the USB class 255 interface we need, but only the
149 // Mass Storage one. Here we search for PRODUCT_RIM_PEARL_DUAL,
150 // (ID 4) which has both enabled.
151 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_PEARL_DUAL, busname, devname);
152 // And a special case, which behaves similar to the PEARL_DUAL,
153 // but with a unique Product ID.
154 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_PEARL_8120, busname, devname);
155 // And one more! The Pearl Flip
156 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_PEARL_FLIP, busname, devname);
158 // And one more time, for the Blackberry Storm
159 ProbeMatching(VENDOR_RIM, PRODUCT_RIM_STORM, busname, devname);
162 void Probe::ProbeMatching(int vendor, int product,
163 const char *busname, const char *devname)
165 Usb::DeviceIDType devid;
167 Match match(vendor, product, busname, devname);
168 while( match.next_device(&devid) ) try {
169 ProbeDevice(devid);
171 catch( Usb::Error &e ) {
172 dout("Usb::Error exception caught: " << e.what());
173 if( e.libusb_errcode() == -EBUSY ) {
174 m_fail_count++;
175 m_fail_msgs.push_back(e.what());
177 else {
178 throw;
183 void Probe::ProbeDevice(Usb::DeviceIDType devid)
185 // skip if we can't properly discover device config
186 DeviceDiscovery discover(devid);
187 ConfigDesc &config = discover.configs[BLACKBERRY_CONFIGURATION];
189 // search for interface class
190 InterfaceDiscovery::base_type::iterator idi = config.interfaces.begin();
191 for( ; idi != config.interfaces.end(); idi++ ) {
192 if( idi->second.desc.bInterfaceClass == BLACKBERRY_DB_CLASS )
193 break;
195 if( idi == config.interfaces.end() ) {
196 dout("Probe: Interface with BLACKBERRY_DB_CLASS ("
197 << BLACKBERRY_DB_CLASS << ") not found.");
198 return; // not found
201 unsigned char InterfaceNumber = idi->second.desc.bInterfaceNumber;
202 dout("Probe: using InterfaceNumber: " << (unsigned int) InterfaceNumber);
204 // check endpoint validity
205 EndpointDiscovery &ed = config.interfaces[InterfaceNumber].endpoints;
206 if( !ed.IsValid() || ed.GetEndpointPairs().size() == 0 ) {
207 dout("Probe: endpoint invalid. ed.IsValud() == "
208 << (ed.IsValid() ? "true" : "false")
209 << ", ed.GetEndpointPairs().size() == "
210 << ed.GetEndpointPairs().size());
211 return;
214 ProbeResult result;
215 result.m_dev = devid;
216 result.m_interface = InterfaceNumber;
217 result.m_zeroSocketSequence = 0;
219 // open device
220 Device dev(devid);
221 // dev.Reset();
222 // sleep(5);
224 // make sure we're talking to the right config
225 unsigned char cfg;
226 if( !dev.GetConfiguration(cfg) )
227 throw Usb::Error(dev.GetLastError(),
228 "Probe: GetConfiguration failed");
229 if( cfg != BLACKBERRY_CONFIGURATION || MUST_SET_CONFIGURATION ) {
230 if( !dev.SetConfiguration(BLACKBERRY_CONFIGURATION) )
231 throw Usb::Error(dev.GetLastError(),
232 "Probe: SetConfiguration failed");
235 // open interface
236 Interface iface(dev, InterfaceNumber);
238 // Try the initial probing of endpoints
239 ProbeDeviceEndpoints(dev, ed, result);
241 if( !result.m_ep.IsComplete() ) {
242 // Probing of end-points failed, so try reprobing
243 // after calling usb_set_altinterface().
245 // Calling usb_set_altinterface() should be harmless
246 // and can help the host and device to synchronize the
247 // USB state, especially on FreeBSD and Mac OS X.
248 // However it can cause usb-storage URBs to be lost
249 // on some devices, so is only used if necessary.
250 dout("Probe: probing endpoints failed, retrying after setting alternate interface");
251 dev.SetAltInterface(InterfaceNumber);
252 result.m_needSetAltInterface = true;
253 ProbeDeviceEndpoints(dev, ed, result);
256 // add to list
257 if( result.m_ep.IsComplete() ) {
258 // before adding to list, try to load the device's
259 // friendly name from the configfile... but don't
260 // fail if we can't do it
261 try {
262 ConfigFile cfg(result.m_pin);
263 result.m_cfgDeviceName = cfg.GetDeviceName();
265 catch( Barry::ConfigFileError & ) {
266 // ignore...
269 m_results.push_back(result);
270 ddout("Using ReadEndpoint: " << (unsigned int)result.m_ep.read);
271 ddout(" WriteEndpoint: " << (unsigned int)result.m_ep.write);
273 else {
274 ddout("Unable to discover endpoint pair for one device.");
278 void Probe::ProbeDeviceEndpoints(Device &dev, EndpointDiscovery &ed, ProbeResult &result)
280 if( m_epp_override ) {
281 // user has given us endpoints to try... so try them
282 uint32_t pin;
283 uint8_t zeroSocketSequence;
284 std::string desc;
285 bool needClearHalt;
286 if( ProbePair(dev, m_epp, pin, desc, zeroSocketSequence, needClearHalt) ) {
287 // looks good, finish filling out the result
288 result.m_ep = m_epp;
289 result.m_pin = pin;
290 result.m_description = desc;
291 result.m_zeroSocketSequence = zeroSocketSequence;
292 result.m_needClearHalt = needClearHalt;
295 else {
296 // find the first bulk read/write endpoint pair that answers
297 // to our probe commands
298 // Start with second pair, since evidence indicates the later pairs
299 // are the ones we need.
300 size_t i;
301 for(i = ed.GetEndpointPairs().size() > 1 ? 1 : 0;
302 i < ed.GetEndpointPairs().size();
303 i++ )
305 const EndpointPair &ep = ed.GetEndpointPairs()[i];
306 if( ep.type == USB_ENDPOINT_TYPE_BULK ) {
308 uint32_t pin;
309 uint8_t zeroSocketSequence;
310 std::string desc;
311 bool needClearHalt;
312 if( ProbePair(dev, ep, pin, desc, zeroSocketSequence, needClearHalt) ) {
313 result.m_ep = ep;
314 result.m_pin = pin;
315 result.m_description = desc;
316 result.m_zeroSocketSequence = zeroSocketSequence;
317 result.m_needClearHalt = needClearHalt;
318 break;
321 else {
322 dout("Probe: Skipping non-bulk endpoint pair (offset: "
323 << i-1 << ") ");
327 // check for ip modem endpoints
328 i++;
329 if( i < ed.GetEndpointPairs().size() ) {
330 const EndpointPair &ep = ed.GetEndpointPairs()[i];
331 if( ProbeModem(dev, ep) ) {
332 result.m_epModem = ep;
338 bool Probe::ProbePair(Usb::Device &dev,
339 const Usb::EndpointPair &ep,
340 uint32_t &pin,
341 std::string &desc,
342 uint8_t &zeroSocketSequence,
343 bool &needClearHalt)
345 // Initially assume that clear halt isn't needed as it causes some
346 // devices to drop packets. The suspicion is that the toggle bits
347 // get out of sync, but this hasn't been confirmed with hardware
348 // tracing.
350 // It is possible to always use clear halt, as long as SET
351 // INTERFACE has been sent before, via usb_set_altinterface().
352 // However this has the side affect that any outstanding URBs
353 // on other interfaces (i.e. usb-storage) timeout and lose
354 // their data. This is not a good thing as it can corrupt the
355 // file system exposed over usb-storage. This also has the
356 // side-affect that usb-storage issues a port reset after the
357 // 30 second timeout, which kills any current Barry
358 // connection.
360 // To further complicate matters some devices, such as the
361 // 8830, always need clear halt before they will respond to
362 // probes.
364 // So to work with all these device quirks the probe is first
365 // attempted without calling clear halt. If that probe fails
366 // then a clear halt is issued followed by a retry on the
367 // probing.
368 needClearHalt = false;
370 Data data;
371 dev.BulkDrain(ep.read);
372 if( !Intro(0, ep, dev, data) ) {
373 // Try clearing halt and then reprobing
374 dout("Probe: Intro(0) failed, retrying after clearing halt");
375 dev.ClearHalt(ep.read);
376 dev.ClearHalt(ep.write);
377 needClearHalt = true;
378 // Retry
379 dev.BulkDrain(ep.read);
380 if( !Intro(0, ep, dev, data) ) {
381 // Still no response so fail the probe
382 dout("Probe: Intro(0) still failed after clearing halt");
383 return false;
387 SocketZero socket(dev, ep.write, ep.read);
389 Data send, receive;
390 ZeroPacket packet(send, receive);
392 // unknown attribute: 0x14 / 0x01
393 packet.GetAttribute(SB_OBJECT_INITIAL_UNKNOWN,
394 SB_ATTR_INITIAL_UNKNOWN);
395 socket.Send(packet);
397 // fetch PIN
398 packet.GetAttribute(SB_OBJECT_PROFILE, SB_ATTR_PROFILE_PIN);
399 socket.Send(packet);
400 if( packet.ObjectID() != SB_OBJECT_PROFILE ||
401 packet.AttributeID() != SB_ATTR_PROFILE_PIN ||
402 !ParsePIN(receive, pin) )
404 dout("Probe: unable to fetch PIN");
405 return false;
408 // fetch Description
409 packet.GetAttribute(SB_OBJECT_PROFILE, SB_ATTR_PROFILE_DESC);
410 socket.Send(packet);
411 // response ObjectID does not match request... :-/
412 if( // packet.ObjectID() != SB_OBJECT_PROFILE ||
413 packet.AttributeID() != SB_ATTR_PROFILE_DESC ||
414 !ParseDesc(receive, desc) )
416 dout("Probe: unable to fetch description");
419 // more unknowns:
420 for( uint16_t attr = 5; attr < 9; attr++ ) {
421 packet.GetAttribute(SB_OBJECT_SOCKET_UNKNOWN, attr);
422 socket.Send(packet);
423 // FIXME parse these responses, if they turn
424 // out to be important
427 // all info obtained!
428 zeroSocketSequence = socket.GetZeroSocketSequence();
429 return true;
432 bool Probe::ProbeModem(Usb::Device &dev, const Usb::EndpointPair &ep)
435 // This check is not needed for all devices. Some devices,
436 // like the 8700 have both the RIM_UsbSerData mode and IpModem mode.
438 // If this function is called, then we have extra endpoints,
439 // so might as well try them.
441 // FIXME - someday, we might wish to confirm that the endpoints
442 // work as a modem, and return true/false based on that test.
444 return true;
447 // Thanks to Rick Scott (XmBlackBerry:bb_usb.c) for reverse engineering this
448 // int num_read;
449 // char data[255];
450 // int local_errno;
452 // num_read = usb_control_msg(dev.GetHandle(),
453 // /* bmRequestType */ USB_ENDPOINT_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
454 // /* bRequest */ 0xa5,
455 // /* wValue */ 0,
456 // /* wIndex */ 1,
457 // /* data */ data,
458 // /* wLength */ sizeof(data),
459 // /* timeout */ 2000);
460 // local_errno = errno;
461 // if( num_read > 1 ) {
462 // if( data[0] == 0x02 ) {
463 // return true;
464 // }
465 // }
466 // return false;
469 int Probe::FindActive(Barry::Pin pin) const
471 return FindActive(m_results, pin);
474 int Probe::FindActive(const Barry::Probe::Results &results, Barry::Pin pin)
476 int i = Find(results, pin);
478 if( i == -1 && pin == 0 ) {
479 // can we default to a single device?
480 if( results.size() == 1 )
481 return 0; // yes!
484 return i;
487 int Probe::Find(const Results &results, Barry::Pin pin)
489 Barry::Probe::Results::const_iterator ci = results.begin();
490 for( int i = 0; ci != results.end(); i++, ++ci ) {
491 if( ci->m_pin == pin )
492 return i;
494 // PIN not found
495 return -1;
498 void ProbeResult::DumpAll(std::ostream &os) const
500 os << *this
501 << ", Interface: 0x" << std::hex << (unsigned int) m_interface
502 << ", Endpoints: (read: 0x" << std::hex << (unsigned int) m_ep.read
503 << ", write: 0x" << std::hex << (unsigned int) m_ep.write
504 << ", type: 0x" << std::hex << (unsigned int) m_ep.type
505 << ", ZeroSocketSequence: 0x" << std::hex << (unsigned int) m_zeroSocketSequence;
508 std::ostream& operator<< (std::ostream &os, const ProbeResult &pr)
510 os << "Device ID: " << pr.m_dev
511 << ". PIN: " << pr.m_pin.Str()
512 << ", Description: " << pr.m_description;
513 if( pr.m_cfgDeviceName.size() )
514 os << ", Name: " << pr.m_cfgDeviceName;
515 return os;
518 } // namespace Barry