menu: added new Keywords tag to .desktop files
[barry.git] / src / m_ipmodem.cc
blob28423dd0bf6f280318cbb3223ce61359e6465ffc
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-2013, 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 "i18n.h"
24 #include "m_ipmodem.h"
25 #include "controller.h"
26 #include "controllerpriv.h"
27 #include "data.h"
28 #include "debug.h"
29 #include <sstream>
30 #include <string.h>
31 #include "sha1.h"
33 namespace Barry { namespace Mode {
35 const char special_flag[] = { 0x78, 0x56, 0x34, 0x12 }; // 0x12345678
36 const char start[] = { 0x01, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
37 const char pw_start[] = { 0x01, 0, 0, 0, 1, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
38 const char stop[] = { 0x01, 0, 0, 0, 0, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
40 //////////////////////////////////////////////////////////////////////////////
41 // Mode::IpModem class
43 IpModem::IpModem(Controller &con,
44 DeviceDataCallback callback,
45 void *callback_context)
46 : m_con(con)
47 , m_dev(con.GetPrivate()->m_dev)
48 , m_continue_reading(false)
49 , m_callback(callback)
50 , m_callback_context(callback_context)
52 memset(m_session_key, 0, sizeof(m_session_key));
55 IpModem::~IpModem()
57 try {
58 Close();
59 } catch( std::exception &DEBUG_ONLY(e) ) {
60 dout(_("Exception caught in IpModem destructor, ignoring: ")
61 << e.what());
65 bool IpModem::SendPassword( const char *password, uint32_t seed )
67 if( !password || strlen(password) == 0 ) {
68 throw BadPassword(_("Logic error: No password provided in SendPassword."), 0, false);
71 int read_ep = m_con.GetProbeResult().m_epModem.read;
72 int write_ep = m_con.GetProbeResult().m_epModem.write;
73 unsigned char pwdigest[SHA_DIGEST_LENGTH];
74 unsigned char prefixedhash[SHA_DIGEST_LENGTH + 4];
75 unsigned char pw_response[SHA_DIGEST_LENGTH + 8];
76 uint32_t new_seed;
77 Data data;
79 if( !password || strlen(password) == 0 ) {
80 throw BadPassword(_("No password provided."), 0, false);
83 // Build the password hash
84 // first, hash the password by itself
85 SHA1((unsigned char *) password, strlen(password), pwdigest);
87 // prefix the resulting hash with the provided seed
88 memcpy(&prefixedhash[0], &seed, sizeof(uint32_t));
89 memcpy(&prefixedhash[4], pwdigest, SHA_DIGEST_LENGTH);
91 // hash again
92 SHA1((unsigned char *) prefixedhash, SHA_DIGEST_LENGTH + 4, pwdigest);
94 // Build the response packet
95 const char pw_rsphdr[] = { 0x03, 0x00, 0x00, 0x00 };
96 memcpy(&pw_response[0], pw_rsphdr, sizeof(pw_rsphdr));
97 memcpy(&pw_response[4], pwdigest, SHA_DIGEST_LENGTH);
98 memcpy(&pw_response[24], special_flag, sizeof(special_flag));
100 // Send the password response packet
101 m_dev.BulkWrite(write_ep, pw_response, sizeof(pw_response));
102 m_dev.BulkRead(read_ep, data);
103 ddout("IPModem: Read password response.\n" << data);
105 // Added for the BB Storm 9000's second password request
106 if( data.GetSize() >= 16 && data.GetData()[0] == 0x00 ) {
107 try {
108 m_dev.BulkRead(read_ep, data, 500);
109 ddout("IPModem: Null Response Packet:\n" << data);
111 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
112 // do nothing on timeouts
113 ddout("IPModem: Null Response Timeout");
118 // check response 04 00 00 00 .......
119 // On the 8703e the seed is incremented, retries are reset to 10
120 // when the password is accepted.
122 // If data.GetData() + 4 is = to the orginal seed +1 or 00 00 00 00
123 // then the password was acceppted.
125 // When data.GetData() + 4 is not 00 00 00 00 then data.GetData()[8]
126 // contains the number of password retrys left.
128 if( data.GetSize() >= 9 && data.GetData()[0] == 0x04 ) {
129 memcpy(&new_seed, data.GetData() + 4, sizeof(uint32_t));
130 seed++;
131 if( seed == new_seed || new_seed == 0 ) {
132 ddout("IPModem: Password accepted.\n");
134 #if SHA_DIGEST_LENGTH < SB_IPMODEM_SESSION_KEY_LENGTH
135 #error Session key field must be smaller than SHA digest
136 #endif
137 // Create session key - last 8 bytes of the password hash
138 memcpy(&m_session_key[0],
139 pwdigest + SHA_DIGEST_LENGTH - sizeof(m_session_key),
140 sizeof(m_session_key));
142 // blank password hashes as we don't need these anymore
143 memset(pwdigest, 0, sizeof(pwdigest));
144 memset(prefixedhash, 0, sizeof(prefixedhash));
145 return true;
147 else {
148 ddout("IPModem: Invalid password.\n" << data);
149 throw BadPassword(_("Password rejected by device."), data.GetData()[8], false);
152 // Unknown packet
153 ddout("IPModem: Error unknown packet.\n" << data);
154 return false;
157 //////////////////////////////////////////////////////////////////////////////
158 // protected API / static functions
160 void *IpModem::DataReadThread(void *userptr)
162 IpModem *ipmodem = (IpModem*) userptr;
164 int read_ep = ipmodem->m_con.GetProbeResult().m_epModem.read;
165 Data data;
167 while( ipmodem->m_continue_reading ) {
169 try {
171 ipmodem->m_dev.BulkRead(read_ep, data, 5000);
173 // is it a special code?
174 if( data.GetSize() > 4 &&
175 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag)) == 0 ) {
176 // log, then drop it on the floor for now
177 ddout("IPModem: Special packet:\n" << data);
178 continue;
181 // call callback if available
182 if( ipmodem->m_callback ) {
183 (*ipmodem->m_callback)(ipmodem->m_callback_context,
184 data.GetData(),
185 data.GetSize());
187 // else {
188 // // append data to readCache
189 // FIXME;
190 // }
193 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
194 // do nothing on timeouts
195 ddout("IPModem: Timeout in DataReadThread!");
197 catch( std::exception &e ) {
198 eout(_("Exception in IpModem::DataReadThread: ") << e.what());
202 return 0;
205 //////////////////////////////////////////////////////////////////////////////
206 // public API
208 void IpModem::Open(const char *password)
210 int read_ep = m_con.GetProbeResult().m_epModem.read;
211 int write_ep = m_con.GetProbeResult().m_epModem.write;
212 unsigned char response[28];
213 uint32_t seed;
214 Data data;
216 // check that we have endpoints for the modem
217 const Usb::EndpointPair &pair = m_con.GetProbeResult().m_epModem;
218 if( !pair.IsComplete() ) {
219 std::ostringstream oss;
220 oss << _("IP Modem not supported by this device: ")
221 << "read: " << std::hex << (unsigned int) pair.read
222 << " write: " << std::hex << (unsigned int) pair.write
223 << " type: " << std::hex << (unsigned int) pair.type;
224 eout(oss.str());
225 throw Barry::Error(oss.str());
228 // clear halt when starting out only if needed
229 if( m_con.GetProbeResult().m_needClearHalt ) {
230 m_dev.ClearHalt(pair.read);
231 m_dev.ClearHalt(pair.write);
234 // Send stop command
235 ddout("IPModem: Sending Stop Response:\n");
236 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
237 try {
238 m_dev.BulkRead(read_ep, data, 500);
239 ddout("IPModem: Stop Response Packet:\n" << data);
241 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
242 // do nothing on timeouts
243 ddout("IPModem: Stop Response Timeout");
246 // Send start commands to figure out if the device needs a password.
247 ddout("IPModem: Sending Start Response:\n");
248 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
249 m_dev.BulkRead(read_ep, data, 5000);
250 ddout("IPModem: Start Response Packet:\n" << data);
252 // 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
253 if( data.GetSize() >= 9 && data.GetData()[0] == 0x02 &&
254 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag))== 0 ) {
255 // Got a password request packet
256 ddout("IPModem: Password request packet:\n" << data);
258 // Check how many retries are left
259 if( data.GetData()[8] < BARRY_MIN_PASSWORD_TRIES ) {
260 throw BadPassword(string_vprintf(_("Fewer than %d password tries remaining in device. Refusing to proceed, to avoid device zapping itself. Use a Windows client, or re-cradle the device."), BARRY_MIN_PASSWORD_TRIES),
261 data.GetData()[8],
262 true);
264 memcpy(&seed, data.GetData() + 4, sizeof(seed));
265 // Send password
266 if( !SendPassword(password, seed) ) {
267 throw Barry::Error(_("IpModem: Error sending password."));
270 // Re-send "start" packet
271 ddout("IPModem: Re-sending Start Response:\n");
272 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
273 try {
274 m_dev.BulkRead(read_ep, data);
275 ddout("IPModem: Start Response Packet:\n" << data);
277 catch( Usb::Timeout &to ) {
278 // do nothing on timeouts
279 ddout("IPModem: Start Response Timeout");
283 // send packet with the session_key
284 unsigned char response_header[] = { 0x00, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0xc2, 1, 0 };
285 memcpy(&response[0], response_header, sizeof(response_header));
286 memcpy(&response[16], m_session_key, sizeof(m_session_key));
287 memcpy(&response[24], special_flag, sizeof(special_flag));
288 ddout("IPModem: Sending Session key:\n");
289 m_dev.BulkWrite(write_ep, response, sizeof(response));
290 if( data.GetSize() >= 16 ) {
291 switch(data.GetData()[0])
293 case 0x00: // Null packet
294 break;
296 case 0x02: // password seed received
297 memcpy(&seed, data.GetData() + 4, sizeof(uint32_t));
298 if( !SendPassword( password, seed ) ) {
299 throw Barry::Error(_("IpModem: Error sending password."));
301 break;
302 case 0x04: // command accepted
303 break;
305 default: // ???
306 ddout("IPModem: Unknown response.\n");
307 break;
311 // see if the modem will respond to commands
312 const char modem_command[] = { "AT\r" };
313 m_dev.BulkWrite(write_ep, modem_command, strlen(modem_command));
314 m_dev.BulkRead(read_ep, data);
315 ddout("IPModem: Test command response.\n" << data);
316 if( data.GetSize() >= 1 ) {
317 switch(data.GetData()[0])
319 case 0x00: // Null packet
320 try {
321 m_dev.BulkRead(read_ep, data, 5000);
322 ddout("IPModem: AT Response Packet:\n" << data);
324 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
325 // do nothing on timeouts
326 ddout("IPModem: AT Response Timeout");
328 break;
330 case 0x02: // password seed received
331 if( !password || strlen(password) == 0 ) {
332 throw BadPassword(_("This device requested a password."),
333 data.GetSize() >= 9 ? data.GetData()[8] : 0, false);
335 else { // added for the Storm 9000
336 memcpy(&seed, data.GetData() + 4, sizeof(seed));
337 if( !SendPassword( password, seed ) ) {
338 throw Barry::Error(_("IpModem: Error sending password."));
341 break;
342 case 0x04: // command accepted
343 break;
345 case 0x07: // device is password protected?
346 throw BadPassword(_("This device requires a password."), 0, false);
348 default: // ???
349 ddout("IPModem: Unknown AT command response.\n");
350 // treat this unknown data as a serial response
351 if( m_callback ) {
352 (*m_callback)(m_callback_context,
353 data.GetData(),
354 data.GetSize());
356 break;
359 ddout("IPModem: Modem Ready.\n");
361 // spawn read thread
362 m_continue_reading = true;
363 int ret = pthread_create(&m_modem_read_thread, NULL, &IpModem::DataReadThread, this);
364 if( ret ) {
365 m_continue_reading = false;
366 throw Barry::ErrnoError(_("IpModem: Error creating USB read thread."), ret);
370 void IpModem::Write(const Data &data, int timeout)
372 if( data.GetSize() == 0 )
373 return; // nothing to do
375 // according to Rick Scott the m_filter is not needed with the ip modem
376 // but with the 8320 with Rogers, it doesn't seem to connect without it
377 // If this is a performance problem, perhaps make this a runtime
378 // option.
379 m_dev.BulkWrite(m_con.GetProbeResult().m_epModem.write,
380 m_filter.Write(data), timeout);
383 void IpModem::Close()
385 // This is the terminate connection sequence
386 // that resets the modem so we can re-connect
387 // without unpluging the USB cable or reseting
388 // the whole device.
389 // This works on a BB 8703e a with password. other BB's??
390 unsigned char end[28];
391 int read_ep = m_con.GetProbeResult().m_epModem.read;
392 int write_ep = m_con.GetProbeResult().m_epModem.write;
393 Data data;
395 //0 0 0 0 b0 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
396 ddout("IpModem: Closing connection.");
397 memset(end, 0, sizeof(end));
398 end[4] = 0xb0;
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 20 0 0 0 3 0 0 0 0 c2 1 0 + session_key + special_flag
406 memset(end, 0, sizeof(end));
407 end[4] = 0x20;
408 end[8] = 0x03;
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));
415 //0 0 0 0 30 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
416 // The session_key is set to 0x0's when there is no password.
417 memset(end, 0, sizeof(end));
418 end[4] = 0x30;
419 end[13] = 0xc2;
420 end[14] = 0x01;
421 memcpy(&end[16], m_session_key, sizeof(m_session_key));
422 memcpy(&end[24], special_flag, sizeof(special_flag));
423 m_dev.BulkWrite(write_ep, end, sizeof(end));
424 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
426 // stop the read thread
427 if( m_continue_reading ) {
428 m_continue_reading = false;
429 pthread_join(m_modem_read_thread, NULL);
431 else {
432 // otherwise, drain the last read
433 try {
434 m_dev.BulkRead(read_ep, data, 5000);
435 ddout("IPModem: Close read packet:\n" << data);
437 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
438 // do nothing on timeouts
439 ddout("IPModem: Close Read Timeout");
443 ddout("IPmodem: Closed!");
447 }} // namespace Barry::Mode