Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BLE communication refactoring #7

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 55 additions & 48 deletions app/src/main/java/com/fidesmo/ble/client/BlePeripheralService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;

import com.fidesmo.ble.client.apdu.CustomFragmentationProtocol;
import com.fidesmo.ble.client.apdu.CustomPacketDefragmenter;
import com.fidesmo.ble.client.apdu.CustomPacketFragmenter;
import com.fidesmo.ble.client.protocol.FragmentationProtocol;
import com.fidesmo.ble.client.protocol.PacketDefragmenter;
import com.fidesmo.ble.client.protocol.PacketFragmenter;
import com.fidesmo.ble.client.protocol.SimplePacketFragmenter;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
Expand Down Expand Up @@ -67,9 +72,9 @@ public class BlePeripheralService extends Service {

private LinkedList<String> messagesList = new LinkedList<>();

private FragmentationProtocol fragmentationProtocol = SimplePacketFragmenter.factory();
private CustomFragmentationProtocol fragmentationProtocol = CustomPacketFragmenter.factory();
private PacketFragmenter currentResponsePacket;
private PacketDefragmenter currentPacketBuilder;
private CustomPacketDefragmenter currentPacketBuilder = fragmentationProtocol.deframenter();
private int mtu = 512;

private AtomicLong requestId = new AtomicLong(0);
Expand Down Expand Up @@ -213,28 +218,17 @@ public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, i
return ;
}

if (characteristic.getUuid().equals(BleCard.APDU_READ_CHARACTERISTIC_UUID) && currentResponsePacket != null) {

if (!characteristic.getUuid().equals(BleCard.APDU_READ_CHARACTERISTIC_UUID)) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
log("Unsupported characteristics read: " + characteristic.getUuid());
return ;
}

if (currentResponsePacket == null) {
log("No answer ready yet");
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
return;
}

if (currentResponsePacket.hasMoreData()) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, currentResponsePacket.nextFragment());
} else {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, new byte[]{0});
}

if (offset == 0 && currentResponsePacket.hasMoreData()) {
// save Fidesmo's internal packet inside characteristic
characteristic.setValue(currentResponsePacket.nextFragment());
}

if (!currentResponsePacket.hasMoreData()) {
currentResponsePacket = null;
int chunkSize = characteristic.getValue().length - offset;
byte[] chunk = new byte[chunkSize];
System.arraycopy(characteristic.getValue(), offset, chunk, 0, chunkSize);
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, chunk);
}
}

Expand All @@ -249,37 +243,20 @@ public void onCharacteristicWriteRequest(final BluetoothDevice device, final int
", flags: prepared=" + preparedWrite + ", respNeeded=" + responseNeeded + ", offset: " + offset
);

if (responseNeeded) {
BleUtils.retryCall(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[]{0});
}
});
}

if (offset != 0) {
log("Offset is not zero: " + offset);
return;
}

if(APDU_CONVERSATION_FINISHED_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {
finishConversation();
return;
}


if (currentPacketBuilder == null) {
log("Starting APDU request session");
currentPacketBuilder = fragmentationProtocol.deframenter();
if (preparedWrite) {
onWriteBuffer(value);
}else {
onWritePacket(value);
}

currentPacketBuilder.append(value);

if (currentPacketBuilder.complete()) {
log("Packet received");
sendBleAPDUToActivity(currentPacketBuilder.getBuffer());
currentPacketBuilder = null;
if (gattServer != null && responseNeeded) {
// need to give the same values we got as an reply, in order to get next possible part.
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
}
}

Expand All @@ -300,8 +277,10 @@ public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, Blue

@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
log("onExecuteWrite(" + requestId + "), execute: " + execute);
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] {});
onExecuteWriteBuffer(execute);
}

@Override
Expand Down Expand Up @@ -370,6 +349,34 @@ public void onMtuChanged(BluetoothDevice device, int mtu) {
}
}

private void onWriteBuffer(byte[] buffer) {
if (currentPacketBuilder.empty()) {
currentPacketBuilder.append(buffer);
} else {
currentPacketBuilder.add(buffer);
}
}

private void onWritePacket(byte[] buffer) {
currentPacketBuilder.append(buffer);
onApduBufferReady();
}

private void onExecuteWriteBuffer(boolean execute) {
if (execute) {
onApduBufferReady();
} else {
currentPacketBuilder.clear();
}
}

private void onApduBufferReady() {
if (currentPacketBuilder.complete()) {
sendBleAPDUToActivity(currentPacketBuilder.getBuffer());
currentPacketBuilder.clear();
}
}

