Created
March 8, 2018 09:12
-
-
Save mancvso/18751519ded6a21f4623429eab301107 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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