lib: added additional checks to socket.cc to avoid using a null device object
[barry.git] / src / m_ipmodem.cc
blob2a56f21fea1390a979af97804dcdda15f55aa95e
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-2010, 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 "data.h"
26 #include "debug.h"
27 #include <sstream>
28 #include <string.h>
29 #include "sha1.h"
31 namespace Barry { namespace Mode {
33 const char special_flag[] = { 0x78, 0x56, 0x34, 0x12 }; // 0x12345678
34 const char start[] = { 0x01, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
35 const char pw_start[] = { 0x01, 0, 0, 0, 1, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
36 const char stop[] = { 0x01, 0, 0, 0, 0, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
38 //////////////////////////////////////////////////////////////////////////////
39 // Mode::IpModem class
41 IpModem::IpModem(Controller &con,
42 DeviceDataCallback callback,
43 void *callback_context)
44 : m_con(con)
45 , m_dev(con.m_dev)
46 , m_continue_reading(false)
47 , m_callback(callback)
48 , m_callback_context(callback_context)
50 memset(m_session_key, 0, sizeof(m_session_key));
53 IpModem::~IpModem()
55 try {
56 Close();
57 } catch( std::exception &e ) {
58 dout("Exception caught in IpModem destructor, ignoring: "
59 << e.what());
63 bool IpModem::SendPassword( const char *password, uint32_t seed )
65 if( !password || strlen(password) == 0 ) {
66 throw BadPassword("Logic error: No password provided in SendPassword.", 0, false);
69 int read_ep = m_con.GetProbeResult().m_epModem.read;
70 int write_ep = m_con.GetProbeResult().m_epModem.write;
71 unsigned char pwdigest[SHA_DIGEST_LENGTH];
72 unsigned char prefixedhash[SHA_DIGEST_LENGTH + 4];
73 unsigned char pw_response[SHA_DIGEST_LENGTH + 8];
74 uint32_t new_seed;
75 Data data;
77 if( !password || strlen(password) == 0 ) {
78 throw BadPassword("No password provided.", 0, false);
81 // Build the password hash
82 // first, hash the password by itself
83 SHA1((unsigned char *) password, strlen(password), pwdigest);
85 // prefix the resulting hash with the provided seed
86 memcpy(&prefixedhash[0], &seed, sizeof(uint32_t));
87 memcpy(&prefixedhash[4], pwdigest, SHA_DIGEST_LENGTH);
89 // hash again
90 SHA1((unsigned char *) prefixedhash, SHA_DIGEST_LENGTH + 4, pwdigest);
92 // Build the response packet
93 const char pw_rsphdr[] = { 0x03, 0x00, 0x00, 0x00 };
94 memcpy(&pw_response[0], pw_rsphdr, sizeof(pw_rsphdr));
95 memcpy(&pw_response[4], pwdigest, SHA_DIGEST_LENGTH);
96 memcpy(&pw_response[24], special_flag, sizeof(special_flag));
98 // Send the password response packet
99 m_dev.BulkWrite(write_ep, pw_response, sizeof(pw_response));
100 m_dev.BulkRead(read_ep, data);
101 ddout("IPModem: Read password response.\n" << data);
103 // Added for the BB Storm 9000's second password request
104 if( data.GetSize() >= 16 && data.GetData()[0] == 0x00 ) {
105 try {
106 m_dev.BulkRead(read_ep, data, 500);
107 ddout("IPModem: Null Response Packet:\n" << data);
109 catch( Usb::Timeout &to ) {
110 // do nothing on timeouts
111 ddout("IPModem: Null Response Timeout");
116 // check response 04 00 00 00 .......
117 // On the 8703e the seed is incremented, retries are reset to 10
118 // when the password is accepted.
120 // If data.GetData() + 4 is = to the orginal seed +1 or 00 00 00 00
121 // then the password was acceppted.
123 // When data.GetData() + 4 is not 00 00 00 00 then data.GetData()[8]
124 // contains the number of password retrys left.
126 if( data.GetSize() >= 9 && data.GetData()[0] == 0x04 ) {
127 memcpy(&new_seed, data.GetData() + 4, sizeof(uint32_t));
128 seed++;
129 if( seed == new_seed || new_seed == 0 ) {
130 ddout("IPModem: Password accepted.\n");
132 #if SHA_DIGEST_LENGTH < SB_IPMODEM_SESSION_KEY_LENGTH
133 #error Session key field must be smaller than SHA digest
134 #endif
135 // Create session key - last 8 bytes of the password hash
136 memcpy(&m_session_key[0],
137 pwdigest + SHA_DIGEST_LENGTH - sizeof(m_session_key),
138 sizeof(m_session_key));
140 // blank password hashes as we don't need these anymore
141 memset(pwdigest, 0, sizeof(pwdigest));
142 memset(prefixedhash, 0, sizeof(prefixedhash));
143 return true;
145 else {
146 ddout("IPModem: Invalid password.\n" << data);
147 throw BadPassword("Password rejected by device.", data.GetData()[8], false);
150 // Unknown packet
151 ddout("IPModem: Error unknown packet.\n" << data);
152 return false;
155 //////////////////////////////////////////////////////////////////////////////
156 // protected API / static functions
158 void *IpModem::DataReadThread(void *userptr)
160 IpModem *ipmodem = (IpModem*) userptr;
162 int read_ep = ipmodem->m_con.GetProbeResult().m_epModem.read;
163 Data data;
165 while( ipmodem->m_continue_reading ) {
167 try {
169 ipmodem->m_dev.BulkRead(read_ep, data, 5000);
171 // is it a special code?
172 if( data.GetSize() > 4 &&
173 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag)) == 0 ) {
174 // log, then drop it on the floor for now
175 ddout("IPModem: Special packet:\n" << data);
176 continue;
179 // call callback if available
180 if( ipmodem->m_callback ) {
181 (*ipmodem->m_callback)(ipmodem->m_callback_context,
182 data.GetData(),
183 data.GetSize());
185 // else {
186 // // append data to readCache
187 // FIXME;
188 // }
191 catch( Usb::Timeout &to ) {
192 // do nothing on timeouts
193 ddout("IPModem: Timeout in DataReadThread!");
195 catch( std::exception &e ) {
196 eout("Exception in IpModem::DataReadThread: " << e.what());
200 return 0;
203 //////////////////////////////////////////////////////////////////////////////
204 // public API
206 void IpModem::Open(const char *password)
208 int read_ep = m_con.GetProbeResult().m_epModem.read;
209 int write_ep = m_con.GetProbeResult().m_epModem.write;
210 unsigned char response[28];
211 uint32_t seed;
212 Data data;
214 // check that we have endpoints for the modem
215 const Usb::EndpointPair &pair = m_con.GetProbeResult().m_epModem;
216 if( !pair.IsComplete() ) {
217 std::ostringstream oss;
218 oss << "IP Modem not supported by this device: "
219 << "read: " << std::hex << (unsigned int) pair.read
220 << " write: " << std::hex << (unsigned int) pair.write
221 << " type: " << std::hex << (unsigned int) pair.type;
222 eout(oss.str());
223 throw Barry::Error(oss.str());
226 // clear halt when starting out only if needed
227 if( m_con.m_result.m_needClearHalt ) {
228 m_dev.ClearHalt(pair.read);
229 m_dev.ClearHalt(pair.write);
232 // Send stop command
233 ddout("IPModem: Sending Stop Response:\n");
234 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
235 try {
236 m_dev.BulkRead(read_ep, data, 500);
237 ddout("IPModem: Stop Response Packet:\n" << data);
239 catch( Usb::Timeout &to ) {
240 // do nothing on timeouts
241 ddout("IPModem: Stop Response Timeout");
244 // Send start commands to figure out if the device needs a password.
245 ddout("IPModem: Sending Start Response:\n");
246 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
247 m_dev.BulkRead(read_ep, data, 5000);
248 ddout("IPModem: Start Response Packet:\n" << data);
250 // 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
251 if( data.GetSize() >= 9 && data.GetData()[0] == 0x02 &&
252 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag))== 0 ) {
253 // Got a password request packet
254 ddout("IPModem: Password request packet:\n" << data);
256 // Check how many retries are left
257 if( data.GetData()[8] < BARRY_MIN_PASSWORD_TRIES ) {
258 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.",
259 data.GetData()[8],
260 true);
262 memcpy(&seed, data.GetData() + 4, sizeof(seed));
263 // Send password
264 if( !SendPassword(password, seed) ) {
265 throw Barry::Error("IpModem: Error sending password.");
268 // Re-send "start" packet
269 ddout("IPModem: Re-sending Start Response:\n");
270 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
271 m_dev.BulkRead(read_ep, data);
272 ddout("IPModem: Start Response Packet:\n" << data);
275 // send packet with the session_key
276 unsigned char response_header[] = { 0x00, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0xc2, 1, 0 };
277 memcpy(&response[0], response_header, sizeof(response_header));
278 memcpy(&response[16], m_session_key, sizeof(m_session_key));
279 memcpy(&response[24], special_flag, sizeof(special_flag));
280 ddout("IPModem: Sending Session key:\n");
281 m_dev.BulkWrite(write_ep, response, sizeof(response));
282 if( data.GetSize() >= 16 ) {
283 switch(data.GetData()[0])
285 case 0x00: // Null packet
286 break;
288 case 0x02: // password seed received
289 memcpy(&seed, data.GetData() + 4, sizeof(uint32_t));
290 if( !SendPassword( password, seed ) ) {
291 throw Barry::Error("IpModem: Error sending password.");
293 break;
294 case 0x04: // command accepted
295 break;
297 default: // ???
298 ddout("IPModem: Unknown response.\n");
299 break;
303 // see if the modem will respond to commands
304 const char modem_command[] = { "AT\r" };
305 m_dev.BulkWrite(write_ep, modem_command, strlen(modem_command));
306 m_dev.BulkRead(read_ep, data);
307 ddout("IPModem: Test command response.\n" << data);
308 if( data.GetSize() >= 1 ) {
309 switch(data.GetData()[0])
311 case 0x00: // Null packet
312 try {
313 m_dev.BulkRead(read_ep, data, 5000);
314 ddout("IPModem: AT Response Packet:\n" << data);
316 catch( Usb::Timeout &to ) {
317 // do nothing on timeouts
318 ddout("IPModem: AT Response Timeout");
320 break;
322 case 0x02: // password seed received
323 if( !password || strlen(password) == 0 ) {
324 throw BadPassword("This device requested a password.",
325 data.GetSize() >= 9 ? data.GetData()[8] : 0, false);
327 else { // added for the Storm 9000
328 memcpy(&seed, data.GetData() + 4, sizeof(seed));
329 if( !SendPassword( password, seed ) ) {
330 throw Barry::Error("IpModem: Error sending password.");
333 break;
334 case 0x04: // command accepted
335 break;
337 case 0x07: // device is password protected?
338 throw BadPassword("This device requires a password.", 0, false);
340 default: // ???
341 ddout("IPModem: Unknown AT command response.\n");
342 // treat this unknown data as a serial response
343 if( m_callback ) {
344 (*m_callback)(m_callback_context,
345 data.GetData(),
346 data.GetSize());
348 break;
351 ddout("IPModem: Modem Ready.\n");
353 // spawn read thread
354 m_continue_reading = true;
355 int ret = pthread_create(&m_modem_read_thread, NULL, &IpModem::DataReadThread, this);
356 if( ret ) {
357 m_continue_reading = false;
358 throw Barry::ErrnoError("IpModem: Error creating USB read thread.", ret);
362 void IpModem::Write(const Data &data, int timeout)
364 if( data.GetSize() == 0 )
365 return; // nothing to do
367 // according to Rick Scott the m_filter is not needed with the ip modem
368 // but with the 8320 with Rogers, it doesn't seem to connect without it
369 // If this is a performance problem, perhaps make this a runtime
370 // option.
371 m_dev.BulkWrite(m_con.GetProbeResult().m_epModem.write,
372 m_filter.Write(data), timeout);
375 void IpModem::Close()
377 // This is the terminate connection sequence
378 // that resets the modem so we can re-connect
379 // without unpluging the USB cable or reseting
380 // the whole device.
381 // This works on a BB 8703e a with password. other BB's??
382 unsigned char end[28];
383 int read_ep = m_con.GetProbeResult().m_epModem.read;
384 int write_ep = m_con.GetProbeResult().m_epModem.write;
385 Data data;
387 //0 0 0 0 b0 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
388 ddout("IpModem: Closing connection.");
389 memset(end, 0, sizeof(end));
390 end[4] = 0xb0;
391 end[13] = 0xc2;
392 end[14] = 0x01;
393 memcpy(&end[16], m_session_key, sizeof(m_session_key));
394 memcpy(&end[24], special_flag, sizeof(special_flag));
395 m_dev.BulkWrite(write_ep, end, sizeof(end));
397 //0 0 0 0 20 0 0 0 3 0 0 0 0 c2 1 0 + session_key + special_flag
398 memset(end, 0, sizeof(end));
399 end[4] = 0x20;
400 end[8] = 0x03;
401 end[13] = 0xc2;
402 end[14] = 0x01;
403 memcpy(&end[16], m_session_key, sizeof(m_session_key));
404 memcpy(&end[24], special_flag, sizeof(special_flag));
405 m_dev.BulkWrite(write_ep, end, sizeof(end));
407 //0 0 0 0 30 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
408 // The session_key is set to 0x0's when there is no password.
409 memset(end, 0, sizeof(end));
410 end[4] = 0x30;
411 end[13] = 0xc2;
412 end[14] = 0x01;
413 memcpy(&end[16], m_session_key, sizeof(m_session_key));
414 memcpy(&end[24], special_flag, sizeof(special_flag));
415 m_dev.BulkWrite(write_ep, end, sizeof(end));
416 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
417 try {
418 m_dev.BulkRead(read_ep, data, 5000);
419 ddout("IPModem: Close read packet:\n" << data);
421 catch( Usb::Timeout &to ) {
422 // do nothing on timeouts
423 ddout("IPModem: Close Read Timeout");
425 // stop the read thread
426 if( m_continue_reading ) {
427 m_continue_reading = false;
428 pthread_join(m_modem_read_thread, NULL);
430 ddout("IPmodem: Closed!");
434 }} // namespace Barry::Mode