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