├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable
│ │ │ │ ├── side_nav_bar.xml
│ │ │ │ ├── ic_menu_send.xml
│ │ │ │ ├── ic_menu_slideshow.xml
│ │ │ │ ├── ic_menu_gallery.xml
│ │ │ │ ├── ic_menu_manage.xml
│ │ │ │ ├── ic_menu_camera.xml
│ │ │ │ ├── ic_menu_share.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── menu
│ │ │ │ ├── main.xml
│ │ │ │ └── activity_main_drawer.xml
│ │ │ ├── xml
│ │ │ │ └── nfc_tech_filter.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── fragment_erase.xml
│ │ │ │ ├── content_main.xml
│ │ │ │ ├── app_bar_main.xml
│ │ │ │ ├── fragment_info.xml
│ │ │ │ ├── fragment_dump.xml
│ │ │ │ ├── nav_header_main.xml
│ │ │ │ └── fragment_program.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── kk4vcz
│ │ │ │ └── goodv
│ │ │ │ ├── NfcRF430Handler.java
│ │ │ │ ├── NfcRF430TAGIT.java
│ │ │ │ ├── GoodVUtil.java
│ │ │ │ ├── NfcRF430Generic.java
│ │ │ │ ├── NfcRF430NXPIcodeSli.java
│ │ │ │ ├── ProgramFragment.java
│ │ │ │ ├── EraseFragment.java
│ │ │ │ ├── InfoFragment.java
│ │ │ │ ├── DumpFragment.java
│ │ │ │ ├── NfcRF430FRL.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── NfcRF430TAL.java
│ │ │ │ └── NfcRF430.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── kk4vcz
│ │ │ └── goodv
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── kk4vcz
│ │ └── goodv
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── encodings.xml
├── vcs.xml
├── misc.xml
├── runConfigurations.xml
├── gradle.xml
├── jarRepositories.xml
└── codeStyles
│ └── Project.xml
├── .gitignore
├── Makefile
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/travisgoodspeed/GoodV/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/NfcRF430Handler.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | /* The multiple activities of the
4 | */
5 |
6 | public interface NfcRF430Handler {
7 | void tagTapped(NfcRF430 tag);
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | *~
15 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jun 25 15:47:05 EDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | help:
3 | @echo "You probably want to run 'make clean assemble install'."
4 | @echo "Install Android Studio and Gradle first."
5 |
6 | .PHONY: clean build assemble install
7 |
8 | clean:
9 | ./gradlew clean
10 | build:
11 | ./gradlew build
12 | assemble:
13 | ./gradlew assemble
14 | install:
15 | ./gradlew installDebug
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/side_nav_bar.xml:
--------------------------------------------------------------------------------
1 |
3 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_send.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 8dp
6 | 176dp
7 | 16dp
8 |
--------------------------------------------------------------------------------
/app/src/test/java/com/kk4vcz/goodv/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_slideshow.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_gallery.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_manage.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/NfcRF430TAGIT.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.nfc.Tag;
4 | import android.util.Log;
5 |
6 | import java.io.IOException;
7 |
8 | public class NfcRF430TAGIT extends NfcRF430 {
9 | public NfcRF430TAGIT(Tag tag) {
10 | super(tag);
11 |
12 | variant = "TAGIT";
13 | blocklen = 4;
14 | baseadr = 0;
15 | blockcount = 0x40;
16 | }
17 |
18 | @Override
19 | public void erase() throws IOException {
20 | Log.v("GoodV", String.format("Eeeek, how do I erase a TagIT tag?"));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_camera.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/GoodVUtil.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | public class GoodVUtil {
4 |
5 | public static String byteArrayToHex(byte[] a) {
6 | StringBuilder sb = new StringBuilder(a.length * 2);
7 | for(byte b: a)
8 | sb.append(String.format("%02x", b));
9 | return sb.toString();
10 | }
11 | public static String byteArrayToHexExceptFirst(byte[] a) {
12 | StringBuilder sb = new StringBuilder(a.length * 2 - 2);
13 | int i=0;
14 | for(byte b: a) {
15 | if(i==0)
16 | i+=1;
17 | else
18 | sb.append(String.format("%02x", b));
19 | }
20 | return sb.toString();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_share.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | GoodV
3 | Open navigation drawer
4 | Close navigation drawer
5 | Tool for the RF430FRL152H
6 | by Travis Goodspeed
7 | Navigation header
8 | Settings
9 |
10 | Home
11 | Gallery
12 | Slideshow
13 | Tools
14 | Share
15 | Send
16 |
17 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## For more details on how to configure your build environment visit
2 | # http://www.gradle.org/docs/current/userguide/build_environment.html
3 | #
4 | # Specifies the JVM arguments used for the daemon process.
5 | # The setting is particularly useful for tweaking memory settings.
6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
8 | #
9 | # When configured, Gradle will run in incubating parallel mode.
10 | # This option should only be used with decoupled projects. More details, visit
11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
12 | # org.gradle.parallel=true
13 | #Fri Nov 29 13:48:35 EST 2019
14 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
15 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/kk4vcz/goodv/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.kk4vcz.goodv", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/NfcRF430Generic.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.nfc.Tag;
4 | import android.util.Log;
5 |
6 | import java.io.IOException;
7 |
8 |
9 | /* This is generic class for interacting with NFC Type V tags that are not RF430 tags.
10 | It might be useful for implementing TagIT support, or for interacting with tags from
11 | other manufacturers.
12 | */
13 |
14 | public class NfcRF430Generic extends NfcRF430 {
15 | public NfcRF430Generic(Tag tag) {
16 | super(tag);
17 |
18 | variant = String.format("Unknown%02x%02x", tagid[6], tagid[5]);
19 |
20 | //Guessing at the size.
21 | blocklen = 4;
22 | baseadr = 0;
23 | blockcount = 0x10;
24 | }
25 |
26 | @Override
27 | public void erase() throws IOException {
28 | Log.v("GoodV", String.format("Eeeek, how do I erase a generic tag?"));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/nfc_tech_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 | android.nfc.tech.NfcV
20 |
21 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/NfcRF430NXPIcodeSli.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.nfc.Tag;
4 | import android.util.Log;
5 |
6 | import java.io.IOException;
7 |
8 | public class NfcRF430NXPIcodeSli extends NfcRF430 {
9 | public NfcRF430NXPIcodeSli(Tag tag) {
10 | super(tag);
11 |
12 |
13 | }
14 |
15 | @Override
16 | public void connect() throws IOException {
17 | super.connect();
18 |
19 | byte[] rawinfo=getRawInfo();
20 | blockcount=rawinfo[12];
21 | baseadr=0;
22 | blocklen=4;
23 | variant="NXPICODESLI";
24 | }
25 |
26 | @Override
27 | public void erase() throws IOException {
28 | Log.v("GoodV", String.format("Eeeek, how do I erase an SLI tag?"));
29 | }
30 |
31 |
32 | //! Gets the tags info as a user-readable string.
33 | public String getInfo() throws IOException {
34 | String info = super.getInfo();
35 |
36 | info +=
37 | "BLOCKS: " + blockcount + "\n";
38 |
39 | return info;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.kk4vcz.goodv"
7 | minSdkVersion 24
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'com.android.support:appcompat-v7:28.0.0'
24 | implementation 'com.android.support:support-v4:28.0.0'
25 | implementation 'com.android.support:design:28.0.0'
26 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
27 | testImplementation 'junit:junit:4.12'
28 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
29 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_erase.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
16 |
17 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/ProgramFragment.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.util.Log;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 |
12 | public class ProgramFragment extends Fragment implements NfcRF430Handler{
13 | EditText programtext;
14 | Button programbutton;
15 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
16 | Bundle savedInstanceState){
17 | View v=inflater.inflate(R.layout.fragment_program, container, false);
18 |
19 | programtext=v.findViewById(R.id.program_text);
20 | programbutton=v.findViewById(R.id.program_button);
21 |
22 | return v;
23 | }
24 |
25 | @Override
26 | public void tagTapped(NfcRF430 tag) {
27 |
28 | Log.d("GoodV", "Tag tapped to program.");
29 | try {
30 | if(tag.writeTITXT(programtext.getText().toString()))
31 | programbutton.setText("Programmed successfully!");
32 | else
33 | programbutton.setText("Programming error. :(");
34 | }catch(Exception e){
35 | e.printStackTrace();
36 | programbutton.setText("IOException.");
37 | }
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/app_bar_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/EraseFragment.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.os.Bundle;
7 | import android.support.v4.app.Fragment;
8 | import android.util.Log;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.TextView;
14 |
15 | import java.io.IOException;
16 |
17 |
18 | /* This fragment exists to show the tag info.
19 | */
20 |
21 | public class EraseFragment extends Fragment implements NfcRF430Handler {
22 | TextView erasetext;
23 |
24 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
25 | Bundle savedInstanceState){
26 | View v=inflater.inflate(R.layout.fragment_erase, container, false);
27 |
28 | erasetext=v.findViewById(R.id.erasetext);
29 |
30 | return v;
31 | }
32 |
33 | @Override
34 | public void tagTapped(NfcRF430 tag) {
35 | Log.d("GoodV", "Tag tapped to erase.");
36 | try {
37 |
38 | tag.erase();
39 |
40 | //And finally we brag about it.
41 | String info =
42 | "Successfully erased tag "+GoodVUtil.byteArrayToHex(tag.getSerialNumber())+" :))\n\n";
43 |
44 | erasetext.setText(info);
45 | }catch(IOException e){
46 | erasetext.setText("Erase error.");
47 | e.printStackTrace();
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_dump.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
22 |
23 |
29 |
30 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_program.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
18 |
19 |
35 |
36 |
37 |
38 |
39 |
43 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/InfoFragment.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.os.Bundle;
7 | import android.support.v4.app.Fragment;
8 | import android.util.Log;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.TextView;
14 |
15 | import java.io.IOException;
16 |
17 |
18 | /* This fragment exists to show the tag info.
19 | */
20 |
21 | public class InfoFragment extends Fragment implements NfcRF430Handler {
22 | TextView infotext;
23 | Button infoexportbutton;
24 |
25 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
26 | Bundle savedInstanceState){
27 | View v=inflater.inflate(R.layout.fragment_info, container, false);
28 |
29 | infotext=v.findViewById(R.id.infotext);
30 | infoexportbutton=v.findViewById(R.id.infoexportbutton);
31 | infoexportbutton.setOnClickListener(new View.OnClickListener(){
32 | @Override
33 | public void onClick(View view){
34 | // Gets a handle to the clipboard service.
35 | ClipboardManager clipboard = (ClipboardManager)
36 | view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
37 | // Make the clip.
38 | ClipData clip = ClipData.newPlainText("RF430FRL152H Info", infotext.getText());
39 | // Set the clipboard's primary clip.
40 | clipboard.setPrimaryClip(clip);
41 | }
42 | });
43 |
44 | return v;
45 | }
46 |
47 | @Override
48 | public void tagTapped(NfcRF430 tag) {
49 | Log.d("GoodV", "Tag tapped to grab info.");
50 |
51 | try {
52 | infotext.setText(tag.getInfo());
53 | }catch(IOException e){
54 | infotext.setText("Read error.");
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/DumpFragment.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.os.Bundle;
7 | import android.support.v4.app.Fragment;
8 | import android.util.Log;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.TextView;
14 |
15 | import java.io.IOException;
16 |
17 | import static android.support.v4.content.ContextCompat.getSystemService;
18 |
19 |
20 | /* This fragment exists to dump various memories of an RF430 over the NFC connection.
21 | */
22 |
23 | public class DumpFragment extends Fragment implements NfcRF430Handler {
24 | TextView dumptext;
25 | Button dumpexportbutton;
26 |
27 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
28 | Bundle savedInstanceState){
29 | View v=inflater.inflate(R.layout.fragment_dump, container, false);
30 |
31 | dumptext=v.findViewById(R.id.dumptext);
32 | dumpexportbutton=v.findViewById(R.id.dumpexportbutton);
33 | dumpexportbutton.setOnClickListener(new View.OnClickListener(){
34 | @Override
35 | public void onClick(View view){
36 | // Gets a handle to the clipboard service.
37 | ClipboardManager clipboard = (ClipboardManager)
38 | view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
39 | // Make the clip.
40 | ClipData clip = ClipData.newPlainText("RF430FRL152H Dump", dumptext.getText());
41 | // Set the clipboard's primary clip.
42 | clipboard.setPrimaryClip(clip);
43 | }
44 | });
45 |
46 | return v;
47 | }
48 |
49 | @Override
50 | public void tagTapped(NfcRF430 tag) {
51 | Log.d("GoodV", "Tag tapped to dump.");
52 |
53 | /* The RF430 chips take a long while to dump. We basically try to dump each section,
54 | assuming that illegal or unavailable sections will quickly fail and move on to the
55 | next.
56 | */
57 |
58 |
59 | //TODO -- Dumping would be a lot nicer as a background thread.
60 | try {
61 | dumptext.setText(tag.dumpTITXT());
62 | } catch (IOException e) {
63 | dumptext.setText("Read error.");
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Howdy y'all,
2 |
3 | Lately I've been playing around with the RF430FRL152H from Texas
4 | Instruments, but preciously few tools were available for communicating
5 | with it, except through custom hardware or by manually typing
6 | commands. GoodV is an attempt to remedy that, supporting the unique
7 | manufacturer commands of this platform.
8 |
9 | For now, it supports reading and writing of memory, and its wrapper
10 | for Android's NfcV class might be handy in writing other RF430
11 | applications.
12 |
13 | It has partial support for the RF430TAL152H chip found in some medical
14 | devices, written as part of a research project with Axelle Apvrille,
15 | which we presented as [The Inner Guts of a Connected Glucose Sensor
16 | for
17 | Diabetes](https://github.com/cryptax/talks/blob/master/BlackAlps-2019/glucose-blackalps2019.pdf)
18 | at BlackAlps 2019.
19 |
20 | Cheers from Yverdon les Bains,
21 |
22 | --Travis Goodspeed
23 |
24 | ## Prebuilt Releases
25 |
26 | Prebuilt APKs of GoodV for those wanting to use it with RF430 chips
27 | are available in the
28 | [Releases](https://github.com/travisgoodspeed/GoodV/releases) section
29 | of the Github page.
30 |
31 | ## Building in Android Studio
32 |
33 | GoodV is developed with [Android
34 | Studio](https://developer.android.com/studio). Begin by choosing
35 | "Check out project from Version Control", then give
36 | `https://github.com/travisgoodspeed/GoodV` as the URL.
37 |
38 | With a little luck it will simply compile, but there might be a
39 | mismatch of the Gradle version and the IDE's plugin. If the Gradle
40 | sync fails, either try the "Install missing platforms and sync
41 | project" option or update the project's build target to something more
42 | modern. Sometimes it helps to jump forward in fewer target revisions,
43 | rather than trying to go all the way to the latest major release.
44 |
45 | ## Building with the Gradle Wrapper
46 |
47 | While Android Studio is damned handy as an IDE, some of us stubborn
48 | ol' fogeys demand a way to compile code from the command line like a
49 | proper gentleman would. For that, use `./gradlew` on Unix or
50 | `graldew.bat` in Windows.
51 |
52 | You can compile with `./gradlew clean` and then `./gradlew assemble`,
53 | then install with `./gradlew installDebug`. For a full list of
54 | targets run `./gradle tasks`, and for convenience, a `Makefile`
55 | wrapper is also included.
56 |
57 | ## News
58 |
59 | Update Jan 27, 2020 -- RF430TAL152H can be erased and reset (@cryptax)
60 |
61 | We will talk about that at [TROOPERS 2020](https://www.troopers.de/).
62 |
63 | ## Related Projects
64 |
65 | [GoodTag](https://github.com/travisgoodspeed/goodtag) is an open
66 | hardware reference design for the RF430FRL152H, as well as a firmware
67 | development kit for the platform.
68 |
69 | [The GoodTag Wiki](https://github.com/travisgoodspeed/goodtag/wiki)
70 | contains plenty of documentation about the RF430FRL152H and related
71 | chips.
72 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/NfcRF430FRL.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.nfc.Tag;
4 | import android.util.Log;
5 |
6 | import java.io.IOException;
7 |
8 | public class NfcRF430FRL extends NfcRF430 {
9 | public NfcRF430FRL(Tag tag) {
10 | super(tag);
11 |
12 | //Things specific to this tag.
13 | variant = "FRL";
14 | }
15 |
16 |
17 |
18 | @Override
19 | public void connect() throws IOException {
20 | super.connect();
21 |
22 | if (variant.equals("FRL")) {
23 | readF867();
24 | }
25 | }
26 |
27 | //! Erases the tag.
28 | public void erase() throws IOException {
29 | /* So, it turns out that erasing is a trickier problem than we might imagine. The FRAM
30 | is used not just for firmware, but also as nonvolatile data memory memory. So what we
31 | do here is invalidate the RESET vector, the patch table, and disable all sensor readings
32 | while leaving the NFCV stack intact.
33 |
34 | A TXT file of a blank image might be cleaner than successive write() calls.
35 | */
36 |
37 |
38 | //Invalidate the RESET vector.
39 | write(0xFFFE, new byte[]{(byte) 0xFF, (byte) 0xFF});
40 | //Invalidate the patch table.
41 | write(0xFFCE, new byte[]{(byte) 0xFF, (byte) 0xFF});
42 | //8 byte pages, NFCV stack but no sensors.
43 | write(0xF867, new byte[]{(byte) 0x7F});
44 |
45 | Log.v("GoodV", "Erase complete.");
46 | }
47 |
48 | @Override
49 | public String dumpTITXT() throws IOException {
50 | //Read what we can, if we can.
51 | String f867 = readTITXT(0xf867, 1) + "\n";
52 | String fram = readTITXT(baseadr, blocklen * blockcount) + "\n";
53 | String sram = readTITXT(0x1C00, 0x1000) + "\n";
54 |
55 | return f867 + fram + sram + "q";
56 | }
57 |
58 | public byte[] exec(int adr) throws IOException {
59 | /* While we could overwrite the call stack, it is much easier to overwrite the
60 | function call table in early SRAM with a pointer to our function, because we
61 | can only perform writes of 4 or 8 bytes at a time, and the call stack within a
62 | write handler will be quite different from the one in a read handler.
63 |
64 | There are plenty of functions to choose from, and an ideal hook would be one that
65 | won't be missed by normal functions. We'd also prefer to have continuation wherever
66 | possible, so that executing the code doesn't crash our target.
67 |
68 | The function pointer we'll overwrite is at 0x1C5C, pointing to rom_rf13_senderror() at
69 | 0x4FF6. For proper continuation, you can just write two bytes to RF13MTXF and return.
70 | Without proper continuation, an IOException will be thrown in the reply timeout.
71 | To unhook, write 0x4FF6 to 0x1C5C, restoring the original handler.
72 |
73 | As a handy side effect, we return the two bytes that need to be transmitted for
74 | continuation, so you can get a bit of data back from your shellcode.
75 | */
76 |
77 | Log.v("GoodV", String.format("Asked to call shellcode at %04x", adr));
78 |
79 | // First we replace the read error reply handler.
80 | write(0x1C5C, new byte[]{(byte) (adr & 0xFF), (byte) (adr >> 8)});
81 |
82 | // Then we read from an illegal address to trigger an error,
83 | // returning the two bytes of its handler.
84 | byte[] shellcodereturn = transceive(new byte[]{
85 | 0x02, // Flags
86 | (byte) 0xC0, // MFG Raw Read Command
87 | 0x07, // MFG Code
88 | (byte) (0xbe), (byte) (0xba) //16-bit block number, little endian.
89 | });
90 | Log.v("GoodV", "Shellcode returned: " + GoodVUtil.byteArrayToHex(shellcodereturn));
91 |
92 | //And finally, we repair the original handler address, like nothing ever happened.
93 | write(0x1C5C, new byte[]{(byte) (0xf6), (byte) (0x4f)});
94 |
95 | return shellcodereturn;
96 | }
97 |
98 | //! Gets the tags info as a user-readable string.
99 | public String getInfo() throws IOException {
100 | String info = super.getInfo();
101 | info +=
102 | "JTAGLOCK: " + (isJTAGLocked() ? "LOCKED" : "UNLOCKED") + "\n" +
103 | "RESET VEC:" + GoodVUtil.byteArrayToHex(read(0xFFFE, 2)) + "\n";
104 | return info;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.app.PendingIntent;
4 | import android.content.Intent;
5 | import android.nfc.Tag;
6 | import android.nfc.tech.NfcV;
7 | import android.os.Bundle;
8 | import android.support.design.widget.FloatingActionButton;
9 | import android.support.design.widget.Snackbar;
10 | import android.support.v4.app.Fragment;
11 | import android.support.v4.app.FragmentManager;
12 | import android.util.Log;
13 | import android.view.View;
14 | import android.support.v4.view.GravityCompat;
15 | import android.support.v7.app.ActionBarDrawerToggle;
16 | import android.view.MenuItem;
17 | import android.support.design.widget.NavigationView;
18 | import android.support.v4.widget.DrawerLayout;
19 |
20 | import android.support.v7.app.AppCompatActivity;
21 | import android.support.v7.widget.Toolbar;
22 | import android.view.Menu;
23 |
24 | import android.nfc.NdefMessage;
25 | import android.nfc.NfcAdapter;
26 | import android.widget.EditText;
27 |
28 | import java.io.IOException;
29 |
30 |
31 | public class MainActivity extends AppCompatActivity
32 | implements NavigationView.OnNavigationItemSelectedListener {
33 | NfcAdapter mAdapter;
34 |
35 | @Override
36 | protected void onCreate(Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_main);
39 | Toolbar toolbar = findViewById(R.id.toolbar);
40 | setSupportActionBar(toolbar);
41 | FloatingActionButton fab = findViewById(R.id.fab);
42 | fab.setOnClickListener(new View.OnClickListener() {
43 | @Override
44 | public void onClick(View view) {
45 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
46 | .setAction("Action", null).show();
47 | }
48 | });
49 | DrawerLayout drawer = findViewById(R.id.drawer_layout);
50 | NavigationView navigationView = findViewById(R.id.nav_view);
51 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
52 | this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
53 | drawer.addDrawerListener(toggle);
54 | toggle.syncState();
55 | navigationView.setNavigationItemSelectedListener(this);
56 |
57 | //Show the default fragment.
58 | setFragment();
59 | }
60 |
61 | @Override
62 | public void onResume(){
63 | super.onResume();
64 |
65 | /* When we resume, we need to tell Android that while we have focus, no other app should
66 | * interfere with our reading of NFC tags. In onPause(), we'll release that claim.
67 | */
68 |
69 |
70 | PendingIntent intent = PendingIntent.getActivity(this, 0,
71 | new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
72 | NfcAdapter.getDefaultAdapter(this).enableForegroundDispatch(this, intent, null, null);
73 | }
74 |
75 | @Override
76 | public void onPause() {
77 | super.onPause();
78 | if(NfcAdapter.getDefaultAdapter(this)!=null)
79 | NfcAdapter.getDefaultAdapter(this).disableForegroundDispatch(this);
80 | }
81 |
82 | @Override
83 | public void onBackPressed() {
84 | DrawerLayout drawer = findViewById(R.id.drawer_layout);
85 | if (drawer.isDrawerOpen(GravityCompat.START)) {
86 | drawer.closeDrawer(GravityCompat.START);
87 | } else {
88 | super.onBackPressed();
89 | }
90 | }
91 |
92 | @Override
93 | public void onNewIntent(Intent intent){
94 | super.onNewIntent(intent);
95 | String action=intent.getAction();
96 |
97 | if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)){
98 | //The tag is attached to the intent.
99 | Tag mTag=intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
100 | Log.d("TAGID", GoodVUtil.byteArrayToHex(mTag.getId()));
101 |
102 |
103 | //We only connect if there's a fragment waiting to handle it.
104 | if (handler != null) {
105 | NfcRF430 rf430 = NfcRF430.get(mTag);//new NfcRF430(mTag);
106 | try {
107 | rf430.connect();
108 | handler.tagTapped(rf430);
109 | rf430.close();
110 | } catch (IOException e) {
111 | Log.d("FAIL", "NFCV connection died before completion.");
112 | }
113 | }
114 | }else{
115 | //Unknown action type. Maybe we forgot to make a handler?
116 | Log.d("GoodV", "onNewIntent()="+action);
117 | }
118 | }
119 |
120 |
121 | @Override
122 | public boolean onCreateOptionsMenu(Menu menu) {
123 | // Inflate the menu; this adds items to the action bar if it is present.
124 | getMenuInflater().inflate(R.menu.main, menu);
125 | return true;
126 | }
127 |
128 | @Override
129 | public boolean onOptionsItemSelected(MenuItem item) {
130 | // Handle action bar item clicks here. The action bar will
131 | // automatically handle clicks on the Home/Up button, so long
132 | // as you specify a parent activity in AndroidManifest.xml.
133 | int id = item.getItemId();
134 |
135 | //noinspection SimplifiableIfStatement
136 | if (id == R.id.action_settings) {
137 | return true;
138 | }
139 |
140 | return super.onOptionsItemSelected(item);
141 | }
142 |
143 | //This holds the shown fragment.
144 | private Fragment fragment=null;
145 | private Class fragmentClass=InfoFragment.class;
146 | private NfcRF430Handler handler=null;
147 |
148 | @SuppressWarnings("StatementWithEmptyBody")
149 | @Override
150 | public boolean onNavigationItemSelected(MenuItem item) {
151 | // Handle navigation view item clicks here.
152 | int id = item.getItemId();
153 |
154 | //Creates a new fragment
155 |
156 | if (id == R.id.nav_info) {
157 | Log.d("GoodV", "Showing the info activity.");
158 | fragmentClass=InfoFragment.class;
159 | } else if (id == R.id.nav_dump) {
160 | // Handle dumping the firmware
161 | Log.d("GoodV", "Showing the dumping activity.");
162 | fragmentClass=DumpFragment.class;
163 | } else if (id == R.id.nav_program) {
164 | Log.d("GoodV", "Showing the programming activity.");
165 | fragmentClass=ProgramFragment.class;
166 | } else if (id == R.id.nav_erase) {
167 | Log.d("GoodV", "Showing the erase activity.");
168 | fragmentClass=EraseFragment.class;
169 | } else if (id == R.id.nav_share) {
170 |
171 | } else if (id == R.id.nav_send) {
172 |
173 | }
174 |
175 |
176 | setFragment();
177 |
178 | DrawerLayout drawer = findViewById(R.id.drawer_layout);
179 | drawer.closeDrawer(GravityCompat.START);
180 | return true;
181 | }
182 |
183 | private void setFragment(){
184 | if(fragmentClass!=null) {
185 | try {
186 | fragment = (Fragment) fragmentClass.newInstance();
187 | handler = (NfcRF430Handler) fragment;
188 | } catch (Exception e) {
189 | e.printStackTrace();
190 | }
191 |
192 | //Insert the fragment, replacing what was already there.
193 | FragmentManager fragmentManager = getSupportFragmentManager();
194 | fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit();
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/NfcRF430TAL.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.nfc.Tag;
4 | import android.util.Log;
5 |
6 | import java.io.IOException;
7 | import java.util.Arrays;
8 |
9 | /* This class handles the RF430TAL152H chip found in some commercial Glucose Monitor devices. With
10 | the backdoor password, it is able to read from any address within those devices. Without the
11 | password, it isn't very useful.
12 |
13 | */
14 |
15 | public class NfcRF430TAL extends NfcRF430FRL {
16 | public NfcRF430TAL(Tag tag) {
17 | //Our super is the RF430FRL, so many functions we can inherit without modification.
18 | super(tag);
19 |
20 | //Then we change the details that differ from the FRL.
21 | baseadr = 0xF860; //Base adr is one block lower.
22 | blocklen = 8; //Block length is always 8.
23 | variant = "GCM";
24 | blockcount = 0xF4;
25 | }
26 |
27 | private byte[] password = {
28 | //Secret 4-byte password to unlock the RF430TAL152H devices.
29 | //You can find this by SPI bus sniffing or by reversing the public apps.
30 | (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
31 | };
32 |
33 | //! Unlocks the tag with the A4 command.
34 | public boolean unlock() throws IOException {
35 | byte[] res = transceive(new byte[]{
36 | 0x02, // Flags
37 | (byte) 0xA4, // MFG Unlock Command
38 | 0x07, // MFG Code
39 |
40 | //Secret password.
41 | password[0], password[1], password[2], password[3],
42 | }
43 | );
44 | return (res[0] == 0);
45 | }
46 |
47 | //! Locks the tag with the A2 command.
48 | public boolean lock() throws IOException {
49 | byte[] res = transceive(new byte[]{
50 | 0x02, // Flags
51 | (byte) 0xA2, // MFG Lock Command
52 | 0x07, // MFG Code
53 |
54 | // Secret password.
55 | password[0], password[1], password[2], password[3],
56 | });
57 | return (res[0] == 0);
58 | }
59 |
60 | //! Initializes the sensor tag and begins a 1-hour calibration process with the A0 command.
61 | public boolean calibrate() throws IOException {
62 | byte[] res = transceive(new byte[]{
63 | 0x02, // Flags
64 | (byte) 0xA0, // MFG Calibrate Command
65 | 0x07, // MFG Code
66 |
67 | //Secret password.
68 | password[0], password[1], password[2], password[3],
69 | });
70 | return (res[0] == 0);
71 | }
72 |
73 | //! Don't yet know what this does.
74 | public boolean vendor_e0() throws IOException {
75 | byte[] res = transceive(new byte[]{
76 | 0x02, // Flags
77 | (byte) 0xE0, // MFG Initialize Command
78 | 0x07, // MFG Code
79 | });
80 | return (res[0] == 0);
81 | }
82 |
83 | //! Don't yet know what this does.
84 | public boolean vendor_e1() throws IOException {
85 | byte[] res = transceive(new byte[]{
86 | 0x02, // Flags
87 | (byte) 0xE1, // MFG Initialize Command
88 | 0x07, // MFG Code
89 | });
90 | return (res[0] == 0);
91 | }
92 |
93 | //! Don't yet know what this does.
94 | public boolean vendor_e2() throws IOException {
95 | byte[] res = transceive(new byte[]{
96 | 0x02, // Flags
97 | (byte) 0xE2, // MFG Initialize Command
98 | 0x07, // MFG Code
99 | });
100 | return (res[0] == 0);
101 | }
102 |
103 | //! Reads a block using the backdoor command of the CGM tags.
104 | public byte[] readA3(int adr) throws IOException {
105 | //Log.v("GoodV", String.format("readA3(): Fetching block at 0x%04x.", adr));
106 |
107 | byte[] res = transceive(new byte[]{
108 | 0x02, // Flags
109 | (byte) 0xA3, // backdoor Raw Read Command
110 | 0x07, // MFG Code
111 |
112 | //Secret password.
113 | password[0], password[1], password[2], password[3],
114 |
115 | (byte) (adr & 0xFF), (byte) (adr >> 8), //16-bit address, little endian.
116 | 0x04 // 4 16-bit words is one block.
117 | });
118 | if (res[0] == 0) {
119 | //Log.v("GoodV", String.format("readA3(): "+GoodVUtil.byteArrayToHex(res), adr));
120 | return Arrays.copyOfRange(res, 1, res.length);
121 | }
122 |
123 | return new byte[]{};
124 | }
125 |
126 | //! Erases the tag.
127 | public void erase() throws IOException {
128 | /* In the case of a TAL tag, an erase restores the tag to its factory setting,
129 | so that it can be initialized again.
130 | This will only work if the region and the code haven't been compromised.
131 | If they have been modified, we'd need to ensure their CRC is okay too.
132 | */
133 |
134 | //First we unlock flash memory.
135 | unlock();
136 |
137 | //Activate
138 | String fram = "50 37 B0 32 01 00 02 08 \n"; // block 0
139 | // blocks 1 and 2
140 | for (int i=1; i<3; i++)
141 | fram += "00 00 00 00 00 00 00 00 \n";
142 | // block 3
143 | fram += "62 c2 00 00 00 00 00 00 \n";
144 | // blocks 4 to 0x27
145 | for (int i=4;i<0x28;i++)
146 | fram += "00 00 00 00 00 00 00 00 \n";
147 |
148 | Log.d("GoodV", "erase(): Writing to FRAM: "+fram);
149 | writeTITXT("@F860 \n"+fram+"q");
150 |
151 | lock();
152 |
153 | Log.v("GoodV", "Erase complete, now in stage " + getStageOfLife());
154 | }
155 |
156 | //! Executed shellcode.
157 | @Override
158 | public byte[] exec(int adr) throws IOException {
159 | Log.e("GoodV", "This tag type doesn't yet support shellcode.");
160 | throw new IOException("Unable to execute shellcode.");
161 | }
162 |
163 | @Override
164 | public String dumpTITXT() throws IOException {
165 | String fram = readTITXT(0xf800, 2048) + "\n";
166 | String rom = readTITXT(0x4400, 0x2000) + "\n";
167 | String sram = readTITXT(0x1C00, 0x1000) + "\n";
168 | //Serial number and calibration are here.
169 | String config = readTITXT(0x1a00, 64) + "\n";
170 |
171 | return config + fram + rom + sram + "q";
172 | }
173 |
174 | public String getStageOfLife() throws IOException {
175 | try {
176 | byte [] stage = read(0xf864, 1);
177 | switch (stage[0]) {
178 | case 0x01:
179 | return "To Activate";
180 | case 0x02:
181 | return "Warming up";
182 | case 0x03:
183 | return "Operational";
184 | case 0x05:
185 | return "Expired";
186 | default:
187 | return String.format("%02x", stage[0]);
188 | }
189 |
190 | }catch(ArrayIndexOutOfBoundsException e){
191 | return "FF";
192 | }
193 | }
194 |
195 | public String getIndicator() throws IOException {
196 | try {
197 | byte [] value = read(0xf865, 1);
198 | return String.format("%02x", value[0]);
199 | }
200 | catch(ArrayIndexOutOfBoundsException e){
201 | return "FF";
202 | }
203 | }
204 |
205 | public int getWearTime() throws IOException {
206 | byte [] wearbytes = read(0xf99c, 2);
207 | return (wearbytes[1] << 8) + wearbytes[0];
208 | }
209 |
210 | public byte getTrendIndex() throws IOException {
211 | return read(0xf87a, 1)[0];
212 | }
213 |
214 | public byte getHistoryIndex() throws IOException {
215 | return read(0xf87b, 1)[0];
216 | }
217 |
218 | public String getRegion() throws IOException {
219 | try {
220 | byte[] regionbytes = read(0xf9a2, 2);
221 | switch (regionbytes[1]) {
222 | case 0x01:
223 | return "France";
224 | case 0x08:
225 | return "Israel";
226 | default:
227 | return String.format("%02x %02x", regionbytes[0], regionbytes[1]);
228 | }
229 | }
230 | catch(ArrayIndexOutOfBoundsException e){
231 | return "ERROR";
232 | }
233 | }
234 |
235 | public String getA1Text() throws IOException {
236 | byte[] res = transceive(new byte[]{
237 | 0x02, // Flags
238 | (byte) 0xA1, // MFG Initialize Command
239 | 0x07 // MFG Code
240 | });
241 | return GoodVUtil.byteArrayToHex(res);
242 | }
243 |
244 | //! Gets the tags info as a user-readable string.
245 | public String getInfo() throws IOException {
246 | String info = super.getInfo();
247 | info += "STAGE: " + getStageOfLife() + "\n";
248 | info += "INDICATOR:" + getIndicator() + "\n";
249 | info += "STATE: " + getA1Text() + "\n";
250 | info += "WEAR : " + getWearTime() + " minutes\n";
251 | info += "REGION: " + getRegion() + "\n";
252 | info += "Trend idx:" + getTrendIndex() + "\n";
253 | info += "Hist idx: " + getHistoryIndex() + "\n";
254 | return info;
255 | }
256 |
257 |
258 | //! Reads data from a native address.
259 | public byte[] read(int adr, int len) throws IOException {
260 | Log.d("GoodV", "read(): address=@" + String.format("%04x", adr) + " len=" + len);
261 |
262 | //F867 special address doesn't exist in the TAL chips.
263 |
264 | //This does nothing if page doesn't need to be changed.
265 | setPageForAdr(adr);
266 |
267 | //Unaligned reads are corrected early.
268 | if (adr % blocklen != 0) {
269 | //First we grab the block before our data.
270 | int earlyblockadr = adr - (adr % blocklen);
271 | int earlyblocklen = adr - earlyblockadr + len;
272 |
273 | byte[] data = read(earlyblockadr, earlyblocklen);
274 |
275 | //Then we return just the requested fragment.
276 | try {
277 | return Arrays.copyOfRange(data, adr - earlyblockadr, adr - earlyblockadr + len);
278 | } catch (ArrayIndexOutOfBoundsException e) {
279 | Log.e("GoodV", "Array index failure. Hoping for the best.");
280 | return new byte[]{};
281 | }
282 | }
283 |
284 |
285 | //Native block size is the easiest.
286 | if (len == blocklen) {
287 | //Fancy backdoor command!
288 | return readA3(adr);
289 | }
290 |
291 | //Buffer to fill as we load grab blocks.
292 | byte[] res = new byte[len];
293 |
294 | //Grab blocklen chunks and shuttle them over.
295 | int i = 0;
296 | while (i < len) {
297 | //Grab the chunk.
298 | //Log.v("GoodV", "read(): recursive call with i="+i);
299 | byte[] chunk = read(adr + i, blocklen);
300 | if (chunk.length < blocklen) {
301 | Log.e("GoodV", "read(): failed to read block address=@"
302 | + String.format("%04x", adr + i) + " len=" + blocklen);
303 | return new byte[]{};
304 | }
305 |
306 | //Copy up to blocklen or buffer length bytes over.
307 | for (int j = 0; j < blocklen && i < len; i++, j++) {
308 | res[i] = chunk[j];
309 | }
310 | }
311 |
312 | return res;
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kk4vcz/goodv/NfcRF430.java:
--------------------------------------------------------------------------------
1 | package com.kk4vcz.goodv;
2 |
3 | import android.nfc.Tag;
4 | import android.nfc.TagLostException;
5 | import android.nfc.tech.NfcV;
6 | import android.nfc.tech.TagTechnology;
7 | import android.util.Log;
8 |
9 | import java.io.IOException;
10 | import java.util.Arrays;
11 | import java.util.StringTokenizer;
12 | import java.util.stream.Stream;
13 |
14 | /* This is a convenient wrapper for the NfcV class that adds support for commands found in the
15 | mask ROM of the RF430FRL152H from Texas Instruments. My goal is to provide some higher-level
16 | abstractions than individual requests, while keeping things sufficiently low level that they
17 | can be traced and understood.
18 |
19 | Previously, this was the only class, but I've been refactoring it out into separate handler
20 | classes for the different chips. If you see any janky if(){} clauses, that's why.
21 |
22 | --Travis
23 | */
24 |
25 | public abstract class NfcRF430 implements TagTechnology {
26 | Tag tag;
27 | NfcV nfcv;
28 |
29 | byte[] tagid;
30 | int blocklen = 8; //8 bytes is default, but 4 can be supported by tag firmware.
31 | int page = 0; //in 4-byte mode, there are two pages.
32 | int baseadr = 0xF868; //F868 on stock ROM, F860 on CGM ROM.
33 | int blockcount = 0xF3; //0xF3 blocks on stock, 0xF4 on GCM.
34 | String variant;
35 |
36 | public NfcRF430(Tag tag) {
37 | this.tag = tag;
38 | this.tagid = tag.getId();
39 | this.nfcv = NfcV.get(this.tag);
40 | }
41 |
42 | /* Use this function to get a non-abstract class that's right for your tag. */
43 | public static NfcRF430 get(Tag tag) {
44 | byte[] tagid = tag.getId();
45 | switch (tagid[6]) {
46 | case 0x07: //Texas Instruments is our primary target.
47 | switch (tagid[5]) {
48 | case 0: //Tag-IT
49 | Log.d("GoodV", "TAG-IT");
50 | return new NfcRF430TAGIT(tag);
51 | case (byte) 0xA2: //Stock ROM of RF430FRL152H.
52 | Log.d("GoodV", "RF430FRL");
53 | return new NfcRF430FRL(tag);
54 | case (byte) 0xA0: //GCM ROM of the RF430TAL152H.
55 | Log.d("GoodV", "RF430TAL");
56 | return new NfcRF430TAL(tag);
57 | }
58 |
59 | case 0x04: //NXP tags are the most common.
60 | switch (tagid[5]) {
61 | case 01: //Stand Label IC
62 | return new NfcRF430NXPIcodeSli(tag);
63 | }
64 | }
65 |
66 | //Unknown tag, so we hope for the best.
67 | return new NfcRF430Generic(tag);
68 | }
69 |
70 | @Override
71 | public Tag getTag() {
72 | return this.tag;
73 | }
74 |
75 | @Override
76 | public void connect() throws IOException {
77 | nfcv.connect();
78 | }
79 |
80 | //! Reads the block length, among other things.
81 | public void readF867() throws IOException{
82 | byte f867 = read(0xf867, 1)[0];
83 |
84 | //RF430FRL152H tags can have either 8 byte or 4 byte blocks.
85 | if ((f867 & 1) == 1) {
86 | blocklen = 8;
87 | page = 0;
88 | } else {
89 | blocklen = 4;
90 | //Page bit is inverted.
91 | page = (((f867 >> 1) & 1) == 1) ? 0 : 1;
92 | }
93 | }
94 |
95 | @Override
96 | public void close() throws IOException {
97 | //Close on page 0 for 4-byte devices.
98 | if(blocklen==4 && variant.equals("FRL"))
99 | setPage(0);
100 | nfcv.close();
101 | }
102 |
103 | @Override
104 | public boolean isConnected() {
105 | return nfcv.isConnected();
106 | }
107 |
108 | /* These functions are the lowest level, directly communicating with the tag. */
109 |
110 | public byte[] transceive(byte[] data) throws IOException {
111 | return nfcv.transceive(data);
112 | }
113 |
114 |
115 | //! Converts an address to a 16-bit block.
116 | public int adr2block(int adr) {
117 | /* Different RF430 devices have different mappings of FRAM to blocks.
118 | In the stock firmware, blocks run [F868,FFF8] as blocks [0,F2]. GCM tags
119 | run a different range, from [F860,FFF8] as blocks [0,F3].
120 |
121 | The special address F867 is available on stock ROM, but not GCM ROM.
122 | */
123 |
124 | if (adr >= 0x1C00 && adr < 0x2C00) { ///SRAM
125 | /* Only available on stock rom by the Cx commands.*/
126 | return ((adr - 0x1C00) / blocklen) + 0x600;
127 | } else if (adr >= baseadr && adr < 0x10000) {
128 | // RF430FRL152H devices wrap into the next page after 0xF2,
129 | // so 0xFC34 is fetched from page 1, block 0.
130 |
131 | //Don't forget to set the page number!
132 | int block = ((adr - baseadr) / blocklen);
133 | if (block >= blockcount)
134 | block -= blockcount;
135 | return block;
136 | } else { //No block number.
137 | Log.e("GoodV", "No block number for address: " + adr);
138 | return -1;
139 | }
140 | }
141 |
142 |
143 |
144 | /* These functions are a bit higher level, implementing one command apiece. */
145 |
146 |
147 | //! Reads the system info.
148 | public byte[] getRawInfo() throws IOException {
149 | byte[] res = transceive(new byte[]{
150 | 0x02, // Flags
151 | (byte) 0x2b, // Standard Read Command
152 | });
153 |
154 | /* Example result:
155 | OK ?? Serial Number ????
156 | 00 04 477a010000a207e0 f207
157 | */
158 |
159 | //Return the result.
160 | return res;
161 | }
162 |
163 | //! Reads the serial number.
164 | public byte[] getSerialNumber() throws IOException {
165 | /* Example result:
166 | OK ?? Serial Number ????
167 | 00 04 477a010000a207e0 f207
168 | /This is backward\
169 | */
170 |
171 | //Return the result.
172 | byte[] revserial = Arrays.copyOfRange(getRawInfo(), 2, 2 + 8);
173 |
174 |
175 | //Easiest to just hardcode the reversal.
176 | byte[] serial = new byte[]{
177 | revserial[7],
178 | revserial[6],
179 | revserial[5],
180 | revserial[4],
181 | revserial[3],
182 | revserial[2],
183 | revserial[1],
184 | revserial[0]
185 | };
186 |
187 | return serial;
188 | }
189 |
190 |
191 | //! Reads a block with an 8-bit address. Standard NFC-V command.
192 | public byte[] readBlock8(byte block) throws IOException {
193 | Log.d("GoodV", "readBlock8(): NFC Read command for block 0x" + String.format("%02X", block));
194 | byte[] res = transceive(new byte[]{
195 | 0x02, // Flags
196 | (byte) 0x20, // Standard Read Command
197 | block // 8-bit block number
198 | });
199 |
200 | //Return just the bytes on success, or nothing on failure.
201 | if (res[0] == 0)
202 | return Arrays.copyOfRange(res, 1, res.length);
203 | else
204 | return new byte[]{};
205 | }
206 |
207 | //! Writes a block with an 8-bit address. Standard NFC-V command.
208 | public boolean writeBlock8(byte block, byte[] data) throws IOException {
209 | byte[] res = new byte[]{1}; //Failure by default.
210 |
211 |
212 | if (blocklen == 8)
213 | res=transceive(new byte[]{
214 | 0x02, // Flags. (Docs say option must be set, but that fails for me.)
215 | (byte) 0x21, // Standard Write Command
216 | block, // 8-bit block number
217 | data[0],
218 | data[1],
219 | data[2],
220 | data[3],
221 | data[4],
222 | data[5],
223 | data[6],
224 | data[7]
225 | });
226 | else if (blocklen == 4)
227 | res=transceive(new byte[]{
228 | 0x02, // Flags. (Docs say option must be set, but that fails for me.)
229 | (byte) 0x21, // Standard Write Command
230 | block, // 8-bit block number
231 | data[0],
232 | data[1],
233 | data[2],
234 | data[3]
235 | });
236 |
237 |
238 | if (res[0] == 0)
239 | return true;
240 |
241 | Log.e("GoodV", String.format("Error writing block8 0x%02x", block));
242 | return false;
243 | }
244 |
245 | //! Writes a block with a 16-bit address. Custom vendor command.
246 | public boolean writeBlock16(int block, byte[] data) throws IOException {
247 | if (block < 0x100)
248 | return writeBlock8((byte) block, data);
249 |
250 |
251 | byte[] res = new byte[]{1};
252 |
253 | if (blocklen == 8)
254 | res=transceive(new byte[]{
255 | 0x02, // Flags
256 | (byte) 0xC1, // MFG Raw Write Command
257 | 0x07, // MFG Code
258 | (byte) (block & 0xFF), (byte) (block >> 8), //16-bit block number, little endian.
259 | data[0],
260 | data[1],
261 | data[2],
262 | data[3],
263 | data[4],
264 | data[5],
265 | data[6],
266 | data[7]
267 | });
268 | else if (blocklen == 4)
269 | res=transceive(new byte[]{
270 | 0x02, // Flags
271 | (byte) 0xC1, // MFG Raw Write Command
272 | 0x07, // MFG Code
273 | (byte) (block & 0xFF), (byte) (block >> 8), //16-bit block number, little endian.
274 | data[0],
275 | data[1],
276 | data[2],
277 | data[3]
278 | });
279 |
280 | if (res[0] == 0) {
281 | return true;
282 | }
283 |
284 | Log.e("GoodV", String.format("Error writing block16 0x%04x", block));
285 | return false;
286 | }
287 |
288 | //! Reads a block with a 16-bit address. Custom vendor command.
289 | public byte[] readBlock16(int block) throws IOException {
290 | Log.v("GoodV", String.format("readBlock16(): Fetching block 0x%04x.", block));
291 |
292 | //For compatibility with custom tags, we default to readBlock8
293 | //where compatible.
294 | if (block < 0x100)
295 | return readBlock8((byte) block);
296 |
297 |
298 | byte[] res = transceive(new byte[]{
299 | 0x02, // Flags
300 | (byte) 0xC0, // MFG Raw Read Command
301 | 0x07, // MFG Code
302 | (byte) (block & 0xFF), (byte) (block >> 8) //16-bit block number, little endian.
303 | });
304 |
305 | //Return just the bytes on success, or nothing on failure.
306 | if (res[0] == 0)
307 | return Arrays.copyOfRange(res, 1, res.length);
308 | else
309 | return new byte[]{};
310 | }
311 |
312 | /* Finally, we want some high level functions, so that we don't need to keep
313 | every little detail of the hardware in mind all the time. These will, of course,
314 | call the lower level functions above.
315 | */
316 |
317 |
318 | //! Reads data from a native address.
319 | public byte[] read(int adr, int len) throws IOException {
320 | Log.d("GoodV", "read(): address=@" + String.format("%04x", adr) + " len=" + len);
321 |
322 | //Special addresses are handled differently.
323 | if (adr == 0xf867) {
324 | // This is the Firmware System Control Register, written by backdoor command.
325 | //Log.v("GoodV", "read(): firmware system control register");
326 | byte[] early = readBlock8((byte) 0xFF);
327 | early=Arrays.copyOf(early, len);
328 |
329 | //When they only want one byte, we're already done.
330 | if(len==1)
331 | return early;
332 |
333 | //Concatenate in the rest.
334 | byte[] late = read(adr+1, len-1);
335 | System.arraycopy(late, 0, early, 1, late.length);
336 |
337 | return early;
338 | }
339 |
340 | //This does nothing if page doesn't need to be changed.
341 | setPageForAdr(adr);
342 |
343 | //Unaligned reads are corrected early.
344 | if (adr % blocklen != 0) {
345 | //First we grab the block before our data.
346 | int earlyblockadr = adr - (adr % blocklen);
347 | int earlyblocklen = adr - earlyblockadr + len;
348 |
349 | byte[] data = read(earlyblockadr, earlyblocklen);
350 |
351 | //Then we return just the requested fragment.
352 | try {
353 | return Arrays.copyOfRange(data, adr - earlyblockadr, adr - earlyblockadr + len);
354 | }catch(ArrayIndexOutOfBoundsException e){
355 | Log.e("GoodV", "Array index failure. Hoping for the best.");
356 | return new byte[]{};
357 | }
358 | }
359 |
360 |
361 | //Native block size is the easiest.
362 | if (len == blocklen) {
363 | //Log.v("GoodV", "= blocklen) {
448 | byte[] blockdata = Arrays.copyOfRange(data, i, i + blocklen);
449 | if (!writeBlock16(blockadr, blockdata)) {
450 | Log.e("GoodV", String.format("Error writing %d bytes to 0x%04x: %s",
451 | blockdata.length, blockadr,
452 | GoodVUtil.byteArrayToHex(blockdata)
453 | ));
454 | return false;
455 | }
456 | } else { //Not enough data for the full block, so we'll have to copy it in.
457 | byte[] blockdata = readBlock16(blockadr);
458 | System.arraycopy(data, i, blockdata, 0, data.length % blocklen);
459 |
460 |
461 | if (!writeBlock16(blockadr, blockdata)) {
462 | Log.e("GoodV", String.format("Error writing %d bytes to 0x%04x: %s (unfilled)",
463 | blockdata.length, blockadr,
464 | GoodVUtil.byteArrayToHex(blockdata)
465 | ));
466 | return false;
467 | }
468 | }
469 | }
470 |
471 | return true;
472 | }
473 |
474 | public byte[] exec(int adr) throws IOException {
475 | Log.e("GoodV", "This tag type doesn't yet support shellcode.");
476 | throw new IOException("Unable to execute shellcode.");
477 | }
478 |
479 | //! Returns true if JTAG is locked.
480 | public boolean isJTAGLocked() throws IOException {
481 | byte[] lockstring = this.read(0xFFD0, 4);
482 | Log.v("GoodV", "JTAG Lock String: " + GoodVUtil.byteArrayToHex(lockstring));
483 |
484 |
485 | //FF's and 00's are unlocked.
486 | return !Arrays.equals(lockstring, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF})
487 | && !Arrays.equals(lockstring, new byte[]{0x00, 0x00, 0x00, 0x00});//Any other value is locked.
488 | }
489 |
490 | //! Locks or unlocks JTAG.
491 | public void setJTAGLocked(boolean lockstate) throws IOException {
492 | if(lockstate)
493 | write(0xFFD0, new byte[]{0x55, 0x55, 0x55, 0x55}); //Locked
494 | else
495 | write(0xFFD0, new byte[]{0x00, 0x00, 0x00, 0x00}); //Unlocked
496 | }
497 |
498 |
499 | //! Sets the page number, iff it needs to be changed.
500 | public void setPage(int newpage) throws IOException {
501 | //No pages in 8-byte mode.
502 | if (blocklen == 8) return;
503 | //Don't fix it if it ain't broke.
504 | if (page == newpage) return;
505 | //Can't set page except in stock.
506 | if (!variant.equals("FRL")) return;
507 |
508 | //Flip the bit and write it to memory. Confusingly it's the inverse of the page number.
509 | byte f867 = read(0xf867, 1)[0];
510 |
511 | if (newpage == 0)
512 | f867 |= 2;
513 | else
514 | f867 &= ~2;
515 |
516 | //Write it back.
517 | write(0xf867, new byte[]{f867});
518 |
519 | //Update our internal page number.
520 | page = newpage;
521 | }
522 |
523 | //! Sets the page number to that of an address.
524 | public void setPageForAdr(int adr) throws IOException {
525 | //Pages don't matter in 8-byte mode.
526 | if (blocklen == 8)
527 | return;
528 |
529 | /* Paging only affects Flash memory, where everything before FC34 is in page 0. */
530 | if (adr < 0xFC34)
531 | setPage(0);
532 | else
533 | setPage(1);
534 |
535 | Log.v("GoodV", String.format("Page %d for adr %04x.", page, adr));
536 | }
537 |
538 | /* And some very high level functions, that probably ought to be done outside of this class. */
539 |
540 | //! Dumps a region of memory as an TI TXT format, popular with the MSP430.
541 | public String readTITXT(int adr, int len) throws IOException {
542 | StringBuilder dump = new StringBuilder();
543 | dump.append(String.format("@%04x", adr));
544 |
545 | Log.d("GoodV", "readTITXT: reading address=" + String.format("@%04x", adr) + " len=" + len);
546 | byte[] data = read(adr, len);
547 |
548 | if (data.length != len) {
549 | Log.e("GoodV", String.format("Requested %d bytes but got %d.", len, data.length));
550 | return ""; //Empty string for unreadable regions.
551 | }
552 |
553 | for (int i = 0; i < len; i++) {
554 | if (i % 16 == 0)
555 | dump.append(String.format("\n%02x", data[i]));
556 | else
557 | dump.append(String.format(" %02x", data[i]));
558 | }
559 |
560 |
561 | return dump.toString();
562 | }
563 |
564 | //! Writes a TITXT file to memory.
565 | public boolean writeTITXT(String txt) throws IOException {
566 | /* TI's TXT format is rather poorly defined, but in general each line should be either
567 | (1) an address preceded by @, (2) a line of up to 16 bytes, or (3) the litter q, which
568 | ends the document.
569 |
570 | Parsing this, we can simply forget about linebreaks and treat each item as a word.
571 | Words beginning with @ set the address, words of just two bytes are data bytes in hex,
572 | and the letter q ends the transaction.
573 |
574 | As a safety check, we should probably overwrite the RESET vector with 0xFFFF first
575 | if the total write will touch the IVT, so that a failed write will repair the IVT
576 | rather than leave stale pointers.
577 | */
578 | StringTokenizer tokens = new StringTokenizer(txt);
579 |
580 | //Target address, length, and data.
581 | int adr = 0;
582 | int len = 0;
583 | byte[] data = new byte[0x10000];
584 |
585 | while (tokens.hasMoreTokens()) {
586 | //Grab the next word.
587 | String word = tokens.nextToken();
588 |
589 | if (word.length() == 2) {
590 | //Load new byte into the buffer.
591 | data[len++] = (byte) Integer.parseInt(word, 16);
592 | } else if (word.equalsIgnoreCase("x")) {
593 | // Ending on the 'x' token, so we Execute the last batch of code.
594 | exec(adr);
595 | } else {
596 | //Not a data byte, so we if we have data, we need to flush it.
597 | if (len > 0) {
598 | if (!write(adr, Arrays.copyOf(data, len)))
599 | return false;
600 | }
601 | adr = len = 0;
602 | }
603 |
604 | if (word.equalsIgnoreCase("q")) {
605 | // Ending on the 'q' token, so we're done.
606 | return true;
607 | } else if (word.indexOf('@') == 0) {
608 | // This is an address.
609 | adr = Integer.parseInt(word.substring(1), 16);
610 | }
611 | }
612 |
613 | Log.e("GoodV", "TI-TXT file doesn't end with q.");
614 | return false;
615 | }
616 |
617 | //! Erases the tag.
618 | public abstract void erase() throws IOException;
619 |
620 | //! Dumps the tag as a string.
621 | public String dumpTITXT() throws IOException {
622 | return readTITXT(baseadr, blockcount * blocklen) + "\nq";
623 | }
624 |
625 | //! Gets the tags info as a user-readable string.
626 | public String getInfo() throws IOException {
627 | String info =
628 | "" +
629 | "INFO: " + GoodVUtil.byteArrayToHex(getRawInfo()) + "\n\n" +
630 | "SERIAL: " + GoodVUtil.byteArrayToHex(getSerialNumber()) + "\n" +
631 | "VARIANT: " + variant + "\n" +
632 | "BLOCKLEN: " + blocklen + "\n" +
633 | "PAGE: " + page + "\n";
634 |
635 | return info;
636 | }
637 | }
638 |
--------------------------------------------------------------------------------