# m_ipmodem.cc

Go to the documentation of this file.

    00001 ///
    00002 /// \file       m_ipmodem.cc
    00003 ///             Mode class for GPRS modem mode (using endpoints on
    00004 ///             modern devices)
    00005 ///
    00006
    00007 /*
    00008     Copyright (C) 2008-2011, Net Direct Inc. (http://www.netdirect.ca/)
    00009
    00010     This program is free software; you can redistribute it and/or modify
    00011     it under the terms of the GNU General Public License as published by
    00012     the Free Software Foundation; either version 2 of the License, or
    00013     (at your option) any later version.
    00014
    00015     This program is distributed in the hope that it will be useful,
    00016     but WITHOUT ANY WARRANTY; without even the implied warranty of
    00017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00018
    00019     See the GNU General Public License in the COPYING file at the
    00020     root directory of this project for more details.
    00021 */
    00022
    00023 #include "m_ipmodem.h"
    00024 #include "controller.h"
    00025 #include "data.h"
    00026 #include "debug.h"
    00027 #include <sstream>
    00028 #include <string.h>
    00029 #include "sha1.h"
    00030
    00031 namespace Barry { namespace Mode {
    00032
    00033 const char special_flag[] = { 0x78, 0x56, 0x34, 0x12 }; // 0x12345678
    00034 const char start[]        = { 0x01, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
    00035 const char pw_start[]     = { 0x01, 0, 0, 0, 1, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
    00036 const char stop[]         = { 0x01, 0, 0, 0, 0, 0, 0, 0, 0x78, 0x56, 0x34, 0x12 };
    00037
    00038 //////////////////////////////////////////////////////////////////////////////
    00039 // Mode::IpModem class
    00040
    00041 IpModem::IpModem(Controller &con,
    00042                 DeviceDataCallback callback,
    00043                 void *callback_context)
    00044         : m_con(con)
    00045         , m_dev(con.m_dev)
    00046         , m_continue_reading(false)
    00047         , m_callback(callback)
    00048         , m_callback_context(callback_context)
    00049 {
    00050         memset(m_session_key, 0, sizeof(m_session_key));
    00051 }
    00052
    00053 IpModem::~IpModem()
    00054 {
    00055         try {
    00056                 Close();
    00057         } catch( std::exception &e ) {
    00058                 dout("Exception caught in IpModem destructor, ignoring: "
    00059                         << e.what());
    00060         }
    00061 }
    00062
    00063 bool IpModem::SendPassword( const char *password, uint32_t seed )
    00064 {
    00065         if( !password || strlen(password) == 0  ) {
    00066                 throw BadPassword("Logic error: No password provided in SendPassword.", 0, false);
    00067         }
    00068
    00069         int read_ep  = m_con.GetProbeResult().m_epModem.read;
    00070         int write_ep = m_con.GetProbeResult().m_epModem.write;
    00071         unsigned char pwdigest[SHA_DIGEST_LENGTH];
    00072         unsigned char prefixedhash[SHA_DIGEST_LENGTH + 4];
    00073         unsigned char pw_response[SHA_DIGEST_LENGTH + 8];
    00074         uint32_t new_seed;
    00075         Data data;
    00076
    00077         if( !password || strlen(password) == 0  ) {
    00078                 throw BadPassword("No password provided.", 0, false);
    00079         }
    00080
    00081         // Build the password hash
    00082         // first, hash the password by itself
    00083         SHA1((unsigned char *) password, strlen(password), pwdigest);
    00084
    00085         // prefix the resulting hash with the provided seed
    00086         memcpy(&prefixedhash[0], &seed, sizeof(uint32_t));
    00087         memcpy(&prefixedhash[4], pwdigest, SHA_DIGEST_LENGTH);
    00088
    00089         // hash again
    00090         SHA1((unsigned char *) prefixedhash, SHA_DIGEST_LENGTH + 4, pwdigest);
    00091
    00092         // Build the response packet
    00093         const char pw_rsphdr[]  = { 0x03, 0x00, 0x00, 0x00 };
    00094         memcpy(&pw_response[0], pw_rsphdr, sizeof(pw_rsphdr));
    00095         memcpy(&pw_response[4], pwdigest, SHA_DIGEST_LENGTH);
    00096         memcpy(&pw_response[24], special_flag, sizeof(special_flag));
    00097
    00098         // Send the password response packet
    00099         m_dev.BulkWrite(write_ep, pw_response, sizeof(pw_response));
    00100         m_dev.BulkRead(read_ep, data);
    00101         ddout("IPModem: Read password response.\n" << data);
    00102
    00103         // Added for the BB Storm 9000's second password request
    00104         if( data.GetSize() >= 16 && data.GetData()[0] == 0x00 ) {
    00105                 try {
    00106                         m_dev.BulkRead(read_ep, data, 500);
    00107                         ddout("IPModem: Null Response Packet:\n" << data);
    00108                 }
    00109                 catch( Usb::Timeout &to ) {
    00110                         // do nothing on timeouts
    00111                         ddout("IPModem: Null Response Timeout");
    00112                 }
    00113         }
    00114
    00115         //
    00116         // check response 04 00 00 00 .......
    00117         // On the 8703e the seed is incremented, retries are reset to 10
    00118         // when the password is accepted.
    00119         //
    00120         // If data.GetData() + 4 is = to the orginal seed +1 or 00 00 00 00
    00121         // then the password was acceppted.
    00122         //
    00123         // When data.GetData() + 4 is not 00 00 00 00 then data.GetData()[8]
    00124         // contains the number of password retrys left.
    00125         //
    00126         if( data.GetSize() >= 9 && data.GetData()[0] == 0x04 ) {
    00127                 memcpy(&new_seed, data.GetData() + 4, sizeof(uint32_t));
    00128                 seed++;
    00129                 if( seed == new_seed || new_seed == 0 ) {
    00130                         ddout("IPModem: Password accepted.\n");
    00131
    00132 #if SHA_DIGEST_LENGTH < SB_IPMODEM_SESSION_KEY_LENGTH
    00133 #error Session key field must be smaller than SHA digest
    00134 #endif
    00135                         // Create session key - last 8 bytes of the password hash
    00136                         memcpy(&m_session_key[0],
    00137                                 pwdigest + SHA_DIGEST_LENGTH - sizeof(m_session_key),
    00138                                 sizeof(m_session_key));
    00139
    00140                         // blank password hashes as we don't need these anymore
    00141                         memset(pwdigest, 0, sizeof(pwdigest));
    00142                         memset(prefixedhash, 0, sizeof(prefixedhash));
    00143                         return true;
    00144                 }
    00145                 else {
    00146                         ddout("IPModem: Invalid password.\n" << data);
    00147                         throw BadPassword("Password rejected by device.", data.GetData()[8], false);
    00148                 }
    00149         }
    00150         // Unknown packet
    00151         ddout("IPModem: Error unknown packet.\n" << data);
    00152         return false;
    00153 }
    00154
    00155 //////////////////////////////////////////////////////////////////////////////
    00156 // protected API / static functions
    00157
    00158 void *IpModem::DataReadThread(void *userptr)
    00159 {
    00160         IpModem *ipmodem = (IpModem*) userptr;
    00161
    00162         int read_ep = ipmodem->m_con.GetProbeResult().m_epModem.read;
    00163         Data data;
    00164
    00165         while( ipmodem->m_continue_reading ) {
    00166
    00167                 try {
    00168
    00169                         ipmodem->m_dev.BulkRead(read_ep, data, 5000);
    00170
    00171                         // is it a special code?
    00172                         if( data.GetSize() > 4 &&
    00173                             memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag)) == 0 ) {
    00174                                 // log, then drop it on the floor for now
    00175                                 ddout("IPModem: Special packet:\n" << data);
    00176                                 continue;
    00177                         }
    00178
    00179                         // call callback if available
    00180                         if( ipmodem->m_callback ) {
    00181                                 (*ipmodem->m_callback)(ipmodem->m_callback_context,
    00182                                         data.GetData(),
    00183                                         data.GetSize());
    00184                         }
    00185 //                      else {
    00186 //                              // append data to readCache
    00187 //                              FIXME;
    00188 //                      }
    00189
    00190                 }
    00191                 catch( Usb::Timeout &to ) {
    00192                         // do nothing on timeouts
    00193                         ddout("IPModem: Timeout in DataReadThread!");
    00194                 }
    00195                 catch( std::exception &e ) {
    00196                         eout("Exception in IpModem::DataReadThread: " << e.what());
    00197                 }
    00198         }
    00199
    00200         return 0;
    00201 }
    00202
    00203 //////////////////////////////////////////////////////////////////////////////
    00204 // public API
    00205
    00206 void IpModem::Open(const char *password)
    00207 {
    00208         int read_ep  = m_con.GetProbeResult().m_epModem.read;
    00209         int write_ep = m_con.GetProbeResult().m_epModem.write;
    00210         unsigned char response[28];
    00211         uint32_t seed;
    00212         Data data;
    00213
    00214         // check that we have endpoints for the modem
    00215         const Usb::EndpointPair &pair = m_con.GetProbeResult().m_epModem;
    00216         if( !pair.IsComplete() ) {
    00217                 std::ostringstream oss;
    00218                 oss << "IP Modem not supported by this device: "
    00219                         << "read: " << std::hex << (unsigned int) pair.read
    00220                         << " write: " << std::hex << (unsigned int) pair.write
    00221                         << " type: " << std::hex << (unsigned int) pair.type;
    00222                 eout(oss.str());
    00223                 throw Barry::Error(oss.str());
    00224         }
    00225
    00226         // clear halt when starting out only if needed
    00227         if( m_con.m_result.m_needClearHalt ) {
    00228                 m_dev.ClearHalt(pair.read);
    00229                 m_dev.ClearHalt(pair.write);
    00230         }
    00231
    00232         // Send stop command
    00233         ddout("IPModem: Sending Stop Response:\n");
    00234         m_dev.BulkWrite(write_ep, stop, sizeof(stop));
    00235         try {
    00236                 m_dev.BulkRead(read_ep, data, 500);
    00237                 ddout("IPModem: Stop Response Packet:\n" << data);
    00238         }
    00239         catch( Usb::Timeout &to ) {
    00240                 // do nothing on timeouts
    00241                 ddout("IPModem: Stop Response Timeout");
    00242         }
    00243
    00244         // Send start commands to figure out if the device needs a password.
    00245         ddout("IPModem: Sending Start Response:\n");
    00246         m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
    00247         m_dev.BulkRead(read_ep, data, 5000);
    00248         ddout("IPModem: Start Response Packet:\n" << data);
    00249
    00250         // 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
    00251         if( data.GetSize() >= 9 && data.GetData()[0] == 0x02  &&
    00252             memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag))== 0 ) {
    00253                 // Got a password request packet
    00254                 ddout("IPModem: Password request packet:\n" << data);
    00255
    00256                 // Check how many retries are left
    00257                 if( data.GetData()[8] < BARRY_MIN_PASSWORD_TRIES ) {
    00258                         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.",
    00259                                 data.GetData()[8],
    00260                                 true);
    00261                 }
    00262                 memcpy(&seed, data.GetData() + 4, sizeof(seed));
    00263                 // Send password
    00264                 if( !SendPassword(password, seed) ) {
    00265                         throw Barry::Error("IpModem: Error sending password.");
    00266                 }
    00267
    00268                 // Re-send "start" packet
    00269                 ddout("IPModem: Re-sending Start Response:\n");
    00270                 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
    00271                 m_dev.BulkRead(read_ep, data);
    00272                 ddout("IPModem: Start Response Packet:\n" << data);
    00273         }
    00274
    00275         // send packet with the session_key
    00276         unsigned char response_header[] = { 0x00, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0xc2, 1, 0 };
    00277         memcpy(&response[0], response_header, sizeof(response_header));
    00278         memcpy(&response[16], m_session_key,  sizeof(m_session_key));
    00279         memcpy(&response[24], special_flag, sizeof(special_flag));
    00280         ddout("IPModem: Sending Session key:\n");
    00281         m_dev.BulkWrite(write_ep, response, sizeof(response));
    00282         if( data.GetSize() >= 16 ) {
    00283                 switch(data.GetData()[0])
    00284                 {
    00285                 case 0x00:      // Null packet
    00286                         break;
    00287
    00288                 case 0x02:      // password seed received
    00289                         memcpy(&seed, data.GetData() + 4, sizeof(uint32_t));
    00290                         if( !SendPassword( password, seed ) ) {
    00291                                 throw Barry::Error("IpModem: Error sending password.");
    00292                         }
    00293                         break;
    00294                 case 0x04:      // command accepted
    00295                         break;
    00296
    00297                 default:        // ???
    00298                         ddout("IPModem: Unknown response.\n");
    00299                         break;
    00300                 }
    00301         }
    00302
    00303         // see if the modem will respond to commands
    00304         const char modem_command[] = { "AT\r" };
    00305         m_dev.BulkWrite(write_ep, modem_command, strlen(modem_command));
    00306         m_dev.BulkRead(read_ep, data);
    00307         ddout("IPModem: Test command response.\n" << data);
    00308         if( data.GetSize() >= 1 ) {
    00309                 switch(data.GetData()[0])
    00310                 {
    00311                 case 0x00:      // Null packet
    00312                         try {
    00313                                 m_dev.BulkRead(read_ep, data, 5000);
    00314                                 ddout("IPModem: AT Response Packet:\n" << data);
    00315                         }
    00316                         catch( Usb::Timeout &to ) {
    00317                                 // do nothing on timeouts
    00318                                 ddout("IPModem: AT Response Timeout");
    00319                         }
    00320                         break;
    00321
    00322                 case 0x02:      // password seed received
    00323                         if( !password || strlen(password) == 0 ) {
    00324                                 throw BadPassword("This device requested a password.",
    00325                                         data.GetSize() >= 9 ? data.GetData()[8] : 0, false);
    00326                         }
    00327                         else {  // added for the Storm 9000
    00328                                 memcpy(&seed, data.GetData() + 4, sizeof(seed));
    00329                                 if( !SendPassword( password, seed ) ) {
    00330                                         throw Barry::Error("IpModem: Error sending password.");
    00331                                 }
    00332                         }
    00333                         break;
    00334                 case 0x04:      // command accepted
    00335                         break;
    00336
    00337                 case 0x07:      // device is password protected?
    00338                         throw BadPassword("This device requires a password.", 0, false);
    00339
    00340                 default:        // ???
    00341                         ddout("IPModem: Unknown AT command response.\n");
    00342                         // treat this unknown data as a serial response
    00343                         if( m_callback ) {
    00344                                 (*m_callback)(m_callback_context,
    00345                                         data.GetData(),
    00346                                         data.GetSize());
    00347                         }
    00348                         break;
    00349                 }
    00350         }
    00351         ddout("IPModem: Modem Ready.\n");
    00352
    00353         // spawn read thread
    00354         m_continue_reading = true;
    00355         int ret = pthread_create(&m_modem_read_thread, NULL, &IpModem::DataReadThread, this);
    00356         if( ret ) {
    00357                 m_continue_reading = false;
    00358                 throw Barry::ErrnoError("IpModem: Error creating USB read thread.", ret);
    00359         }
    00360 }
    00361
    00362 void IpModem::Write(const Data &data, int timeout)
    00363 {
    00364         if( data.GetSize() == 0 )
    00365                 return; // nothing to do
    00366
    00367         // according to Rick Scott the m_filter is not needed with the ip modem
    00368         // but with the 8320 with Rogers, it doesn't seem to connect without it
    00369         // If this is a performance problem, perhaps make this a runtime
    00370         // option.
    00371         m_dev.BulkWrite(m_con.GetProbeResult().m_epModem.write,
    00372                 m_filter.Write(data), timeout);
    00373 }
    00374
    00375 void IpModem::Close()
    00376 {
    00377         // This is the terminate connection sequence
    00378         // that resets the modem so we can re-connect
    00379         // without unpluging the USB cable or reseting
    00380         // the whole device.
    00381         // This works on a BB 8703e a with password. other BB's??
    00382         unsigned char end[28];
    00383         int read_ep  = m_con.GetProbeResult().m_epModem.read;
    00384         int write_ep = m_con.GetProbeResult().m_epModem.write;
    00385         Data data;
    00386
    00387         //0 0 0 0 b0 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
    00388         ddout("IpModem: Closing connection.");
    00389         memset(end, 0, sizeof(end));
    00390         end[4]  = 0xb0;
    00391         end[13] = 0xc2;
    00392         end[14] = 0x01;
    00393         memcpy(&end[16], m_session_key,  sizeof(m_session_key));
    00394         memcpy(&end[24], special_flag, sizeof(special_flag));
    00395         m_dev.BulkWrite(write_ep, end, sizeof(end));
    00396
    00397         //0 0 0 0 20 0 0 0 3 0 0 0 0 c2 1 0 + session_key + special_flag
    00398         memset(end, 0, sizeof(end));
    00399         end[4]  = 0x20;
    00400         end[8]  = 0x03;
    00401         end[13] = 0xc2;
    00402         end[14] = 0x01;
    00403         memcpy(&end[16], m_session_key,  sizeof(m_session_key));
    00404         memcpy(&end[24], special_flag, sizeof(special_flag));
    00405         m_dev.BulkWrite(write_ep, end, sizeof(end));
    00406
    00407         //0 0 0 0 30 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
    00408         // The session_key is set to 0x0's when there is no password.
    00409         memset(end, 0, sizeof(end));
    00410         end[4]  = 0x30;
    00411         end[13] = 0xc2;
    00412         end[14] = 0x01;
    00413         memcpy(&end[16], m_session_key,  sizeof(m_session_key));
    00414         memcpy(&end[24], special_flag, sizeof(special_flag));
    00415         m_dev.BulkWrite(write_ep, end, sizeof(end));
    00416         m_dev.BulkWrite(write_ep, stop, sizeof(stop));
    00417         try {
    00418                 m_dev.BulkRead(read_ep, data, 5000);
    00419                 ddout("IPModem: Close read packet:\n" << data);
    00420         }
    00421         catch( Usb::Timeout &to ) {
    00422                 // do nothing on timeouts
    00423                 ddout("IPModem: Close Read Timeout");
    00424         }
    00425         // stop the read thread
    00426         if( m_continue_reading ) {
    00427                 m_continue_reading = false;
    00428                 pthread_join(m_modem_read_thread, NULL);
    00429         }
    00430         ddout("IPmodem: Closed!");
    00431
    00432 }
    00433
    00434 }} // namespace Barry::Mode
    00435