Skip to content
Snippets Groups Projects
Commit 2880c8e9 authored by Lennart Goedhart's avatar Lennart Goedhart
Browse files

Add mechanism to login to CareLink and retrieve HMAC and key for the attached device.

Remove hardcoded keys.
parent fc0b862d
No related branches found
No related tags found
No related merge requests found
Showing
with 1156 additions and 1328 deletions
......@@ -2,5 +2,10 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintRtlHardcoded" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AndroidLintUselessParent" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
</profile>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="appcompat-v7-21.0.3">
<CLASSES>
<root url="file://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3/res" />
<root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="commons-lang3-3.4">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.4/5fe28b9518e58819180a43a850fbc0dd24b7c050/commons-lang3-3.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.4/b49dafc9cfef24c356827f322e773e7c26725dd2/commons-lang3-3.4-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="support-annotations-21.0.3">
<CLASSES>
<root url="jar://$USER_HOME$/Library/Android/sdk/extras/android/m2repository/com/android/support/support-annotations/21.0.3/support-annotations-21.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/Library/Android/sdk/extras/android/m2repository/com/android/support/support-annotations/21.0.3/support-annotations-21.0.3-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
<component name="libraryTable">
<library name="support-v4-21.0.3">
<CLASSES>
<root url="file://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/res" />
<root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/jars/libs/internal_impl-21.0.3.jar!/" />
<root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/Library/Android/sdk/extras/android/m2repository/com/android/support/support-v4/21.0.3/support-v4-21.0.3-sources.jar!/" />
</SOURCES>
</library>
</component>
\ No newline at end of file
......@@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
......
This diff is collapsed.
......@@ -64,14 +64,6 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
......@@ -80,13 +72,22 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/.DS_Store" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
......@@ -108,8 +109,12 @@
</content>
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="appcompat-v7-21.0.3" level="project" />
<orderEntry type="library" exported="" name="mongo-java-driver-3.0.2" level="project" />
<orderEntry type="library" exported="" name="physicaloidlibrary" level="project" />
<orderEntry type="library" exported="" name="commons-lang3-3.4" level="project" />
<orderEntry type="library" exported="" name="support-v4-21.0.3" level="project" />
<orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" />
<orderEntry type="library" exported="" name="slf4j-api-1.7.2" level="project" />
<orderEntry type="library" exported="" name="logback-android-1.1.1-3" level="project" />
</component>
......
......@@ -19,6 +19,8 @@ android {
}
dependencies {
compile 'com.android.support:appcompat-v7:21.+'
compile 'org.apache.commons:commons-lang3:3.4'
compile files('libs/logback-android-1.1.1-3.jar')
compile files('libs/mongo-java-driver-3.0.2.jar')
compile files('libs/physicaloidlibrary.jar')
......
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nightscout.android"
android:versionCode="1"
android:versionName="1.0">
package="com.nightscout.android"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:maxSdkVersion="21"
android:minSdkVersion="15"
android:targetSdkVersion="21" />
<uses-feature android:name="android.hardware.usb.host" />
<uses-sdk android:minSdkVersion="15" android:maxSdkVersion="21" android:targetSdkVersion="21"/>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"></uses-permission>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name">
<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<android:uses-permission android:name="android.permission.READ_PHONE_STATE" />
<android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<android:uses-permission android:name="android.permission.READ_CALL_LOG" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppBaseTheme" >
<!-- I have set screenOrientation to "portrait" to avoid the restart of AsyncTasks when you rotate the phone -->
<activity
android:name=".dexcom.DexcomG4Activity"
android:icon="@drawable/ic_launcher"
android:label="NightScout"
android:launchMode="singleTask"
android:screenOrientation="portrait"
>
<intent-filter android:icon="@drawable/ic_launcher">
<activity
android:name=".dexcom.DexcomG4Activity"
android:icon="@drawable/ic_launcher"
android:label="NightScout"
android:launchMode="singleTask"
android:screenOrientation="portrait" >
<intent-filter android:icon="@drawable/ic_launcher" >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:icon="@drawable/ic_launcher" >
<intent-filter android:icon="@drawable/ic_launcher" >
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
<activity android:name=".settings.SettingsActivity"
android:icon="@drawable/ic_launcher"
android:label="Settings"/>
<service android:icon="@drawable/ic_launcher" android:name=".dexcom.DexcomG4Service" >
<activity
android:name=".settings.SettingsActivity"
android:icon="@drawable/ic_launcher"
android:label="Settings" />
<service
android:name=".dexcom.DexcomG4Service"
android:icon="@drawable/ic_launcher" >
</service>
<!--<service android:icon="@drawable/ic_launcher"
<!--
<service android:icon="@drawable/ic_launcher"
android:label="NightScout" android:name=".medtronic.MedtronicCGMService">
</service>-->
<service android:icon="@drawable/ic_launcher" android:name=".medtronic.service.MedtronicCNLService" >
</service>
<service android:icon="@drawable/ic_launcher" android:process=":nightscoutcnlservice" android:name=".medtronic.service.TestService" >
-->
<service
android:name=".medtronic.service.MedtronicCNLService"
android:icon="@drawable/ic_launcher" >
</service>
<service android:icon="@drawable/ic_launcher"
android:label="NightScout" android:name=".widget.CGMWidgetUpdater">
<service
android:name=".medtronic.service.TestService"
android:icon="@drawable/ic_launcher"
android:process=":nightscoutcnlservice" >
</service>
<service
android:name=".widget.CGMWidgetUpdater"
android:icon="@drawable/ic_launcher"
android:label="NightScout" >
</service>
<receiver android:name=".widget.CGMWidget" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
<receiver android:name=".widget.CGMWidget" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
<activity
android:name=".dexcom.LoginActivity"
android:label="@string/title_activity_login" >
</activity>
</application>
</manifest>
\ No newline at end of file
package com.nightscout.android.dexcom;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Loader;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.nightscout.android.R;
import com.nightscout.android.medtronic.data.CNLConfigContract;
import com.nightscout.android.medtronic.data.CNLConfigDbHelper;
import com.nightscout.android.medtronic.message.MessageUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* A login screen that offers login via username/password.
*/
public class LoginActivity extends Activity implements LoaderCallbacks<Cursor> {
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private GetHmacAndKey mHmacAndKeyTask = null;
// UI references.
private EditText mUsernameView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Set up the login form.
mUsernameView = (EditText) findViewById(R.id.username);
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
Button mUsernameSignInButton = (Button) findViewById(R.id.username_sign_in_button);
mUsernameSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid username, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() {
if (mHmacAndKeyTask != null) {
return;
}
// Reset errors.
mUsernameView.setError(null);
mPasswordView.setError(null);
// Store values at the time of the login attempt.
String username = mUsernameView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid username address.
if (TextUtils.isEmpty(username)) {
mUsernameView.setError(getString(R.string.error_field_required));
focusView = mUsernameView;
cancel = true;
} else if (!isUsernameValid(username)) {
mUsernameView.setError(getString(R.string.error_invalid_username));
focusView = mUsernameView;
cancel = true;
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mHmacAndKeyTask = new GetHmacAndKey(username, password);
mHmacAndKeyTask.execute((Void) null);
}
}
private boolean isUsernameValid(String username) {
return true;
}
private boolean isPasswordValid(String password) {
return true;
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class GetHmacAndKey extends AsyncTask<Void, Void, Boolean> {
private final String mUsername;
private final String mPassword;
GetHmacAndKey(String username, String password ) {
mUsername = username;
mPassword = password;
}
@Override
protected Boolean doInBackground(final Void... params) {
try {
DefaultHttpClient client = new DefaultHttpClient();
HttpPost loginPost = new HttpPost("https://carelink.minimed.eu/patient/j_security_check");
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("j_username", mUsername));
nameValuePairs.add(new BasicNameValuePair("j_password", mPassword));
nameValuePairs.add(new BasicNameValuePair("j_character_encoding", "UTF-8"));
loginPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = client.execute(loginPost);
if (response.getStatusLine().getStatusCode() == 200) {
// Get the HMAC/keys for every serial in the Config database
CNLConfigDbHelper configDbHelper = new CNLConfigDbHelper(getBaseContext());
Cursor cursor = configDbHelper.getAllRows();
while( !cursor.isAfterLast() ) {
String longSerial = cursor.getString(cursor.getColumnIndex(CNLConfigContract.ConfigEntry.COLUMN_NAME_STICK_SERIAL));
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream hmacRequest = new ObjectOutputStream(buffer);
hmacRequest.writeInt(0x1c);
hmacRequest.writeObject(longSerial.replaceAll("\\d+-", ""));
HttpPost hmacPost = new HttpPost("https://carelink.minimed.eu/patient/secure/SnapshotServer/");
hmacPost.setEntity(new ByteArrayEntity(buffer.toByteArray()));
hmacPost.setHeader("Content-type", "application/octet-stream");
response = client.execute(hmacPost);
ByteArrayInputStream inputBuffer = new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));
ObjectInputStream hmacResponse = new ObjectInputStream(inputBuffer);
byte[] hmacBytes = (byte[]) hmacResponse.readObject();
ArrayUtils.reverse(hmacBytes);
String hmac = MessageUtils.byteArrayToHexString(hmacBytes);
buffer.reset();
inputBuffer.reset();
ObjectOutputStream keyRequest = new ObjectOutputStream(buffer);
keyRequest.writeInt(0x1f);
keyRequest.writeObject(longSerial);
HttpPost keyPost = new HttpPost("https://carelink.minimed.eu/patient/secure/SnapshotServer/");
keyPost.setEntity(new ByteArrayEntity(buffer.toByteArray()));
keyPost.setHeader("Content-type", "application/octet-stream");
response = client.execute(keyPost);
inputBuffer = new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));
ObjectInputStream keyResponse = new ObjectInputStream(inputBuffer);
keyResponse.readInt(); // Throw away the first int. Not sure what it does
String key = MessageUtils.byteArrayToHexString((byte[]) keyResponse.readObject());
// TODO - return false if this returns 0? What would we do anyway?
configDbHelper.setHmacAndKey(longSerial, hmac, key);
cursor.moveToNext();
}
return true;
}
} catch (ClientProtocolException e) {
return false;
} catch (IOException e) {
return false;
} catch (ClassNotFoundException e) {
return false;
}
return false;
}
@Override
protected void onPostExecute(final Boolean success) {
mHmacAndKeyTask = null;
showProgress(false);
if (success) {
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
@Override
protected void onCancelled() {
mHmacAndKeyTask = null;
showProgress(false);
}
}
}
\ No newline at end of file
......@@ -19,6 +19,7 @@ import com.nightscout.android.medtronic.message.PumpStatusRequestMessage;
import com.nightscout.android.medtronic.message.PumpStatusResponseMessage;
import com.nightscout.android.medtronic.message.PumpTimeRequestMessage;
import com.nightscout.android.medtronic.message.PumpTimeResponseMessage;
import com.nightscout.android.medtronic.message.ReadInfoResponseMessage;
import com.nightscout.android.medtronic.message.UnexpectedMessageException;
import com.nightscout.android.medtronic.service.MedtronicCNLService;
import com.nightscout.android.upload.Medtronic640gPumpRecord;
......@@ -34,6 +35,8 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by lgoedhart on 24/03/2016.
......@@ -48,12 +51,23 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler {
private static final byte[] RADIO_CHANNELS = {0x14, 0x11, 0x0e, 0x17, 0x1a};
private UsbHidDriver mDevice;
private MedtronicCNLSession mPumpSession = new MedtronicCNLSession();
private String mStickSerial = null;
public MedtronicCNLReader(UsbHidDriver device) {
mDevice = device;
}
public String getStickSerial() {
return mStickSerial;
}
public MedtronicCNLSession getPumpSession() {
return mPumpSession;
}
public byte[] readMessage() throws IOException, TimeoutException {
ByteArrayOutputStream responseMessage = new ByteArrayOutputStream();
......@@ -146,9 +160,11 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler {
if (response1[0] == ASCII.EOT.value) {
// response 1 is the ASTM message
checkControlMessage(response2, ASCII.ENQ.value);
extractStickSerial( new String( response1 ) );
} else {
// response 2 is the ASTM message
checkControlMessage(response1, ASCII.ENQ.value);
extractStickSerial( new String( response2 ) );
}
} catch (TimeoutException e) {
// Terminate comms with the pump, then try again
......@@ -162,12 +178,19 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler {
}
}
private void extractStickSerial( String astmMessage ) {
Pattern pattern = Pattern.compile( ".*?\\^(\\d{4}-\\d{7})\\^.*" );
Matcher matcher = pattern.matcher( astmMessage );
if( matcher.find() ) {
mStickSerial = matcher.group(1);
}
}
public void enterControlMode() throws IOException, TimeoutException, UnexpectedMessageException {
new ContourNextLinkCommandMessage(ASCII.NAK.value).send(this);
checkControlMessage(readMessage(), ASCII.EOT.value);
new ContourNextLinkCommandMessage(ASCII.ENQ.value).send(this);
checkControlMessage(readMessage(), ASCII.ACK.value);
}
public void enterPassthroughMode() throws IOException, TimeoutException, UnexpectedMessageException {
......@@ -185,10 +208,20 @@ public class MedtronicCNLReader implements ContourNextLinkMessageHandler {
readMessage();
}
public void requestReadInfo() throws IOException, TimeoutException {
public void requestReadInfo() throws IOException, TimeoutException, EncryptionException, ChecksumException {
new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.READ_INFO, mPumpSession, null).send(this);
// FIXME - pull the linkMAC and pumpMAC from here. It needs to go into the session.
readMessage();
ContourNextLinkMessage response = ReadInfoResponseMessage.fromBytes(mPumpSession, readMessage());
// FIXME - this needs to go into ReadInfoResponseMessage
ByteBuffer infoBuffer = ByteBuffer.allocate(16);
infoBuffer.order(ByteOrder.BIG_ENDIAN);
infoBuffer.put(response.encode(), 0x21, 16);
long linkMAC = infoBuffer.getLong(0);
long pumpMAC = infoBuffer.getLong(8);
this.getPumpSession().setLinkMAC( linkMAC );
this.getPumpSession().setPumpMAC( pumpMAC );
}
public byte negotiateChannel() throws IOException, ChecksumException, TimeoutException {
......
......@@ -6,13 +6,11 @@ import com.nightscout.android.medtronic.message.MessageUtils;
* Created by lgoedhart on 26/03/2016.
*/
public class MedtronicCNLSession {
// FIXME - Lennart's hard coded key and HMAC
private final static byte[] HMAC = MessageUtils.hexStringToByteArray("e28fe4e5cf3c1eb6d6a2ec5a093093d4f397237dc60b3f2c1ef64f31e32077c4");
private final static byte[] KEY = MessageUtils.hexStringToByteArray("57833334130906a587b7a0437bc28a69");
private byte[] HMAC;
private byte[] key;
// FIXME - Lennart's hard coded serial numbers
private final static long linkMAC = 1055866 + 0x0023F70682000000L;
private final static long pumpMAC = 1057941 + 0x0023F745EE000000L;
private long linkMAC;
private long pumpMAC;
private byte radioChannel;
private int bayerSequenceNumber = 1;
......@@ -22,24 +20,32 @@ public class MedtronicCNLSession {
return HMAC;
}
public static byte[] getKey() {
return KEY;
public byte[] getKey() {
return key;
}
public byte[] getIV() {
byte[] iv = new byte[KEY.length];
System.arraycopy(KEY,0,iv,0,KEY.length);
byte[] iv = new byte[key.length];
System.arraycopy(key,0,iv,0,key.length);
iv[0] = radioChannel;
return iv;
}
public static long getLinkMAC() {
public long getLinkMAC() {
return linkMAC;
}
public static long getPumpMAC() {
public void setLinkMAC( long linkMAC ) {
this.linkMAC = linkMAC;
}
public long getPumpMAC() {
return pumpMAC;
}
public void setPumpMAC( long pumpMAC ) {
this.pumpMAC = pumpMAC;
}
public int getBayerSequenceNumber() {
return bayerSequenceNumber;
}
......@@ -63,4 +69,12 @@ public class MedtronicCNLSession {
public void setRadioChannel(byte radioChannel) {
this.radioChannel = radioChannel;
}
public void setHMAC( byte[] hmac ) {
this.HMAC = hmac;
}
public void setKey( byte[] key ) {
this.key = key;
}
}
package com.nightscout.android.medtronic.data;
import android.provider.BaseColumns;
/**
* Created by lgoedhart on 9/05/2016.
*/
public class CNLConfigContract {
// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
public CNLConfigContract() {}
/* Inner class that defines the table contents */
public static abstract class ConfigEntry implements BaseColumns {
public static final String TABLE_NAME = "config";
public static final String COLUMN_NAME_STICK_SERIAL = "stick_serial";
public static final String COLUMN_NAME_HMAC = "hmac";
public static final String COLUMN_NAME_KEY = "key";
public static final String COLUMN_NAME_LAST_RADIO_CHANNEL = "last_radio_channel";
}
}
package com.nightscout.android.medtronic.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by lgoedhart on 9/05/2016.
*/
public class CNLConfigDbHelper extends SQLiteOpenHelper {
// Database Specific Details
// If you change the database schema, you must increment the database version.
private static final int DATABASE_VERSION = 1;
// DB Name, same is used to name the sqlite DB file
private static final String DATABASE_NAME = "cnl_config.db";
private static final String SQL_CREATE_CONFIG =
"CREATE TABLE " + CNLConfigContract.ConfigEntry.TABLE_NAME + " (" +
CNLConfigContract.ConfigEntry._ID + " INTEGER PRIMARY KEY," +
CNLConfigContract.ConfigEntry.COLUMN_NAME_STICK_SERIAL + " TEXT UNIQUE, " +
CNLConfigContract.ConfigEntry.COLUMN_NAME_HMAC + " TEXT, "+
CNLConfigContract.ConfigEntry.COLUMN_NAME_KEY + " TEXT, " +
CNLConfigContract.ConfigEntry.COLUMN_NAME_LAST_RADIO_CHANNEL + " INTEGER " +
")";
private static final String SQL_DROP_CONFIG =
"DROP TABLE IF EXISTS " + CNLConfigContract.ConfigEntry.TABLE_NAME;
public CNLConfigDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_CONFIG);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// No upgrades yet, so drop and rebuild
db.execSQL(SQL_DROP_CONFIG);
onCreate(db);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
public void insertStickSerial( String stickSerial ) {
SQLiteDatabase configDb = this.getWritableDatabase();
ContentValues insertValues = new ContentValues();
insertValues.put(CNLConfigContract.ConfigEntry.COLUMN_NAME_STICK_SERIAL, stickSerial );
insertValues.put(CNLConfigContract.ConfigEntry.COLUMN_NAME_HMAC, "");
insertValues.put(CNLConfigContract.ConfigEntry.COLUMN_NAME_KEY, "");
insertValues.put(CNLConfigContract.ConfigEntry.COLUMN_NAME_LAST_RADIO_CHANNEL, 0x14 );
configDb.insertWithOnConflict(CNLConfigContract.ConfigEntry.TABLE_NAME, null, insertValues, SQLiteDatabase.CONFLICT_IGNORE);
}
public String getHmac( String stickSerial ){
SQLiteDatabase configDb = this.getWritableDatabase();
Cursor cursor = configDb.query( CNLConfigContract.ConfigEntry.TABLE_NAME,
new String[] { CNLConfigContract.ConfigEntry.COLUMN_NAME_HMAC },
CNLConfigContract.ConfigEntry.COLUMN_NAME_STICK_SERIAL + " = ?", new String[]{ stickSerial }, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String hmac = cursor.getString(cursor.getColumnIndex(CNLConfigContract.ConfigEntry.COLUMN_NAME_HMAC));
cursor.close();
return hmac;
} else {
return null;
}
}
public String getKey( String stickSerial ){
SQLiteDatabase configDb = this.getWritableDatabase();
Cursor cursor = configDb.query( CNLConfigContract.ConfigEntry.TABLE_NAME,
new String[] { CNLConfigContract.ConfigEntry.COLUMN_NAME_KEY },
CNLConfigContract.ConfigEntry.COLUMN_NAME_STICK_SERIAL + " = ?", new String[]{ stickSerial }, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String hmac = cursor.getString(cursor.getColumnIndex(CNLConfigContract.ConfigEntry.COLUMN_NAME_KEY));
cursor.close();
return hmac;
} else {
return null;
}
}
public int setHmacAndKey( String stickSerial, String hmac, String key ) {
SQLiteDatabase configDb = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(CNLConfigContract.ConfigEntry.COLUMN_NAME_HMAC, hmac);
values.put(CNLConfigContract.ConfigEntry.COLUMN_NAME_KEY, key);
// Which row to update, based on the ID
String whereClause = CNLConfigContract.ConfigEntry.COLUMN_NAME_STICK_SERIAL + " = ?";
String[] whereArgs = { stickSerial };
int affectedRows = configDb.update(
CNLConfigContract.ConfigEntry.TABLE_NAME,
values,
whereClause,
whereArgs
);
return affectedRows;
}
public Cursor getAllRows(){
SQLiteDatabase configDb = this.getReadableDatabase();
String where = null;
String whereArgs[] = null;
String groupBy = null;
String having = null;
String order = null;
String limit = null;
Cursor cursor = configDb.query(CNLConfigContract.ConfigEntry.TABLE_NAME, null, where, whereArgs, groupBy, having, order, limit);
if (cursor != null){
cursor.moveToFirst();
}
return cursor;
}
}
......@@ -45,6 +45,14 @@ public class MessageUtils {
return data;
}
public static String byteArrayToHexString(byte[] in) {
final StringBuilder builder = new StringBuilder();
for(byte b : in) {
builder.append(String.format("%02x", b));
}
return builder.toString();
}
public static Date decodeDateTime( long rtc, long offset ) {
TimeZone currentTz = java.util.Calendar.getInstance().getTimeZone();
GregorianCalendar gregorianCalendar = new GregorianCalendar(2000, 0, 1, 0, 0, 0);
......
package com.nightscout.android.medtronic.message;
import com.nightscout.android.medtronic.MedtronicCNLSession;
/**
* Created by lgoedhart on 10/05/2016.
*/
public class ReadInfoResponseMessage extends MedtronicReceiveMessage {
protected ReadInfoResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCNLSession pumpSession, byte[] payload) {
super(commandType, commandAction, pumpSession, payload);
}
public static ContourNextLinkMessage fromBytes(MedtronicCNLSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException {
// TODO - turn this into a factory
ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes);
// TODO - Validate the MessageType
return message;
}
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.BitmapFactory;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
......@@ -21,9 +22,12 @@ import android.util.Log;
import com.nightscout.android.R;
import com.nightscout.android.USB.UsbHidDriver;
import com.nightscout.android.dexcom.DexcomG4Activity;
import com.nightscout.android.dexcom.LoginActivity;
import com.nightscout.android.medtronic.MedtronicCNLReader;
import com.nightscout.android.medtronic.data.CNLConfigDbHelper;
import com.nightscout.android.medtronic.message.ChecksumException;
import com.nightscout.android.medtronic.message.EncryptionException;
import com.nightscout.android.medtronic.message.MessageUtils;
import com.nightscout.android.medtronic.message.UnexpectedMessageException;
import com.nightscout.android.service.AbstractService;
import com.nightscout.android.upload.Medtronic640gPumpRecord;
......@@ -128,8 +132,23 @@ public class MedtronicCNLService extends AbstractService {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
try {
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_STATUS, "Connecting to the Contour CareLink Next..."));
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_STATUS, "Connecting to the Contour Next Link..."));
cnlReader.requestDeviceInfo();
// Is the device already configured?
CNLConfigDbHelper configDbHelper = new CNLConfigDbHelper(mContext);
configDbHelper.insertStickSerial( cnlReader.getStickSerial() );
String hmac = configDbHelper.getHmac( cnlReader.getStickSerial() );
String key = configDbHelper.getKey( cnlReader.getStickSerial() );
if( hmac.equals( "" ) || key.equals("") ) {
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Before you can use the Contour Next Link, you need to register it with the app. Select 'Register USB Stick' from the menu."));
return;
}
cnlReader.getPumpSession().setHMAC( MessageUtils.hexStringToByteArray( hmac ) );
cnlReader.getPumpSession().setKey( MessageUtils.hexStringToByteArray( key ) );
cnlReader.enterControlMode();
cnlReader.enterPassthroughMode();
cnlReader.openConnection();
......@@ -138,7 +157,7 @@ public class MedtronicCNLService extends AbstractService {
if (radioChannel == 0) {
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Could not communicate with the 640g. Are you near the pump?"));
} else {
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_STATUS, String.format("Connected to Contour CareLink Next on channel %d.", (int) radioChannel)));
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_STATUS, String.format("Connected to Contour Next Link on channel %d.", (int) radioChannel)));
cnlReader.beginEHSMSession();
cnlReader.getPumpTime(pumpRecord);
......@@ -152,16 +171,16 @@ public class MedtronicCNLService extends AbstractService {
cnlReader.endControlMode();
} catch (IOException e) {
Log.e(TAG, "Error getting BGLs", e);
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Error connecting to Contour CareLink Next."));
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Error connecting to Contour Next Link."));
} catch (ChecksumException e) {
Log.e(TAG, "Checksum error", e);
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Checksum error getting message from the Contour CareLink Next."));
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Checksum error getting message from the Contour Next Link."));
} catch (EncryptionException e) {
Log.e(TAG, "Encryption exception", e);
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Error decrypting messages from Contour CareLink Next."));
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Error decrypting messages from Contour Next Link."));
} catch (TimeoutException e) {
Log.e(TAG, "Timeout communicating with Contour", e);
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Timeout communicating with the Contour CareLink Next."));
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Timeout communicating with the Contour Next Link."));
} catch (UnexpectedMessageException e) {
Log.e(TAG, "Unexpected Message", e);
send(Message.obtain(null, DexcomG4Activity.DexcomG4ActivityHandler.MSG_ERROR, "Communication Error: " + e.getMessage()));
......
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.nightscout.android.dexcom.LoginActivity">
<!-- Login progress -->
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Please enter your CareLink username and password to retrieve the encryption key for this USB stick. Your username and password will not be stored."
android:id="@+id/textView" />
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/user_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_username"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"/>
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
android:imeActionId="@+id/login"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true"/>
<Button
android:id="@+id/username_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment