Skip to content

Commit

Permalink
Merge pull request #54 from tw-mosip/handle-uncaught-exception
Browse files Browse the repository at this point in the history
[iOS] refactor exception handling on iOS

- Add tuvali arch docs
  • Loading branch information
krishnakumar4a4 authored Apr 19, 2023
2 parents 474e1ab + a8c9164 commit e2b89bb
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 57 deletions.
Binary file added docs/assets/ble-high-level-comm-generic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/openid-for-vp-over-ble-spec-impl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/ble-packet-capture-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ While Packet logger provides good packet capture details. You can always export
6. Find adb from your ANDROID_HOME directory
7. If your device is properly connected via cable, adb devices should give list of devices
8. Run -> ${ANDROID_HOME}/platform-tools/adb bugreport report
- If there are multiple devices connected. We identify specific device using adb devices -l and run the above command with -s <device ID> to get bug report for that particular device.
9. unzip report.zip
10. Find the BLE capture file inside the extracted content using a find command from terminal. This will give you possible location for snoop log.
```
Expand Down
88 changes: 88 additions & 0 deletions docs/tuvali-implementation
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Tuvali implementation
Before we understand how Tuvali component performs activities on the BLE layer to transfer VC/VP. We can take a look at how BLE communication works in general between two devices A & B.

## How does BLE Communication work?
![BLE Communication](./assets/ble-high-level-comm-generic.png)

In BLE communication, one device is designated as Peripheral and another one designated as Central.

Peripheral can perform advertisement which can be read by a Central device and connect to it, if the advertisement is connectable.

Once the connection is made, Central can perform further actions on the device like
- discovering services and characteristics
- subscribing to notifications on characteritics
- write/read data from characteristics
- disconnect from device

While peripheral can perform actions like
- sending notifications to subscribed characteristics
- respond to read requests from Central

The above diagram explains the sequence of actions for a BLE communciation in general.

1. Advertisement from Peripheral
2. Connection establishment & additional data exchange
3. Service & Characteristic discovery from Central
4. Characteristic subscription on Peripheral
5. Write with response to Characteristic
6. Write without response to characteristic
7. Send Notification from GATT Server
8. Disconnection from GATT Server/Client

More details about other BLE terminology used here can be found in standard BLE specifications of 4.2 and above.

## How Tuvali works with BLE to transfer VC from Central to Peripheral
![OpenID for VP over BLE Implementation](./assets/openid-for-vp-over-ble-spec-impl.png)

> Note: Tuvali is supposed to implement OpenID for VP over BLE specification. As part of it, both VP request and response transfer should be implemented. However the current version of Tuvali only transfers VC from Central to Peripheral.

In case of Tuvali, the entire VC transfer flow can be divided into following stages
1. Connection Setup & Crypographic key exchange
2. Data transfer
3. Connection Closure

### 1. Connection Setup & Crypographic key exchange
Steps 1 to 6 mentioned in the above diagram explains how first stage is achieved.
Before even the advertisement is started, peripheral generates a 32 byte public key. This public key is sent to Central as two parts. First part will have 5 bytes of public key sent as part of the advertisement payload and while second part is sent as part of SCAN_RESP.

Since the advertisement from Peripheral is of connectable type, Central would send out a SCAN_REQ on receiving advertisement and gets the remaining 27 bytes of peripheral public key.
Post that, Central would derive a public key pair and send the its 32 bytes public key on Identify characteristic of Peripheral.

Once the public keys are exchanged between Central and Peripheral, a set of keys are derived on both sides which would be used for encryption and decryption of data on the wire.

> Note: Details about the the algorithm used for public key pair generation, key derivation is available in the spec.


### 2. Data transfer
Steps 7 to 11 mentioned in the above diagram explains how second stage is achieved.
Before VC is transferred, Central performs encryption and compression and communicate the resultant data size by writing to Response Size characteristic to Peripheral. The actual data transfer would happen on `Submit Response` characteristic.

Since the maximum allowed write value for a characteristic is limited to 512 bytes. The actual VC data is split by a component called Chunker into multiple smaller chunks. After the split, the data is transferred on the `Submit response`characteritics one after another until all chunks are completely sent.

Peripheral on the other hand, receives data on the `Submit response` characteristic. The received chunks are collected and final VC is assembled by a component called Assembler.

At the end of sending one frame of data from Central. It would request for a transfer report via `Transfer report request` characteristic. Peripheral responds with a summary of missing/corrupted chunks sequence numbers via another `Transfer report summary` characteristic.

Central would read the Transfer report sumamry to understand if the Peripheral received all the chunks properly. If summary report has atleast one chunk sequence number. Central would send those specific chunks to Peripheral which is called Failure frame.

The failure frame will be sent from Central repeatedly until Transfer report summary is successful. If during the process, Central reached the maximum allowed failure frame retry limit, the tranfer is halted, devices will be disconnected and an error is generated (Please refer to API documentation on how this error can be read).

### 3. Connection closure
#### Disconnect initiated by Peripheral:
- On a sucessful data transfer
- On non recoverable error occured on Peripheral

Peripheral notifies Central to perform disconnection via `Disconnect` characteristic mentioned in Step 12 of the above diagram.

#### Disconnect initiated by Central:
Central also performs disconnect in the following scenarios
- On a succesful data transfer
- Non recoverable error on Central
- Peripheral is out of range/disconnected
- Destroy Connection API

As part of Connection closure. Both Central and Peripheral objects are cleaned up along with Wallet and Verifier objects.




33 changes: 33 additions & 0 deletions ios/Exception/ErrorHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

class ErrorHandler {
public static var sharedInstance = ErrorHandler()
private var walletExceptionHandler: WalletExceptionHandler?
private var onError: ((_ message: String) -> Void)?

private init() {}

func handleException(type: ExceptionType, error: WalletErrorEnum) {
if type == .walletException {
walletExceptionHandler?.handle(error: error)
} else if type == .verifierException {
} else {
handleUnknownException(error: error)
}
}

func setOnError(onError: @escaping (_ message: String) -> Void) {
self.onError = onError
walletExceptionHandler = WalletExceptionHandler(error: self.onError!)
}

private func handleUnknownException(error: WalletErrorEnum) {
os_log(.error, "Error in OpenID4vBLE: %{public}@", error.description)
self.onError?(error.description)
}
}

enum ExceptionType {
case walletException
case verifierException
}
20 changes: 20 additions & 0 deletions ios/Exception/VerifierExceptionHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

enum VerifierErrorEnum: Error {
case corruptedChunkReceived
case tooManyFailureChunks
case unsupportedMTUSizeException
}

extension VerifierErrorEnum: CustomStringConvertible {
public var description: String {
switch self {
case .corruptedChunkReceived:
return "Received corrupted chunks from the wallet"
case .tooManyFailureChunks:
return "Failing VC transfer as failure chunks are more than 70% of total chunks"
case .unsupportedMTUSizeException:
return "Minimum 512 MTU is required for VC transfer"
}
}
}
31 changes: 31 additions & 0 deletions ios/Exception/WalletExceptionHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Foundation

class WalletExceptionHandler {

private var onError: ((_ message: String) -> Void)?

init(error: (@escaping (String) -> Void)) {
self.onError = error
}

func handle(error: WalletErrorEnum) {
os_log(.error, "Error in OpenID4vBLE: %{public}@", error.description)
self.onError?(error.description)
}
}

enum WalletErrorEnum: Error {
case invalidMTUSizeError(mtu: Int)
case responseTransferFailure
}

extension WalletErrorEnum: CustomStringConvertible {
public var description: String {
switch self {
case .invalidMTUSizeError(let mtu):
return "Negotiated MTU: \(mtu) is too low."
case .responseTransferFailure:
return "failed to write response"
}
}
}
32 changes: 17 additions & 15 deletions ios/Openid4vpBle.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
0A62BB732938C1FB0092E47D /* Verifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A62BB722938C1FA0092E47D /* Verifier.swift */; };
0ACACE8A2966D8C800DABDFE /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACACE892966D8C800DABDFE /* Utils.swift */; };
0D36889829A782AA000B12CD /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D36889629A782AA000B12CD /* ErrorHandler.swift */; };
0D36889929A782AA000B12CD /* OpenId4vpError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D36889729A782AA000B12CD /* OpenId4vpError.swift */; };
0D36889929A782AA000B12CD /* WalletExceptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D36889729A782AA000B12CD /* WalletExceptionHandler.swift */; };
8D069F652964466900AB61A9 /* Central.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D069F642964466900AB61A9 /* Central.swift */; };
8D069F672964475A00AB61A9 /* CentralManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D069F662964475A00AB61A9 /* CentralManagerDelegate.swift */; };
8D069F69296447D000AB61A9 /* Peripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D069F68296447D000AB61A9 /* Peripheral.swift */; };
Expand All @@ -33,6 +33,7 @@
8D31C4022956B3EF0073B710 /* WalletCryptoBoxImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D31C4012956B3EF0073B710 /* WalletCryptoBoxImpl.swift */; };
A7A42E4129A8CCF10026167C /* Wallet+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7A42E4029A8CCF10026167C /* Wallet+Extension.swift */; };
E223F0322977BE090042F919 /* TransferHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E223F0312977BE090042F919 /* TransferHandler.swift */; };
E2399AA329D5ED140017C313 /* VerifierExceptionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2399AA229D5ED140017C313 /* VerifierExceptionHandler.swift */; };
E25800A42976BF8700968EA0 /* chunker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25800A32976BF8700968EA0 /* chunker.swift */; };
E2D682D429A494D8009E71A6 /* PeripheralCommunicatorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D682D329A494D8009E71A6 /* PeripheralCommunicatorDelegate.swift */; };
E2F1AFCB297176C200AC3355 /* BLEConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F1AFCA297176C200AC3355 /* BLEConstants.swift */; };
Expand Down Expand Up @@ -60,7 +61,7 @@
0A62BB722938C1FA0092E47D /* Verifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Verifier.swift; sourceTree = "<group>"; };
0ACACE892966D8C800DABDFE /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
0D36889629A782AA000B12CD /* ErrorHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
0D36889729A782AA000B12CD /* OpenId4vpError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenId4vpError.swift; sourceTree = "<group>"; };
0D36889729A782AA000B12CD /* WalletExceptionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletExceptionHandler.swift; sourceTree = "<group>"; };
134814201AA4EA6300B7C361 /* libOpenid4vpBle.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libOpenid4vpBle.a; sourceTree = BUILT_PRODUCTS_DIR; };
8D069F642964466900AB61A9 /* Central.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Central.swift; sourceTree = "<group>"; };
8D069F662964475A00AB61A9 /* CentralManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CentralManagerDelegate.swift; sourceTree = "<group>"; };
Expand All @@ -84,6 +85,7 @@
A7A42E4029A8CCF10026167C /* Wallet+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Wallet+Extension.swift"; sourceTree = "<group>"; };
B3E7B5891CC2AC0600A0062D /* Openid4vpBle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Openid4vpBle.m; sourceTree = "<group>"; };
E223F0312977BE090042F919 /* TransferHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferHandler.swift; sourceTree = "<group>"; };
E2399AA229D5ED140017C313 /* VerifierExceptionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifierExceptionHandler.swift; sourceTree = "<group>"; };
E25800A32976BF8700968EA0 /* chunker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = chunker.swift; sourceTree = "<group>"; };
E2D682D329A494D8009E71A6 /* PeripheralCommunicatorDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralCommunicatorDelegate.swift; sourceTree = "<group>"; };
E2F1AFCA297176C200AC3355 /* BLEConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEConstants.swift; sourceTree = "<group>"; };
Expand All @@ -106,16 +108,6 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
0D36889529A782AA000B12CD /* Error */ = {
isa = PBXGroup;
children = (
0D36889629A782AA000B12CD /* ErrorHandler.swift */,
0D36889729A782AA000B12CD /* OpenId4vpError.swift */,
);
name = Error;
path = Openid4vpBle/Error;
sourceTree = "<group>";
};
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
Expand All @@ -127,8 +119,8 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
E2399AA129D3FECF0017C313 /* Exception */,
FD494F4C29ADBE00002612C1 /* BackOffStrategy.swift */,
0D36889529A782AA000B12CD /* Error */,
FDA18F3D2977B67C00290433 /* crc */,
8D069F612964454700AB61A9 /* ble */,
8D179C82295486B500017EE5 /* crypto */,
Expand All @@ -145,7 +137,6 @@
isa = PBXGroup;
children = (
0A62BB702938C1EC0092E47D /* Wallet.swift */,
E2F1AFCC2971883500AC3355 /* WalletViewModel.swift */,
A7A42E4029A8CCF10026167C /* Wallet+Extension.swift */,
);
path = Wallet;
Expand Down Expand Up @@ -259,6 +250,16 @@
path = Wallet;
sourceTree = "<group>";
};
E2399AA129D3FECF0017C313 /* Exception */ = {
isa = PBXGroup;
children = (
0D36889629A782AA000B12CD /* ErrorHandler.swift */,
0D36889729A782AA000B12CD /* WalletExceptionHandler.swift */,
E2399AA229D5ED140017C313 /* VerifierExceptionHandler.swift */,
);
path = Exception;
sourceTree = "<group>";
};
E2F1AFC9297176A700AC3355 /* Utility */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -342,6 +343,7 @@
8D31C3F82956B0B30073B710 /* SecretTranslator.swift in Sources */,
0D36889829A782AA000B12CD /* ErrorHandler.swift in Sources */,
8D069F69296447D000AB61A9 /* Peripheral.swift in Sources */,
E2399AA329D5ED140017C313 /* VerifierExceptionHandler.swift in Sources */,
FDA18F3F2977B69A00290433 /* CRC16.swift in Sources */,
8D069F652964466900AB61A9 /* Central.swift in Sources */,
FDD9411E297FAA6B00FBBDD4 /* DataExtension.swift in Sources */,
Expand All @@ -357,7 +359,7 @@
8D179C8F29548B3A00017EE5 /* CryptoBoxImpl.swift in Sources */,
8D179C8D29548AB400017EE5 /* CryptoBox.swift in Sources */,
8D31C4022956B3EF0073B710 /* WalletCryptoBoxImpl.swift in Sources */,
0D36889929A782AA000B12CD /* OpenId4vpError.swift in Sources */,
0D36889929A782AA000B12CD /* WalletExceptionHandler.swift in Sources */,
8D31C3FA2956B10E0073B710 /* SenderTransferOwnershipOfData.swift in Sources */,
FDD941202980FD2500FBBDD4 /* TransferReport.swift in Sources */,
8D31C3FE2956B2650073B710 /* WalletCryptoBoxBuilder.swift in Sources */,
Expand Down
22 changes: 0 additions & 22 deletions ios/Openid4vpBle/Error/ErrorHandler.swift

This file was deleted.

17 changes: 0 additions & 17 deletions ios/Openid4vpBle/Error/OpenId4vpError.swift

This file was deleted.

2 changes: 1 addition & 1 deletion ios/Openid4vpBle/Openid4vpBle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import Foundation
@objc(Openid4vpBle)
class Openid4vpBle: RCTEventEmitter {
var wallet: Wallet?

var tuvaliVersion: String = "unknown"

override init() {
super.init()
EventEmitter.sharedInstance.registerEventEmitter(eventEmitter: self)
Expand Down
2 changes: 1 addition & 1 deletion ios/ble/Utility/TransferHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class TransferHandler {
sendMessage(message: imessage(msgType: .READ_TRANSMISSION_REPORT))
} else if msg.msgType == .RESPONSE_TRANSFER_FAILED {
currentState = States.ResponseWriteFailed
ErrorHandler.sharedInstance.handle(error: OpenId4vpError.responseTransferFailure)
ErrorHandler.sharedInstance.handleException(type: .walletException, error: .responseTransferFailure)
} else {
os_log(.error, "Out of scope")
}
Expand Down
2 changes: 1 addition & 1 deletion ios/ble/central/PeripheralDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extension Central: CBPeripheralDelegate {
let mtu = peripheral.maximumWriteValueLength(for: .withoutResponse);

if mtu < 64 {
ErrorHandler.sharedInstance.handle(error: OpenId4vpError.invalidMTUSizeError(mtu: mtu))
ErrorHandler.sharedInstance.handleException(type: .walletException, error: .invalidMTUSizeError(mtu: mtu))
return
}

Expand Down

0 comments on commit e2b89bb

Please sign in to comment.