Skip to content

Instantly share code, notes, and snippets.

@mancvso
Created March 8, 2018 09:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mancvso/18751519ded6a21f4623429eab301107 to your computer and use it in GitHub Desktop.
Save mancvso/18751519ded6a21f4623429eab301107 to your computer and use it in GitHub Desktop.
package com.polidea.rxandroidble.sample.example4_characteristic;
import android.bluetooth.BluetoothGattCharacteristic;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.polidea.rxandroidble.RxBleConnection;
import com.polidea.rxandroidble.RxBleDevice;
import com.polidea.rxandroidble.sample.DeviceActivity;
import com.polidea.rxandroidble.sample.Nonce;
import com.polidea.rxandroidble.sample.R;
import com.polidea.rxandroidble.sample.SampleApplication;
import com.polidea.rxandroidble.sample.util.HexString;
import com.polidea.rxandroidble.utils.ConnectionSharingAdapter;
import com.trello.rxlifecycle.components.support.RxAppCompatActivity;
import com.polidea.rxandroidble.sample.LlaveConexion;
import java.util.UUID;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.subjects.PublishSubject;
import static com.trello.rxlifecycle.android.ActivityEvent.PAUSE;
public class CharacteristicOperationExampleActivity extends RxAppCompatActivity {
public static final String EXTRA_CHARACTERISTIC_UUID = "extra_uuid";
@BindView(R.id.connect)
Button connectButton;
@BindView(R.id.read_output)
TextView readOutputView;
@BindView(R.id.read_hex_output)
TextView readHexOutputView;
@BindView(R.id.write_input)
TextView writeInput;
@BindView(R.id.read)
Button readButton;
@BindView(R.id.write)
Button writeButton;
@BindView(R.id.notify)
Button notifyButton;
private UUID characteristicUuid;
private PublishSubject<Void> disconnectTriggerSubject = PublishSubject.create();
private Observable<RxBleConnection> connectionObservable;
private RxBleDevice bleDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_example4);
ButterKnife.bind(this);
String macAddress = getIntent().getStringExtra(DeviceActivity.EXTRA_MAC_ADDRESS);
characteristicUuid = (UUID) getIntent().getSerializableExtra(EXTRA_CHARACTERISTIC_UUID);
bleDevice = SampleApplication.getRxBleClient(this).getBleDevice(macAddress);
connectionObservable = prepareConnectionObservable();
//noinspection ConstantConditions
getSupportActionBar().setSubtitle(getString(R.string.mac_address, macAddress));
}
private Observable<RxBleConnection> prepareConnectionObservable() {
return bleDevice
.establishConnection(false)
.takeUntil(disconnectTriggerSubject)
.compose(bindUntilEvent(PAUSE))
.compose(new ConnectionSharingAdapter());
}
@OnClick(R.id.connect)
public void onConnectToggleClick() {
if (isConnected()) {
triggerDisconnect();
} else {
connectionObservable
.flatMap(RxBleConnection::discoverServices)
.flatMap(rxBleDeviceServices -> rxBleDeviceServices.getCharacteristic(characteristicUuid))
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(() -> connectButton.setText(R.string.connecting))
.subscribe(
characteristic -> {
updateUI(characteristic);
Log.i(getClass().getSimpleName(), "Hey, connection has been established!");
},
this::onConnectionFailure,
this::onConnectionFinished
);
}
}
@OnClick(R.id.read)
public void onReadClick() {
if (isConnected()) {
/*connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(characteristicUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
readOutputView.setText(new String(bytes));
readHexOutputView.setText(HexString.bytesToHex(bytes));
writeInput.setText(HexString.bytesToHex(bytes));
}, this::onReadFailure);*/
// XXX Leer ambos valores para generar la llave
connectionObservable
.flatMap(rxBleConnection ->
Observable.combineLatest(
rxBleConnection.readCharacteristic(UUID.fromString("0000b003-0000-1000-8000-00805f9b34fb")),
rxBleConnection.readCharacteristic(UUID.fromString("0000b004-0000-1000-8000-00805f9b34fb")),
LlaveConexion::new
)
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(llave -> {
Log.d("llave", llave.hexString());
Log.d("llave", llave.toString());
//Log.d("cipher", llave.encryptedKeyHexString());
//verificarNonce(llave, new Nonce());
escribirNonce(llave, new Nonce());
/*readOutputView.setText(llave.concat());
readHexOutputView.setText(llave.hexString());
writeInput.setText(llave.hexString());*/
}, this::onReadFailure);
}
}
private void verificarNonce(LlaveConexion llave, Nonce nonce){
UUID nonceUpdatedCharasteristicUuid = UUID.fromString("0000a003-0000-1000-8000-00805f9b34fb");
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(nonceUpdatedCharasteristicUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
/*readOutputView.setText(new String(bytes));
readHexOutputView.setText(HexString.bytesToHex(bytes));
writeInput.setText(HexString.bytesToHex(bytes));*/
Log.d("nonce?", HexString.bytesToHex(bytes));
if(bytes.length == 1 && bytes[0] == 0x01){
Log.d("nonce", "Válido. Autenticando...");
escribirClave(llave, nonce);
} else {
Log.d("nonce", "Inválido, escribiendo...");
escribirNonce(llave, nonce);
}
}, this::onReadFailure);
}
private void escribirNonce(LlaveConexion llave, Nonce nonce){
UUID nonceCharasteristicUuid = UUID.fromString("0000a002-0000-1000-8000-00805f9b34fb");
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(nonceCharasteristicUuid, nonce.getControlNonce() ))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
bytes -> {
Log.d("nonce", "Nonce escrito. Autenticando...");
Log.d("nonce", HexString.bytesToHex(bytes) + " desde " + nonce.hexString());
verificarNonce(llave, nonce);
},
this::onWriteFailure
);
}
private void escribirClave(LlaveConexion llave, Nonce nonce){
UUID passCharasteristicUuid = UUID.fromString("0000a001-0000-1000-8000-00805f9b34fb");
byte[] encryptedKey = llave.encryptedKey(nonce);
writeInput.setText(HexString.bytesToHex(encryptedKey));
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(passCharasteristicUuid, encryptedKey ) )
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
bytes -> {
Log.d("auth", "Verificando " + HexString.bytesToHex(bytes) + " vs " + HexString.bytesToHex(encryptedKey) );
verificarAutenticacion(llave, nonce);
},
this::onWriteFailure
);
}
private void verificarAutenticacion(LlaveConexion llave, Nonce nonce){
UUID authenticatedCharasteristicUUID = UUID.fromString("0000a004-0000-1000-8000-00805f9b34fb");
if (isConnected()) {
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(authenticatedCharasteristicUUID))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
/*readOutputView.setText(new String(bytes));
readHexOutputView.setText(HexString.bytesToHex(bytes));
writeInput.setText(HexString.bytesToHex(bytes));*/
Log.d("auth", "Resultado: " + HexString.bytesToHex(bytes));
if(bytes.length == 1 && bytes[0] == 0x01){
Toast.makeText(this, "Autenticación correcta", Toast.LENGTH_SHORT);
} else {
Toast.makeText(this, "Autenticación fallida: " + HexString.bytesToHex(bytes), Toast.LENGTH_SHORT);
}
}, this::onReadFailure);
} else {
Log.d("check", "Desconectado :(");
}
}
private void abrirRelay(LlaveConexion llave, Nonce nonce){
UUID relayCharasteristicUuid = UUID.fromString("0000c001-0000-1000-8000-00805f9b34fb");
byte[] openRelay = {1};
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(relayCharasteristicUuid, openRelay ) )
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
bytes -> {
Log.d("relay", HexString.bytesToHex(bytes));
verificarAutenticacion(llave, nonce);
},
this::onWriteFailure
);
}
@OnClick(R.id.write)
public void onWriteClick() {
Log.d("uuid write", characteristicUuid.toString());
if (isConnected()) {
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(characteristicUuid, getInputBytes()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
bytes -> onWriteSuccess(),
this::onWriteFailure
);
}
}
@OnClick(R.id.notify)
public void onNotifyClick() {
if (isConnected()) {
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristicUuid))
.doOnNext(notificationObservable -> runOnUiThread(this::notificationHasBeenSetUp))
.flatMap(notificationObservable -> notificationObservable)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onNotificationReceived, this::onNotificationSetupFailure);
}
}
private boolean isConnected() {
return bleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.CONNECTED;
}
private void onConnectionFailure(Throwable throwable) {
//noinspection ConstantConditions
Snackbar.make(findViewById(R.id.main), "Connection error: " + throwable, Snackbar.LENGTH_SHORT).show();
updateUI(null);
}
private void onConnectionFinished() {
updateUI(null);
}
private void onReadFailure(Throwable throwable) {
//noinspection ConstantConditions
Snackbar.make(findViewById(R.id.main), "Read error: " + throwable, Snackbar.LENGTH_SHORT).show();
}
private void onWriteSuccess() {
//noinspection ConstantConditions
Snackbar.make(findViewById(R.id.main), "Write success", Snackbar.LENGTH_SHORT).show();
}
private void onWriteFailure(Throwable throwable) {
//noinspection ConstantConditions
Snackbar.make(findViewById(R.id.main), "Write error: " + throwable, Snackbar.LENGTH_SHORT).show();
}
private void onNotificationReceived(byte[] bytes) {
//noinspection ConstantConditions
Snackbar.make(findViewById(R.id.main), "Change: " + HexString.bytesToHex(bytes), Snackbar.LENGTH_SHORT).show();
}
private void onNotificationSetupFailure(Throwable throwable) {
//noinspection ConstantConditions
Snackbar.make(findViewById(R.id.main), "Notifications error: " + throwable, Snackbar.LENGTH_SHORT).show();
}
private void notificationHasBeenSetUp() {
//noinspection ConstantConditions
Snackbar.make(findViewById(R.id.main), "Notifications has been set up", Snackbar.LENGTH_SHORT).show();
}
private void triggerDisconnect() {
disconnectTriggerSubject.onNext(null);
}
/**
* This method updates the UI to a proper state.
* @param characteristic a nullable {@link BluetoothGattCharacteristic}. If it is null then UI is assuming a disconnected state.
*/
private void updateUI(BluetoothGattCharacteristic characteristic) {
connectButton.setText(characteristic != null ? R.string.disconnect : R.string.connect);
readButton.setEnabled(hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_READ));
writeButton.setEnabled(hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_WRITE));
notifyButton.setEnabled(hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_NOTIFY));
}
private boolean hasProperty(BluetoothGattCharacteristic characteristic, int property) {
return characteristic != null && (characteristic.getProperties() & property) > 0;
}
private byte[] getInputBytes() {
return HexString.hexToBytes(writeInput.getText().toString());
}
}
package com.polidea.rxandroidble.sample;
import android.util.Log;
import com.polidea.rxandroidble.sample.util.HexString;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.spongycastle.jce.provider.BouncyCastleProvider;
/**
* Created by mancvso on 07-03-18.
*/
public class LlaveConexion {
private byte[] passwordPlain;
private byte[] encryptionKey;
public static final String ENCRYPTION_MODE = "AES/CTR/NoPadding";
//public static final String ENCRYPTION_MODE = "AES/CTR/PKCS5Padding";
//public static final String ENCRYPTION_MODE = "AES/CCM/NoPadding";
public static final String AES = "AES";
public LlaveConexion(byte[] b003, byte[] b004) {
this.passwordPlain = ByteBuffer.allocate(b003.length*2+b004.length*2)
.put(b003).put(b003)
.put(b004).put(b004)
.array();
this.encryptionKey = ByteBuffer.allocate(b004.length*4)
.put(b004).put(b004)
.put(b004).put(b004)
.array();
}
public String hexString(){
return HexString.bytesToHex(this.encryptionKey);
}
@Override
public String toString(){
return new String(this.encryptionKey);
}
public byte[] getKey(){
return this.encryptionKey;
}
public byte[] encryptedKey(Nonce nonce){
try {
Cipher c = Cipher.getInstance(ENCRYPTION_MODE, new BouncyCastleProvider());
SecretKeySpec s = new SecretKeySpec(getKey(), AES);
Log.d("key", HexString.bytesToHex(s.getEncoded()) + " vs " + HexString.bytesToHex(getKey()) );
Log.d("secret", HexString.bytesToHex(this.passwordPlain) + " vs " + HexString.bytesToHex(getKey()) );
c.init(Cipher.ENCRYPT_MODE, s, nonce.getIV() );
return c.doFinal(this.passwordPlain);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch(InvalidKeyException e){
e.printStackTrace();
return null;
} catch(IllegalBlockSizeException e) {
e.printStackTrace();
return null;
} catch(BadPaddingException e){
e.printStackTrace();
return null;
} catch (InvalidAlgorithmParameterException e){
e.printStackTrace();
return null;
}
}
}
package com.polidea.rxandroidble.sample;
import com.polidea.rxandroidble.sample.util.HexString;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.util.Random;
import javax.crypto.spec.IvParameterSpec;
/**
* Created by mancvso on 07-03-18.
*/
public class Nonce {
private byte[] nonce;
private byte[] controlNonce;
private IvParameterSpec iv;
public Nonce(){
byte[] precursor = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0};
byte[] pad = {0x0,0x0,0x0,0x0};
// Llenar precursor con 12 bytes aleatorios
new Random().nextBytes(precursor);
// agregar 4 bytes en ceros al COMIENZO
this.nonce = precursor;
this.controlNonce = ByteBuffer.allocate(pad.length+precursor.length)
.put(pad)
.put(precursor)
.array();
this.iv = new IvParameterSpec(this.nonce);
}
public byte[] getNonce(){
return this.nonce;
}
public byte[] getControlNonce(){
return this.controlNonce;
}
public String hexString(){
return HexString.bytesToHex(this.getNonce());
}
@Override
public String toString(){
return new String(this.nonce);
}
public IvParameterSpec getIV() {
return this.iv;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment