Killed trailing whitespace
[barry/pauldeden.git] / src / m_ipmodem.cc
blob29b8f856b0ad87bd08dc4210ac8603f194495836
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, 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)
65 if( !password || strlen(password) == 0 ) {
66 throw BadPassword("No password provided.", 0, false);
69 int read_ep = m_con.GetProbeResult().m_epModem.read;
70 int write_ep = m_con.GetProbeResult().m_epModem.write;
71 Data data;
73 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
74 m_dev.BulkRead(read_ep, data);
75 ddout("IPModem read packet.\n" << data);
77 // Need to add checks for other packet types.
78 // 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
79 if( data.GetSize() >= 9 && data.GetData()[0] == 0x02 &&
80 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag))== 0 ) {
81 // Got a password request packet
82 ddout("IPModem password request packet:\n" << data);
84 // Check how many retries are left
85 if( data.GetData()[8] < 6 ) {
86 throw BadPassword("Fewer than 6 password tries "
87 "remaining in device. Refusing to proceed, "
88 "to avoid device zapping itself. Use a "
89 "Windows client, or re-cradle the device.",
90 data.GetData()[8],
91 true);
94 // Build the password hash
95 unsigned char pwdigest[SHA_DIGEST_LENGTH];
96 unsigned char prefixedhash[SHA_DIGEST_LENGTH + 4];
97 unsigned char pw_response[SHA_DIGEST_LENGTH + 8];
98 unsigned char seed[4];
100 // first, hash the password by itself
101 SHA1((unsigned char *) password, strlen(password), pwdigest);
103 // prefix the resulting hash with the provided seed
104 memcpy(&seed[0], data.GetData() + 4, sizeof(uint32_t));
105 memcpy(&prefixedhash[0], &seed, sizeof(uint32_t));
106 memcpy(&prefixedhash[4], pwdigest, SHA_DIGEST_LENGTH);
108 // hash again
109 SHA1((unsigned char *) prefixedhash, SHA_DIGEST_LENGTH + 4, pwdigest);
111 // Build the response packet
112 const char pw_rsphdr[] = { 0x03, 0x00, 0x00, 0x00 };
113 memcpy(&pw_response[0], pw_rsphdr, sizeof(pw_rsphdr));
114 memcpy(&pw_response[4], pwdigest, SHA_DIGEST_LENGTH);
115 memcpy(&pw_response[24], special_flag, sizeof(special_flag));
117 // Send the password response packet
118 m_dev.BulkWrite(write_ep, pw_response, sizeof(pw_response));
119 m_dev.BulkRead(read_ep, data);
120 ddout("IPModem read password response.\n" << data);
122 // check response 04 00 00 00 .......
123 // On the 8703e the seed is incremented, retries are reset to 10 when the password is accepted.
124 // if( data.GetData()[0] == 0x04 && data.GetData()[8] == 0x0a ) {
125 if( data.GetSize() >= 9 && data.GetData()[0] == 0x04 ) {
126 if( memcmp(data.GetData() + 4, seed, sizeof(seed)) == 0 ) {
127 ddout("IPModem invalid password.\n" << data);
128 throw BadPassword("Password rejected by device.", data.GetData()[8], false);
130 ddout("IPModem password accepted.\n");
131 // send "start"? packet
132 m_dev.BulkWrite(write_ep, pw_start, sizeof(pw_start));
134 // send packet with the last 8 bytes of the password hash (session_key?)
135 //unsigned char pw_response[SHA_DIGEST_LENGTH + 8];
136 unsigned char pw_rsphdr[] = { 0x00, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0xc2, 1, 0 };
137 memcpy(&m_session_key[0], pwdigest + 12, sizeof(m_session_key));
138 memcpy(&pw_response[0], pw_rsphdr, sizeof(pw_rsphdr));
139 memcpy(&pw_response[16], pwdigest + 12, 8);
140 //memcpy(&pw_response[16], m_session_key, 8);
141 memcpy(&pw_response[24], special_flag, sizeof(special_flag));
142 m_dev.BulkWrite(write_ep, pw_response, sizeof(pw_response));
144 // blank password hashes as we don't need these anymore
145 memset(pwdigest, 0, sizeof(pwdigest));
146 memset(prefixedhash, 0, sizeof(prefixedhash));
148 // The modem should be ready to accept AT commands
149 return true;
152 // Unknown packet
153 ddout("IPModem Error unknown packet.\n" << data);
155 return false;
158 //////////////////////////////////////////////////////////////////////////////
159 // protected API / static functions
161 void *IpModem::DataReadThread(void *userptr)
163 IpModem *ipmodem = (IpModem*) userptr;
165 int read_ep = ipmodem->m_con.GetProbeResult().m_epModem.read;
166 Data data;
168 while( ipmodem->m_continue_reading ) {
170 try {
172 ipmodem->m_dev.BulkRead(read_ep, data, 5000);
174 // is it a special code?
175 if( data.GetSize() > 4 &&
176 memcmp(data.GetData() + data.GetSize() - 4, special_flag, sizeof(special_flag)) == 0 ) {
177 // log, then drop it on the floor for now
178 ddout("IPModem special packet:\n" << data);
179 continue;
182 // call callback if available
183 if( ipmodem->m_callback ) {
184 (*ipmodem->m_callback)(ipmodem->m_callback_context,
185 data.GetData(),
186 data.GetSize());
188 // else {
189 // // append data to readCache
190 // FIXME;
191 // }
194 catch( Usb::Timeout &to ) {
195 // do nothing on timeouts
196 ddout("Timeout in DataReadThread!");
198 catch( std::exception &e ) {
199 eout("Exception in IpModem::DataReadThread: " << e.what());
203 return 0;
206 //////////////////////////////////////////////////////////////////////////////
207 // public API
209 void IpModem::Open(const char *password)
211 int read_ep = m_con.GetProbeResult().m_epModem.read;
212 int write_ep = m_con.GetProbeResult().m_epModem.write;
213 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 if( !password || strlen(password) == 0 ) {
231 Data block(start, sizeof(start));
232 Write(block);
234 else {
235 if( !SendPassword(password) ) {
236 throw Barry::Error("IpModem:: Error sending password.");
240 // see if the modem will respond to commands
241 const char modem_command[] = { "AT\r" };
242 m_dev.BulkWrite(write_ep, modem_command, sizeof(modem_command));
243 m_dev.BulkRead(read_ep, data);
244 ddout("IPModem:: AT command response.\n" << data);
245 if( data.GetSize() >= 1 ) {
246 switch(data.GetData()[0])
248 case 0x02: // password seed received
249 throw BadPassword("This device requested a password.",
250 data.GetSize() >= 9 ? data.GetData()[8] : 0, false);
252 case 0x04: // command accepted
253 break;
255 case 0x07: // device is password protected?
256 throw BadPassword("This device requires a password.", 0, false);
258 default: // ???
259 ddout("IPModem:: unknown AT command response.\n");
260 break;
264 // spawn read thread
265 m_continue_reading = true;
266 int ret = pthread_create(&m_modem_read_thread, NULL, &IpModem::DataReadThread, this);
267 if( ret ) {
268 m_continue_reading = false;
269 throw Barry::ErrnoError("IpModem:: Error creating USB read thread.", ret);
273 void IpModem::Write(const Data &data, int timeout)
275 if( data.GetSize() == 0 )
276 return; // nothing to do
278 // according to Rick Scott the m_filter is not needed with the ip modem
279 // but with the 8320 with Rogers, it doesn't seem to connect without it
280 // If this is a performance problem, perhaps make this a runtime
281 // option.
282 m_dev.BulkWrite(m_con.GetProbeResult().m_epModem.write,
283 m_filter.Write(data), timeout);
286 void IpModem::Close()
288 // This is the terminate connection sequence
289 // that resets the modem so we can re-connect
290 // without unpluging the USB cable or reseting
291 // the whole device.
292 // This works on a BB 8703e a with password. other BB's??
293 unsigned char end[28];
294 int read_ep = m_con.GetProbeResult().m_epModem.read;
295 int write_ep = m_con.GetProbeResult().m_epModem.write;
296 Data data;
298 //0 0 0 0 b0 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
299 ddout("IpModem:: Closing connection.");
300 memset(end, 0, sizeof(end));
301 end[4] = 0xb0;
302 end[13] = 0xc2;
303 end[14] = 0x01;
304 memcpy(&end[16], m_session_key, sizeof(m_session_key));
305 memcpy(&end[24], special_flag, sizeof(special_flag));
306 m_dev.BulkWrite(write_ep, end, sizeof(end));
308 //0 0 0 0 20 0 0 0 3 0 0 0 0 c2 1 0 + session_key + special_flag
309 memset(end, 0, sizeof(end));
310 end[4] = 0x20;
311 end[8] = 0x03;
312 end[13] = 0xc2;
313 end[14] = 0x01;
314 memcpy(&end[16], m_session_key, sizeof(m_session_key));
315 memcpy(&end[24], special_flag, sizeof(special_flag));
316 m_dev.BulkWrite(write_ep, end, sizeof(end));
318 //0 0 0 0 30 0 0 0 0 0 0 0 0 c2 1 0 + session_key + special_flag
319 // The session_key is set to 0x0's when there is no password.
320 memset(end, 0, sizeof(end));
321 end[4] = 0x30;
322 end[13] = 0xc2;
323 end[14] = 0x01;
324 memcpy(&end[16], m_session_key, sizeof(m_session_key));
325 memcpy(&end[24], special_flag, sizeof(special_flag));
326 m_dev.BulkWrite(write_ep, end, sizeof(end));
327 m_dev.BulkWrite(write_ep, stop, sizeof(stop));
328 try {
329 m_dev.BulkRead(read_ep, data, 5000);
330 ddout("IPModem:: Close read packet:\n" << data);
332 catch( Usb::Timeout &to ) {
333 // do nothing on timeouts
334 ddout("IPModem:: Close Read Timeout");
336 // stop the read thread
337 if( m_continue_reading ) {
338 m_continue_reading = false;
339 pthread_join(m_modem_read_thread, NULL);
341 ddout("IPmodem:: Closed!");
345 }} // namespace Barry::Mode