Updated ChangeLog
[barry.git] / src / m_ipmodem.cc
blob47361c1676b4e8e0928de4e24154489c6c2542dc
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
227 m_dev.ClearHalt(pair.read);
228 m_dev.ClearHalt(pair.write);
230 // Send stop command
231 ddout("IPModem: Sending Stop Response:\n");
232 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
233 try {
234 m_dev.BulkRead(read_ep, data, 500);
235 ddout("IPModem: Stop Response Packet:\n" << data);
237 catch( Usb::Timeout &to ) {
238 // do nothing on timeouts
239 ddout("IPModem: Stop Response Timeout");
242 // Send start commands to figure out if the device needs a password.
243 ddout("IPModem: Sending Start Response:\n");
244 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
245 m_dev.BulkRead(read_ep, data, 5000);
246 ddout("IPModem: Start Response Packet:\n" << data);
248 // 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
249 if( data.GetSize() >= 9 && data.GetData()[0] == 0x02 &&
250 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag))== 0 ) {
251 // Got a password request packet
252 ddout("IPModem: Password request packet:\n" << data);
254 // Check how many retries are left
255 if( data.GetData()[8] < BARRY_MIN_PASSWORD_TRIES ) {
256 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.",
257 data.GetData()[8],
258 true);
260 memcpy(&seed, data.GetData() + 4, sizeof(seed));
261 // Send password
262 if( !SendPassword(password, seed) ) {
263 throw Barry::Error("IpModem: Error sending password.");
266 // Re-send "start" packet
267 ddout("IPModem: Re-sending Start Response:\n");
268 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
269 m_dev.BulkRead(read_ep, data);
270 ddout("IPModem: Start Response Packet:\n" << data);
273 // send packet with the session_key
274 unsigned char response_header[] = { 0x00, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0xc2, 1, 0 };
275 memcpy(&response[0], response_header, sizeof(response_header));
276 memcpy(&response[16], m_session_key, sizeof(m_session_key));
277 memcpy(&response[24], special_flag, sizeof(special_flag));
278 ddout("IPModem: Sending Session key:\n");
279 m_dev.BulkWrite(write_ep, response, sizeof(response));
280 if( data.GetSize() >= 16 ) {
281 switch(data.GetData()[0])
283 case 0x00: // Null packet
284 break;
286 case 0x02: // password seed received
287 memcpy(&seed, data.GetData() + 4, sizeof(uint32_t));
288 if( !SendPassword( password, seed ) ) {
289 throw Barry::Error("IpModem: Error sending password.");
291 break;
292 case 0x04: // command accepted
293 break;
295 default: // ???
296 ddout("IPModem: Unknown response.\n");
297 break;
301 // see if the modem will respond to commands
302 const char modem_command[] = { "AT\r" };
303 m_dev.BulkWrite(write_ep, modem_command, strlen(modem_command));
304 m_dev.BulkRead(read_ep, data);
305 ddout("IPModem: Test command response.\n" << data);
306 if( data.GetSize() >= 1 ) {
307 switch(data.GetData()[0])
309 case 0x00: // Null packet
310 try {
311 m_dev.BulkRead(read_ep, data, 5000);
312 ddout("IPModem: AT Response Packet:\n" << data);
314 catch( Usb::Timeout &to ) {
315 // do nothing on timeouts
316 ddout("IPModem: AT Response Timeout");
318 break;
320 case 0x02: // password seed received
321 if( !password || strlen(password) == 0 ) {
322 throw BadPassword("This device requested a password.",
323 data.GetSize() >= 9 ? data.GetData()[8] : 0, false);
325 else { // added for the Storm 9000
326 memcpy(&seed, data.GetData() + 4, sizeof(seed));
327 if( !SendPassword( password, seed ) ) {
328 throw Barry::Error("IpModem: Error sending password.");
331 break;
332 case 0x04: // command accepted
333 break;
335 case 0x07: // device is password protected?
336 throw BadPassword("This device requires a password.", 0, false);
338 default: // ???
339 ddout("IPModem: Unknown AT command response.\n");
340 // treat this unknown data as a serial response
341 if( m_callback ) {
342 (*m_callback)(m_callback_context,
343 data.GetData(),
344 data.GetSize());
346 break;
349 ddout("IPModem: Modem Ready.\n");
351 // spawn read thread
352 m_continue_reading = true;
353 int ret = pthread_create(&m_modem_read_thread, NULL, &IpModem::DataReadThread, this);
354 if( ret ) {
355 m_continue_reading = false;
356 throw Barry::ErrnoError("IpModem: Error creating USB read thread.", ret);
360 void IpModem::Write(const Data &data, int timeout)
362 if( data.GetSize() == 0 )
363 return; // nothing to do
365 // according to Rick Scott the m_filter is not needed with the ip modem
366 // but with the 8320 with Rogers, it doesn't seem to connect without it
367 // If this is a performance problem, perhaps make this a runtime
368 // option.
369 m_dev.BulkWrite(m_con.GetProbeResult().m_epModem.write,
370 m_filter.Write(data), timeout);
373 void IpModem::Close()
375 // This is the terminate connection sequence
376 // that resets the modem so we can re-connect
377 // without unpluging the USB cable or reseting
378 // the whole device.
379 // This works on a BB 8703e a with password. other BB's??
380 unsigned char end[28];
381 int read_ep = m_con.GetProbeResult().m_epModem.read;
382 int write_ep = m_con.GetProbeResult().m_epModem.write;
383 Data data;
385 //0 0 0 0 b0 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
386 ddout("IpModem: Closing connection.");
387 memset(end, 0, sizeof(end));
388 end[4] = 0xb0;
389 end[13] = 0xc2;
390 end[14] = 0x01;
391 memcpy(&end[16], m_session_key, sizeof(m_session_key));
392 memcpy(&end[24], special_flag, sizeof(special_flag));
393 m_dev.BulkWrite(write_ep, end, sizeof(end));
395 //0 0 0 0 20 0 0 0 3 0 0 0 0 c2 1 0 + session_key + special_flag
396 memset(end, 0, sizeof(end));
397 end[4] = 0x20;
398 end[8] = 0x03;
399 end[13] = 0xc2;
400 end[14] = 0x01;
401 memcpy(&end[16], m_session_key, sizeof(m_session_key));
402 memcpy(&end[24], special_flag, sizeof(special_flag));
403 m_dev.BulkWrite(write_ep, end, sizeof(end));
405 //0 0 0 0 30 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
406 // The session_key is set to 0x0's when there is no password.
407 memset(end, 0, sizeof(end));
408 end[4] = 0x30;
409 end[13] = 0xc2;
410 end[14] = 0x01;
411 memcpy(&end[16], m_session_key, sizeof(m_session_key));
412 memcpy(&end[24], special_flag, sizeof(special_flag));
413 m_dev.BulkWrite(write_ep, end, sizeof(end));
414 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
415 try {
416 m_dev.BulkRead(read_ep, data, 5000);
417 ddout("IPModem: Close read packet:\n" << data);
419 catch( Usb::Timeout &to ) {
420 // do nothing on timeouts
421 ddout("IPModem: Close Read Timeout");
423 // stop the read thread
424 if( m_continue_reading ) {
425 m_continue_reading = false;
426 pthread_join(m_modem_read_thread, NULL);
428 ddout("IPmodem: Closed!");
432 }} // namespace Barry::Mode