lib: Adding DEBUG_ONLY macro for parameters only used in debug builds.
[barry.git] / src / m_ipmodem.cc
blob8ed60d142eddb9541e247f5b1ffd5536cc9a9aa5
1 ///
2 /// \file m_ipmodem.cc
3 /// Mode class for GPRS modem mode (using endpoints on
4 /// modern devices)
5 ///
7 /*
8 Copyright (C) 2008-2012, Net Direct Inc. (http://www.netdirect.ca/)
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 See the GNU General Public License in the COPYING file at the
20 root directory of this project for more details.
23 #include "m_ipmodem.h"
24 #include "controller.h"
25 #include "controllerpriv.h"
26 #include "data.h"
27 #include "debug.h"
28 #include <sstream>
29 #include <string.h>
30 #include "sha1.h"
32 namespace Barry { namespace Mode {
34 const char special_flag[] = { 0x78, 0x56, 0x34, 0x12 }; // 0x12345678
35 const char start[] = { 0x01, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
36 const char pw_start[] = { 0x01, 0, 0, 0, 1, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
37 const char stop[] = { 0x01, 0, 0, 0, 0, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
39 //////////////////////////////////////////////////////////////////////////////
40 // Mode::IpModem class
42 IpModem::IpModem(Controller &con,
43 DeviceDataCallback callback,
44 void *callback_context)
45 : m_con(con)
46 , m_dev(con.GetPrivate()->m_dev)
47 , m_continue_reading(false)
48 , m_callback(callback)
49 , m_callback_context(callback_context)
51 memset(m_session_key, 0, sizeof(m_session_key));
54 IpModem::~IpModem()
56 try {
57 Close();
58 } catch( std::exception &DEBUG_ONLY(e) ) {
59 dout("Exception caught in IpModem destructor, ignoring: "
60 << e.what());
64 bool IpModem::SendPassword( const char *password, uint32_t seed )
66 if( !password || strlen(password) == 0 ) {
67 throw BadPassword("Logic error: No password provided in SendPassword.", 0, false);
70 int read_ep = m_con.GetProbeResult().m_epModem.read;
71 int write_ep = m_con.GetProbeResult().m_epModem.write;
72 unsigned char pwdigest[SHA_DIGEST_LENGTH];
73 unsigned char prefixedhash[SHA_DIGEST_LENGTH + 4];
74 unsigned char pw_response[SHA_DIGEST_LENGTH + 8];
75 uint32_t new_seed;
76 Data data;
78 if( !password || strlen(password) == 0 ) {
79 throw BadPassword("No password provided.", 0, false);
82 // Build the password hash
83 // first, hash the password by itself
84 SHA1((unsigned char *) password, strlen(password), pwdigest);
86 // prefix the resulting hash with the provided seed
87 memcpy(&prefixedhash[0], &seed, sizeof(uint32_t));
88 memcpy(&prefixedhash[4], pwdigest, SHA_DIGEST_LENGTH);
90 // hash again
91 SHA1((unsigned char *) prefixedhash, SHA_DIGEST_LENGTH + 4, pwdigest);
93 // Build the response packet
94 const char pw_rsphdr[] = { 0x03, 0x00, 0x00, 0x00 };
95 memcpy(&pw_response[0], pw_rsphdr, sizeof(pw_rsphdr));
96 memcpy(&pw_response[4], pwdigest, SHA_DIGEST_LENGTH);
97 memcpy(&pw_response[24], special_flag, sizeof(special_flag));
99 // Send the password response packet
100 m_dev.BulkWrite(write_ep, pw_response, sizeof(pw_response));
101 m_dev.BulkRead(read_ep, data);
102 ddout("IPModem: Read password response.\n" << data);
104 // Added for the BB Storm 9000's second password request
105 if( data.GetSize() >= 16 && data.GetData()[0] == 0x00 ) {
106 try {
107 m_dev.BulkRead(read_ep, data, 500);
108 ddout("IPModem: Null Response Packet:\n" << data);
110 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
111 // do nothing on timeouts
112 ddout("IPModem: Null Response Timeout");
117 // check response 04 00 00 00 .......
118 // On the 8703e the seed is incremented, retries are reset to 10
119 // when the password is accepted.
121 // If data.GetData() + 4 is = to the orginal seed +1 or 00 00 00 00
122 // then the password was acceppted.
124 // When data.GetData() + 4 is not 00 00 00 00 then data.GetData()[8]
125 // contains the number of password retrys left.
127 if( data.GetSize() >= 9 && data.GetData()[0] == 0x04 ) {
128 memcpy(&new_seed, data.GetData() + 4, sizeof(uint32_t));
129 seed++;
130 if( seed == new_seed || new_seed == 0 ) {
131 ddout("IPModem: Password accepted.\n");
133 #if SHA_DIGEST_LENGTH < SB_IPMODEM_SESSION_KEY_LENGTH
134 #error Session key field must be smaller than SHA digest
135 #endif
136 // Create session key - last 8 bytes of the password hash
137 memcpy(&m_session_key[0],
138 pwdigest + SHA_DIGEST_LENGTH - sizeof(m_session_key),
139 sizeof(m_session_key));
141 // blank password hashes as we don't need these anymore
142 memset(pwdigest, 0, sizeof(pwdigest));
143 memset(prefixedhash, 0, sizeof(prefixedhash));
144 return true;
146 else {
147 ddout("IPModem: Invalid password.\n" << data);
148 throw BadPassword("Password rejected by device.", data.GetData()[8], false);
151 // Unknown packet
152 ddout("IPModem: Error unknown packet.\n" << data);
153 return false;
156 //////////////////////////////////////////////////////////////////////////////
157 // protected API / static functions
159 void *IpModem::DataReadThread(void *userptr)
161 IpModem *ipmodem = (IpModem*) userptr;
163 int read_ep = ipmodem->m_con.GetProbeResult().m_epModem.read;
164 Data data;
166 while( ipmodem->m_continue_reading ) {
168 try {
170 ipmodem->m_dev.BulkRead(read_ep, data, 5000);
172 // is it a special code?
173 if( data.GetSize() > 4 &&
174 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag)) == 0 ) {
175 // log, then drop it on the floor for now
176 ddout("IPModem: Special packet:\n" << data);
177 continue;
180 // call callback if available
181 if( ipmodem->m_callback ) {
182 (*ipmodem->m_callback)(ipmodem->m_callback_context,
183 data.GetData(),
184 data.GetSize());
186 // else {
187 // // append data to readCache
188 // FIXME;
189 // }
192 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
193 // do nothing on timeouts
194 ddout("IPModem: Timeout in DataReadThread!");
196 catch( std::exception &e ) {
197 eout("Exception in IpModem::DataReadThread: " << e.what());
201 return 0;
204 //////////////////////////////////////////////////////////////////////////////
205 // public API
207 void IpModem::Open(const char *password)
209 int read_ep = m_con.GetProbeResult().m_epModem.read;
210 int write_ep = m_con.GetProbeResult().m_epModem.write;
211 unsigned char response[28];
212 uint32_t seed;
213 Data data;
215 // check that we have endpoints for the modem
216 const Usb::EndpointPair &pair = m_con.GetProbeResult().m_epModem;
217 if( !pair.IsComplete() ) {
218 std::ostringstream oss;
219 oss << "IP Modem not supported by this device: "
220 << "read: " << std::hex << (unsigned int) pair.read
221 << " write: " << std::hex << (unsigned int) pair.write
222 << " type: " << std::hex << (unsigned int) pair.type;
223 eout(oss.str());
224 throw Barry::Error(oss.str());
227 // clear halt when starting out only if needed
228 if( m_con.GetProbeResult().m_needClearHalt ) {
229 m_dev.ClearHalt(pair.read);
230 m_dev.ClearHalt(pair.write);
233 // Send stop command
234 ddout("IPModem: Sending Stop Response:\n");
235 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
236 try {
237 m_dev.BulkRead(read_ep, data, 500);
238 ddout("IPModem: Stop Response Packet:\n" << data);
240 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
241 // do nothing on timeouts
242 ddout("IPModem: Stop Response Timeout");
245 // Send start commands to figure out if the device needs a password.
246 ddout("IPModem: Sending Start Response:\n");
247 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
248 m_dev.BulkRead(read_ep, data, 5000);
249 ddout("IPModem: Start Response Packet:\n" << data);
251 // check for 02 00 00 00 SS SS SS SS RR 00 00 00 0a 00 00 00 PP PP PP PP PP 00 00 00 78 56 34 12
252 if( data.GetSize() >= 9 && data.GetData()[0] == 0x02 &&
253 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag))== 0 ) {
254 // Got a password request packet
255 ddout("IPModem: Password request packet:\n" << data);
257 // Check how many retries are left
258 if( data.GetData()[8] < BARRY_MIN_PASSWORD_TRIES ) {
259 throw BadPassword("Fewer than " BARRY_MIN_PASSWORD_TRIES_ASC " password tries remaining in device. Refusing to proceed, to avoid device zapping itself. Use a Windows client, or re-cradle the device.",
260 data.GetData()[8],
261 true);
263 memcpy(&seed, data.GetData() + 4, sizeof(seed));
264 // Send password
265 if( !SendPassword(password, seed) ) {
266 throw Barry::Error("IpModem: Error sending password.");
269 // Re-send "start" packet
270 ddout("IPModem: Re-sending Start Response:\n");
271 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
272 m_dev.BulkRead(read_ep, data);
273 ddout("IPModem: Start Response Packet:\n" << data);
276 // send packet with the session_key
277 unsigned char response_header[] = { 0x00, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0xc2, 1, 0 };
278 memcpy(&response[0], response_header, sizeof(response_header));
279 memcpy(&response[16], m_session_key, sizeof(m_session_key));
280 memcpy(&response[24], special_flag, sizeof(special_flag));
281 ddout("IPModem: Sending Session key:\n");
282 m_dev.BulkWrite(write_ep, response, sizeof(response));
283 if( data.GetSize() >= 16 ) {
284 switch(data.GetData()[0])
286 case 0x00: // Null packet
287 break;
289 case 0x02: // password seed received
290 memcpy(&seed, data.GetData() + 4, sizeof(uint32_t));
291 if( !SendPassword( password, seed ) ) {
292 throw Barry::Error("IpModem: Error sending password.");
294 break;
295 case 0x04: // command accepted
296 break;
298 default: // ???
299 ddout("IPModem: Unknown response.\n");
300 break;
304 // see if the modem will respond to commands
305 const char modem_command[] = { "AT\r" };
306 m_dev.BulkWrite(write_ep, modem_command, strlen(modem_command));
307 m_dev.BulkRead(read_ep, data);
308 ddout("IPModem: Test command response.\n" << data);
309 if( data.GetSize() >= 1 ) {
310 switch(data.GetData()[0])
312 case 0x00: // Null packet
313 try {
314 m_dev.BulkRead(read_ep, data, 5000);
315 ddout("IPModem: AT Response Packet:\n" << data);
317 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
318 // do nothing on timeouts
319 ddout("IPModem: AT Response Timeout");
321 break;
323 case 0x02: // password seed received
324 if( !password || strlen(password) == 0 ) {
325 throw BadPassword("This device requested a password.",
326 data.GetSize() >= 9 ? data.GetData()[8] : 0, false);
328 else { // added for the Storm 9000
329 memcpy(&seed, data.GetData() + 4, sizeof(seed));
330 if( !SendPassword( password, seed ) ) {
331 throw Barry::Error("IpModem: Error sending password.");
334 break;
335 case 0x04: // command accepted
336 break;
338 case 0x07: // device is password protected?
339 throw BadPassword("This device requires a password.", 0, false);
341 default: // ???
342 ddout("IPModem: Unknown AT command response.\n");
343 // treat this unknown data as a serial response
344 if( m_callback ) {
345 (*m_callback)(m_callback_context,
346 data.GetData(),
347 data.GetSize());
349 break;
352 ddout("IPModem: Modem Ready.\n");
354 // spawn read thread
355 m_continue_reading = true;
356 int ret = pthread_create(&m_modem_read_thread, NULL, &IpModem::DataReadThread, this);
357 if( ret ) {
358 m_continue_reading = false;
359 throw Barry::ErrnoError("IpModem: Error creating USB read thread.", ret);
363 void IpModem::Write(const Data &data, int timeout)
365 if( data.GetSize() == 0 )
366 return; // nothing to do
368 // according to Rick Scott the m_filter is not needed with the ip modem
369 // but with the 8320 with Rogers, it doesn't seem to connect without it
370 // If this is a performance problem, perhaps make this a runtime
371 // option.
372 m_dev.BulkWrite(m_con.GetProbeResult().m_epModem.write,
373 m_filter.Write(data), timeout);
376 void IpModem::Close()
378 // This is the terminate connection sequence
379 // that resets the modem so we can re-connect
380 // without unpluging the USB cable or reseting
381 // the whole device.
382 // This works on a BB 8703e a with password. other BB's??
383 unsigned char end[28];
384 int read_ep = m_con.GetProbeResult().m_epModem.read;
385 int write_ep = m_con.GetProbeResult().m_epModem.write;
386 Data data;
388 //0 0 0 0 b0 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
389 ddout("IpModem: Closing connection.");
390 memset(end, 0, sizeof(end));
391 end[4] = 0xb0;
392 end[13] = 0xc2;
393 end[14] = 0x01;
394 memcpy(&end[16], m_session_key, sizeof(m_session_key));
395 memcpy(&end[24], special_flag, sizeof(special_flag));
396 m_dev.BulkWrite(write_ep, end, sizeof(end));
398 //0 0 0 0 20 0 0 0 3 0 0 0 0 c2 1 0 + session_key + special_flag
399 memset(end, 0, sizeof(end));
400 end[4] = 0x20;
401 end[8] = 0x03;
402 end[13] = 0xc2;
403 end[14] = 0x01;
404 memcpy(&end[16], m_session_key, sizeof(m_session_key));
405 memcpy(&end[24], special_flag, sizeof(special_flag));
406 m_dev.BulkWrite(write_ep, end, sizeof(end));
408 //0 0 0 0 30 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
409 // The session_key is set to 0x0's when there is no password.
410 memset(end, 0, sizeof(end));
411 end[4] = 0x30;
412 end[13] = 0xc2;
413 end[14] = 0x01;
414 memcpy(&end[16], m_session_key, sizeof(m_session_key));
415 memcpy(&end[24], special_flag, sizeof(special_flag));
416 m_dev.BulkWrite(write_ep, end, sizeof(end));
417 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
419 // stop the read thread
420 if( m_continue_reading ) {
421 m_continue_reading = false;
422 pthread_join(m_modem_read_thread, NULL);
424 else {
425 // otherwise, drain the last read
426 try {
427 m_dev.BulkRead(read_ep, data, 5000);
428 ddout("IPModem: Close read packet:\n" << data);
430 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
431 // do nothing on timeouts
432 ddout("IPModem: Close Read Timeout");
436 ddout("IPmodem: Closed!");
440 }} // namespace Barry::Mode