private void finishConversation() {
Intent intent = new Intent(BlePeripheralService.CONVERSATION_FINISHED);
LocalBroadcastManager.getInstance(BlePeripheralService.this).sendBroadcast(intent);
Expand Down
13 changes: 9 additions & 4 deletions app/src/main/java/com/fidesmo/ble/client/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import android.widget.Toast;
import com.fidesmo.ble.R;
import com.fidesmo.ble.client.apdu.CardInfoClient;
import com.fidesmo.ble.client.apdu.SimpleApduFragmenter;
import com.fidesmo.ble.client.models.CardInfo;
import com.fidesmo.ble.client.models.CardOperation;
import nordpol.IsoCard;
Expand Down Expand Up @@ -50,6 +51,8 @@ public class MainActivity extends AppCompatActivity implements OnDiscoveredTagLi

private LinkedList<CardOperation> pendingOperations = new LinkedList<>();

private SimpleApduFragmenter apduFragmenter = new SimpleApduFragmenter();

private BroadcastReceiver apduReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Expand Down Expand Up @@ -101,7 +104,6 @@ public void onReceive(Context context, Intent intent) {
nfcTagDispatcher = TagDispatcher.get(this, this, false, false, false, true, false, true);
}


@Override
public void tagDiscovered(Tag tag) {
try {
Expand Down Expand Up @@ -271,9 +273,12 @@ private void processPendingCardOperations() {
}

Log.i(TAG, "Trying to transcieve data to a card: " + byteArrayToString(operation.getRequest()));

byte[] result = nfcCard.transceive(operation.getRequest());
operation.setResponse(result);
byte[][] apdus = apduFragmenter.decode(operation.getRequest());
byte[][] response = new byte[apdus.length][];
for (int i = 0; i < apdus.length; i ++) {
response[i] = nfcCard.transceive(apdus[i]);
}
operation.setResponse(apduFragmenter.encode(response));
sendResponse(operation);

operation = pendingOperations.poll();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.fidesmo.ble.client.apdu;

public interface ApduFragmenter {

byte[][] decode(byte[] encodedApdus);
byte[] encode(byte[][] apdus);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.fidesmo.ble.client.apdu;

import com.fidesmo.ble.client.protocol.PacketFragmenter;

public interface CustomFragmentationProtocol {
PacketFragmenter fragmenter(int var1, byte[] var2);

CustomPacketDefragmenter deframenter();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.fidesmo.ble.client.apdu;


public interface CustomPacketDefragmenter {
Copy link
Member

@sergkh sergkh Jun 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can think on how to merge this CustomPacketDefragmenter with original Packet fragmenter/defragmenter interfaces? Some of those methods can be handy there too.

But I have some notes about the interface:

  • Method add is not informative, looking at the interface you can't say what it is doing and how it is different from the append. Maybe we can rename both to something like addPacket and addBuffer (or nay other variant)?
  • Interface is not consistent: you have Java Bean style getBuffer and simplified complete (which in turn has to be completed maybe?) and empty. IHMO it would be good to choose one naming scheme or append isCompleted or remove get what is better to you.
  • Original intent was to not use clear and simply recreate the builder, why do you want to reuse it?
  • Github complains about not having newline at the end

void clear();

void append(byte[] buffer);

void add(byte[] buffer);

boolean complete();

boolean empty();

byte[] getBuffer();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.fidesmo.ble.client.apdu;


import android.util.Log;

import com.fidesmo.ble.client.BleUtils;
import com.fidesmo.ble.client.protocol.PacketFragmenter;
import com.fidesmo.ble.client.protocol.SimplePacketFragmenter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CustomPacketFragmenter implements PacketFragmenter {
public static final int HEADER_SIZE = 4;
private final int maxLength;
private final byte[] buffer;
private final int totalPackets;
private int sentPackets = 0;

public CustomPacketFragmenter(int maxLength, byte[] buffer) {
if(maxLength <= HEADER_SIZE) {
throw new IllegalArgumentException("MTU size can not be less of equal to header size");
} else {
this.maxLength = maxLength;
this.buffer = Arrays.copyOf(buffer, buffer.length);
int packetMax = maxLength - HEADER_SIZE;
int carriedLen = Math.max(buffer.length, 1);
this.totalPackets = carriedLen / packetMax + (carriedLen % packetMax > 0?1:0);
}
}

public byte[] nextFragment() {
int available = this.maxLength - HEADER_SIZE;
int offset = available * this.sentPackets;
int len = Math.min(available, this.buffer.length - offset);
byte[] chunk = new byte[len + HEADER_SIZE];
BleUtils.packInt2(this.totalPackets, chunk, 0);
BleUtils.packInt2(++this.sentPackets, chunk, HEADER_SIZE/2);
System.arraycopy(this.buffer, offset, chunk, HEADER_SIZE, len);
return chunk;
}

public boolean hasMoreData() {
return this.sentPackets < this.totalPackets;
}

public static CustomFragmentationProtocol factory() {
return new CustomPacketFragmenter.Factory();
}

private static class Factory implements CustomFragmentationProtocol {
private Factory() {
}

public PacketFragmenter fragmenter(int mtu, byte[] buffer) {
return new CustomPacketFragmenter(mtu, buffer);
}

public CustomPacketDefragmenter deframenter() {
return new CustomPacketFragmenter.Builder();
}
}

public static class Builder implements CustomPacketDefragmenter {
private int totalNo;
private int packetNo;

List<byte[]> byteArray;

private Builder() {
byteArray = new ArrayList<>();
totalNo = 0;
packetNo = 0;
}

public void clear(){
byteArray.clear();
totalNo = 0;
packetNo = 0;
}

public void append(byte[] array){
totalNo = BleUtils.unpackInt2(array, 0);
packetNo = BleUtils.unpackInt2(array, HEADER_SIZE/2);
byte[] buffer = new byte[array.length - HEADER_SIZE];
System.arraycopy(array, HEADER_SIZE, buffer, 0, buffer.length);
byteArray.add(buffer);
}

public void add(byte[] array){
byteArray.add(array);
}

public byte[] getBuffer(){
byte[] retArray;
int totalSize = 0;

for(int i=0; i < byteArray.size();i++){
totalSize = totalSize + byteArray.get(i).length;
}

int copuCounter = 0;
if(totalSize > 0) {
retArray = new byte[totalSize];
for(int ii=0; ii < byteArray.size();ii++){
byte[] tmpArr = byteArray.get(ii);
System.arraycopy(tmpArr, 0, retArray,copuCounter,tmpArr.length);
copuCounter = copuCounter + tmpArr.length;
}
}else{
retArray = new byte[]{};
}
return retArray;
}

public boolean complete() {
return totalNo == packetNo;
}

public boolean empty() { return totalNo == 0 && packetNo == 0; }
}
}
Loading