lib+tools: updated strings to support i18n translations
[barry.git] / src / m_ipmodem.cc
blob13d74bf69fdcb54aabfa510e5d3e0bbf145e8d45
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 "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 m_dev.BulkRead(read_ep, data);
274 ddout("IPModem: Start Response Packet:\n" << data);
277 // send packet with the session_key
278 unsigned char response_header[] = { 0x00, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0xc2, 1, 0 };
279 memcpy(&response[0], response_header, sizeof(response_header));
280 memcpy(&response[16], m_session_key, sizeof(m_session_key));
281 memcpy(&response[24], special_flag, sizeof(special_flag));
282 ddout("IPModem: Sending Session key:\n");
283 m_dev.BulkWrite(write_ep, response, sizeof(response));
284 if( data.GetSize() >= 16 ) {
285 switch(data.GetData()[0])
287 case 0x00: // Null packet
288 break;
290 case 0x02: // password seed received
291 memcpy(&seed, data.GetData() + 4, sizeof(uint32_t));
292 if( !SendPassword( password, seed ) ) {
293 throw Barry::Error(_("IpModem: Error sending password."));
295 break;
296 case 0x04: // command accepted
297 break;
299 default: // ???
300 ddout("IPModem: Unknown response.\n");
301 break;
305 // see if the modem will respond to commands
306 const char modem_command[] = { "AT\r" };
307 m_dev.BulkWrite(write_ep, modem_command, strlen(modem_command));
308 m_dev.BulkRead(read_ep, data);
309 ddout("IPModem: Test command response.\n" << data);
310 if( data.GetSize() >= 1 ) {
311 switch(data.GetData()[0])
313 case 0x00: // Null packet
314 try {
315 m_dev.BulkRead(read_ep, data, 5000);
316 ddout("IPModem: AT Response Packet:\n" << data);
318 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
319 // do nothing on timeouts
320 ddout("IPModem: AT Response Timeout");
322 break;
324 case 0x02: // password seed received
325 if( !password || strlen(password) == 0 ) {
326 throw BadPassword(_("This device requested a password."),
327 data.GetSize() >= 9 ? data.GetData()[8] : 0, false);
329 else { // added for the Storm 9000
330 memcpy(&seed, data.GetData() + 4, sizeof(seed));
331 if( !SendPassword( password, seed ) ) {
332 throw Barry::Error(_("IpModem: Error sending password."));
335 break;
336 case 0x04: // command accepted
337 break;
339 case 0x07: // device is password protected?
340 throw BadPassword(_("This device requires a password."), 0, false);
342 default: // ???
343 ddout("IPModem: Unknown AT command response.\n");
344 // treat this unknown data as a serial response
345 if( m_callback ) {
346 (*m_callback)(m_callback_context,
347 data.GetData(),
348 data.GetSize());
350 break;
353 ddout("IPModem: Modem Ready.\n");
355 // spawn read thread
356 m_continue_reading = true;
357 int ret = pthread_create(&m_modem_read_thread, NULL, &IpModem::DataReadThread, this);
358 if( ret ) {
359 m_continue_reading = false;
360 throw Barry::ErrnoError(_("IpModem: Error creating USB read thread."), ret);
364 void IpModem::Write(const Data &data, int timeout)
366 if( data.GetSize() == 0 )
367 return; // nothing to do
369 // according to Rick Scott the m_filter is not needed with the ip modem
370 // but with the 8320 with Rogers, it doesn't seem to connect without it
371 // If this is a performance problem, perhaps make this a runtime
372 // option.
373 m_dev.BulkWrite(m_con.GetProbeResult().m_epModem.write,
374 m_filter.Write(data), timeout);
377 void IpModem::Close()
379 // This is the terminate connection sequence
380 // that resets the modem so we can re-connect
381 // without unpluging the USB cable or reseting
382 // the whole device.
383 // This works on a BB 8703e a with password. other BB's??
384 unsigned char end[28];
385 int read_ep = m_con.GetProbeResult().m_epModem.read;
386 int write_ep = m_con.GetProbeResult().m_epModem.write;
387 Data data;
389 //0 0 0 0 b0 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
390 ddout("IpModem: Closing connection.");
391 memset(end, 0, sizeof(end));
392 end[4] = 0xb0;
393 end[13] = 0xc2;
394 end[14] = 0x01;
395 memcpy(&end[16], m_session_key, sizeof(m_session_key));
396 memcpy(&end[24], special_flag, sizeof(special_flag));
397 m_dev.BulkWrite(write_ep, end, sizeof(end));
399 //0 0 0 0 20 0 0 0 3 0 0 0 0 c2 1 0 + session_key + special_flag
400 memset(end, 0, sizeof(end));
401 end[4] = 0x20;
402 end[8] = 0x03;
403 end[13] = 0xc2;
404 end[14] = 0x01;
405 memcpy(&end[16], m_session_key, sizeof(m_session_key));
406 memcpy(&end[24], special_flag, sizeof(special_flag));
407 m_dev.BulkWrite(write_ep, end, sizeof(end));
409 //0 0 0 0 30 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
410 // The session_key is set to 0x0's when there is no password.
411 memset(end, 0, sizeof(end));
412 end[4] = 0x30;
413 end[13] = 0xc2;
414 end[14] = 0x01;
415 memcpy(&end[16], m_session_key, sizeof(m_session_key));
416 memcpy(&end[24], special_flag, sizeof(special_flag));
417 m_dev.BulkWrite(write_ep, end, sizeof(end));
418 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
420 // stop the read thread
421 if( m_continue_reading ) {
422 m_continue_reading = false;
423 pthread_join(m_modem_read_thread, NULL);
425 else {
426 // otherwise, drain the last read
427 try {
428 m_dev.BulkRead(read_ep, data, 5000);
429 ddout("IPModem: Close read packet:\n" << data);
431 catch( Usb::Timeout &DEBUG_ONLY(to) ) {
432 // do nothing on timeouts
433 ddout("IPModem: Close Read Timeout");
437 ddout("IPmodem: Closed!");
441 }} // namespace Barry::Mode