-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
345 additions
and
6 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import CoreBluetooth | ||
import SpruceIDWalletSdkRs | ||
|
||
public class MDocReader { | ||
var sessionManager: MdlSessionManager | ||
var bleManager: MDocReaderBLEPeripheral! | ||
var callback: BLEReaderSessionStateDelegate | ||
|
||
public init?(callback: BLEReaderSessionStateDelegate, uri: String, requestedItems: [String: [String: Bool]]) { | ||
self.callback = callback | ||
do { | ||
let sessionData = try SpruceIDWalletSdkRs.establishSession(uri: uri, requestedItems: requestedItems, trustAnchorRegistry: nil) | ||
self.sessionManager = sessionData.state | ||
self.bleManager = MDocReaderBLEPeripheral(callback: self, serviceUuid: CBUUID(string: sessionData.uuid), request: sessionData.request, bleIdent: Data(sessionData.bleIdent.utf8)) | ||
} catch { | ||
print("\(error)") | ||
return nil | ||
} | ||
} | ||
|
||
public func cancel() { | ||
bleManager.disconnect() | ||
} | ||
} | ||
|
||
extension MDocReader: MDocReaderBLEDelegate { | ||
func callback(message: MDocReaderBLECallback) { | ||
switch message { | ||
case .done(let data): | ||
self.callback.update(state: .success(data)) | ||
case .connected: | ||
self.callback.update(state: .connected) | ||
case .error(let error): | ||
self.callback.update(state: .error(BleReaderSessionError(readerBleError: error))) | ||
self.cancel() | ||
case .message(let data): | ||
do { | ||
let responseData = try SpruceIDWalletSdkRs.handleResponse(state: self.sessionManager, response: data) | ||
self.sessionManager = responseData.state | ||
self.callback.update(state: .success(responseData.verifiedResponse)) | ||
} catch { | ||
self.callback.update(state: .error(.generic("\(error)"))) | ||
self.cancel() | ||
} | ||
case .downloadProgress(let index): | ||
self.callback.update(state: .downloadProgress(index)) | ||
} | ||
} | ||
} | ||
|
||
/// To be implemented by the consumer to update the UI | ||
public protocol BLEReaderSessionStateDelegate: AnyObject { | ||
func update(state: BLEReaderSessionState) | ||
} | ||
|
||
public enum BLEReaderSessionState { | ||
/// App should display the error message | ||
case error(BleReaderSessionError) | ||
/// App should indicate to the reader is waiting to connect to the holder | ||
case advertizing | ||
/// App should indicate to the user that BLE connection has been established | ||
case connected | ||
/// App should display the fact that a certain amount of data has been received | ||
/// - Parameters: | ||
/// - 0: The number of chunks received to far | ||
case downloadProgress(Int) | ||
/// App should display a success message and offer to close the page | ||
case success([String: [String: [String: MDocItem]]]) | ||
} | ||
|
||
public enum BleReaderSessionError { | ||
/// When communication with the server fails | ||
case server(String) | ||
/// When Bluetooth is unusable (e.g. unauthorized). | ||
case bluetooth(CBCentralManager) | ||
/// Generic unrecoverable error | ||
case generic(String) | ||
|
||
init(readerBleError: MdocReaderBleError) { | ||
switch readerBleError { | ||
case .server(let string): | ||
self = .server(string) | ||
case .bluetooth(let string): | ||
self = .bluetooth(string) | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
import Algorithms | ||
import CoreBluetooth | ||
import Foundation | ||
import SpruceIDWalletSdkRs | ||
|
||
class MDocReaderBLEPeripheral: NSObject { | ||
var peripheralManager: CBPeripheralManager! | ||
var serviceUuid: CBUUID | ||
var bleIdent: Data | ||
var incomingMessageBuffer = Data() | ||
var incomingMessageIndex = 0 | ||
var callback: MDocReaderBLEDelegate | ||
var writeCharacteristic: CBMutableCharacteristic? | ||
var readCharacteristic: CBMutableCharacteristic? | ||
var stateCharacteristic: CBMutableCharacteristic? | ||
var identCharacteristic: CBMutableCharacteristic? | ||
var l2capCharacteristic: CBMutableCharacteristic? | ||
var requestData: Data | ||
var maximumCharacteristicSize: Int? | ||
var writingQueueTotalChunks: Int? | ||
var writingQueueChunkIndex: Int? | ||
var writingQueue: IndexingIterator<ChunksOfCountCollection<Data>>? | ||
|
||
init(callback: MDocReaderBLEDelegate, serviceUuid: CBUUID, request: Data, bleIdent: Data) { | ||
self.serviceUuid = serviceUuid | ||
self.callback = callback | ||
self.bleIdent = bleIdent | ||
self.requestData = request | ||
self.incomingMessageBuffer = Data() | ||
super.init() | ||
self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: [CBPeripheralManagerOptionShowPowerAlertKey: true]) | ||
} | ||
|
||
func setupService() { | ||
let service = CBMutableService(type: self.serviceUuid, primary: true) | ||
// CBUUIDClientCharacteristicConfigurationString only returns "2902" | ||
// let clientDescriptor = CBMutableDescriptor(type: CBUUID(string: "00002902-0000-1000-8000-00805f9b34fb"), value: Data([0x00, 0x00])) as CBDescriptor | ||
// wallet-sdk-kt isn't using write without response... | ||
self.stateCharacteristic = CBMutableCharacteristic(type: readerStateCharacteristicId, | ||
properties: [.notify, .writeWithoutResponse, .write], | ||
value: nil, | ||
permissions: [.writeable]) | ||
// for some reason this seems to drop all other descriptors | ||
// self.stateCharacteristic!.descriptors = [clientDescriptor] + (self.stateCharacteristic!.descriptors ?? [] ) | ||
// self.stateCharacteristic!.descriptors?.insert(clientDescriptor, at: 0) | ||
// wallet-sdk-kt isn't using write without response... | ||
self.readCharacteristic = CBMutableCharacteristic(type: readerClient2ServerCharacteristicId, | ||
properties: [.writeWithoutResponse, .write], | ||
value: nil, | ||
permissions: [.writeable]) | ||
self.writeCharacteristic = CBMutableCharacteristic(type: readerServer2ClientCharacteristicId, | ||
properties: [.notify], | ||
value: nil, | ||
permissions: [.readable, .writeable]) | ||
// self.writeCharacteristic!.descriptors = [clientDescriptor] + (self.writeCharacteristic!.descriptors ?? [] ) | ||
// self.writeCharacteristic!.descriptors?.insert(clientDescriptor, at: 0) | ||
self.identCharacteristic = CBMutableCharacteristic(type: readerIdentCharacteristicId, | ||
properties: [.read], | ||
value: bleIdent, | ||
permissions: [.readable]) | ||
// wallet-sdk-kt is failing if this is present | ||
// self.l2capCharacteristic = CBMutableCharacteristic(type: readerL2CAPCharacteristicId, | ||
// properties: [.read], | ||
// value: nil, | ||
// permissions: [.readable]) | ||
service.characteristics = (service.characteristics ?? []) + [ | ||
stateCharacteristic! as CBCharacteristic, | ||
readCharacteristic! as CBCharacteristic, | ||
writeCharacteristic! as CBCharacteristic, | ||
identCharacteristic! as CBCharacteristic, | ||
// l2capCharacteristic! as CBCharacteristic | ||
] | ||
peripheralManager.add(service) | ||
} | ||
|
||
func disconnect() { | ||
return | ||
} | ||
|
||
func writeOutgoingValue(data: Data) { | ||
let chunks = data.chunks(ofCount: maximumCharacteristicSize! - 1) | ||
writingQueueTotalChunks = chunks.count | ||
writingQueue = chunks.makeIterator() | ||
writingQueueChunkIndex = 0 | ||
drainWritingQueue() | ||
} | ||
|
||
private func drainWritingQueue() { | ||
if writingQueue != nil { | ||
if var chunk = writingQueue?.next() { | ||
var firstByte: Data.Element | ||
writingQueueChunkIndex! += 1 | ||
if writingQueueChunkIndex == writingQueueTotalChunks { | ||
firstByte = 0x00 | ||
} else { | ||
firstByte = 0x01 | ||
} | ||
chunk.reverse() | ||
chunk.append(firstByte) | ||
chunk.reverse() | ||
self.peripheralManager?.updateValue(chunk, for: self.writeCharacteristic!, onSubscribedCentrals: nil) | ||
} else { | ||
writingQueue = nil | ||
} | ||
} | ||
} | ||
|
||
func processData(central: CBCentral, characteristic: CBCharacteristic, value: Data?) throws { | ||
if var data = value { | ||
print("Processing data for \(characteristic.uuid)") | ||
switch characteristic.uuid { | ||
case readerClient2ServerCharacteristicId: | ||
let firstByte = data.popFirst() | ||
incomingMessageBuffer.append(data) | ||
switch firstByte { | ||
case .none: | ||
throw DataError.noData(characteristic: characteristic.uuid) | ||
case 0x00: // end | ||
print("End of message") | ||
self.callback.callback(message: MDocReaderBLECallback.message(incomingMessageBuffer)) | ||
self.incomingMessageBuffer = Data() | ||
self.incomingMessageIndex = 0 | ||
return | ||
case 0x01: // partial | ||
print("Partial message") | ||
self.incomingMessageIndex += 1 | ||
self.callback.callback(message: .downloadProgress(self.incomingMessageIndex)) | ||
// TODO check length against MTU | ||
return | ||
case let .some(byte): | ||
throw DataError.unknownDataTransferPrefix(byte: byte) | ||
} | ||
case readerStateCharacteristicId: | ||
if data.count != 1 { | ||
throw DataError.invalidStateLength | ||
} | ||
switch data[0] { | ||
case 0x01: | ||
print("Starting to send request") | ||
writeOutgoingValue(data: self.requestData) | ||
case let byte: | ||
throw DataError.unknownState(byte: byte) | ||
} | ||
return | ||
// case readerL2CAPCharacteristicId: | ||
// return | ||
case let uuid: | ||
throw DataError.unknownCharacteristic(uuid: uuid) | ||
} | ||
} else { | ||
throw DataError.noData(characteristic: characteristic.uuid) | ||
} | ||
} | ||
} | ||
|
||
extension MDocReaderBLEPeripheral: CBPeripheralManagerDelegate { | ||
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { | ||
switch peripheral.state { | ||
case .poweredOn: | ||
print("Advertising...") | ||
setupService() | ||
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [serviceUuid]]) | ||
case .unsupported: | ||
print("Peripheral Is Unsupported.") | ||
case .unauthorized: | ||
print("Peripheral Is Unauthorized.") | ||
case .unknown: | ||
print("Peripheral Unknown") | ||
case .resetting: | ||
print("Peripheral Resetting") | ||
case .poweredOff: | ||
print("Peripheral Is Powered Off.") | ||
@unknown default: | ||
print("Error") | ||
} | ||
} | ||
|
||
// This is called when there is space in the queue again (so it is part of the loop for drainWritingQueue) | ||
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) { | ||
self.drainWritingQueue() | ||
} | ||
|
||
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { | ||
print("Subscribed to \(characteristic.uuid)") | ||
self.callback.callback(message: .connected) | ||
self.peripheralManager?.stopAdvertising() | ||
switch characteristic.uuid { | ||
case readerStateCharacteristicId: | ||
// This will trigger wallet-sdk-swift to send 0x01 to start the exchange | ||
peripheralManager.updateValue(bleIdent, for: self.identCharacteristic!, onSubscribedCentrals: nil) | ||
// This will trigger wallet-sdk-kt to send 0x01 to start the exchange | ||
peripheralManager.updateValue(Data([0x01]), for: self.stateCharacteristic!, onSubscribedCentrals: nil) | ||
case _: | ||
return | ||
} | ||
} | ||
|
||
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { | ||
print("Received read request for \(request.characteristic.uuid)") | ||
|
||
// Since there is no callback for MTU on iOS we will grab it here. | ||
maximumCharacteristicSize = min(request.central.maximumUpdateValueLength, 512) | ||
|
||
if (request.characteristic.uuid == readerIdentCharacteristicId) { | ||
peripheralManager.respond(to: request, withResult: .success) | ||
} else if (request.characteristic.uuid == readerL2CAPCharacteristicId) { | ||
// peripheralManager.publishL2CAPChannel(withEncryption: true) | ||
// peripheralManager.respond(to: request, withResult: .success) | ||
} else { | ||
self.callback.callback(message: .error(.server("Read on unexpected characteristic with UUID \(request.characteristic.uuid)"))) | ||
} | ||
} | ||
|
||
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { | ||
for request in requests { | ||
// Since there is no callback for MTU on iOS we will grab it here. | ||
maximumCharacteristicSize = min(request.central.maximumUpdateValueLength, 512) | ||
|
||
do { | ||
print("Processing request") | ||
try processData(central: request.central, characteristic: request.characteristic, value: request.value) | ||
// This can be removed, or return an error, once wallet-sdk-kt is fixed and uses withoutResponse writes | ||
if request.characteristic.properties.contains(.write) { | ||
peripheralManager.respond(to: request, withResult: .success) | ||
} | ||
} catch { | ||
self.callback.callback(message: .error(.server("\(error)"))) | ||
self.peripheralManager?.updateValue(Data([0x02]), for: self.stateCharacteristic!, onSubscribedCentrals: nil) | ||
} | ||
} | ||
} | ||
} |
This file contains 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