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

IOS-4025 Refactor preflight filtering #329

Merged
merged 14 commits into from
Nov 28, 2023
Merged
20 changes: 20 additions & 0 deletions TangemSdk/TangemSdk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@
DC1244E829BB9E0C0037BC05 /* ExtendedKeySerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC1244E729BB9E0C0037BC05 /* ExtendedKeySerializer.swift */; };
DC22228729D431AB001129F8 /* SetUserCodeRecoveryAllowed.json in Resources */ = {isa = PBXBuildFile; fileRef = DC22228629D431AB001129F8 /* SetUserCodeRecoveryAllowed.json */; };
DC234CC629F1A3F100082063 /* ImportWalletMnemonic.json in Resources */ = {isa = PBXBuildFile; fileRef = DC234CC529F1A3F100082063 /* ImportWalletMnemonic.json */; };
DC30D70A2AF024D000744FA4 /* PreflightReadFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC30D7092AF024D000744FA4 /* PreflightReadFilter.swift */; };
DC3D97F92A77D3C6001EEE7A /* SLIP23.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3D97F82A77D3C6001EEE7A /* SLIP23.swift */; };
DC3D97FB2A77E079001EEE7A /* SLIP23Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3D97FA2A77E079001EEE7A /* SLIP23Tests.swift */; };
DC3D97FE2A77F5C6001EEE7A /* BIP32MasterKeyFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3D97FD2A77F5C6001EEE7A /* BIP32MasterKeyFactory.swift */; };
Expand All @@ -281,6 +282,8 @@
DC59CB0A29AF6F9C00EC14E1 /* EntropyLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0929AF6F9C00EC14E1 /* EntropyLength.swift */; };
DC59CB0C29AF706100EC14E1 /* MnemonicError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0B29AF706100EC14E1 /* MnemonicError.swift */; };
DC59CB0E29AF70C700EC14E1 /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59CB0D29AF70C700EC14E1 /* Mnemonic.swift */; };
DC612D6C2AFD58A3005A547F /* CardIdPreflightReadFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC612D6B2AFD58A3005A547F /* CardIdPreflightReadFilter.swift */; };
DC612D722AFD60C2005A547F /* SessionFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC612D712AFD60C2005A547F /* SessionFilter.swift */; };
DC70AD652A80FC9F00928836 /* CommonFirmwareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC70AD642A80FC9F00928836 /* CommonFirmwareTests.swift */; };
DC70AD672A8115BB00928836 /* FWTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC70AD662A8115BB00928836 /* FWTestCase.swift */; };
DC7254902A03E20A0003FE1B /* DerivedKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72548F2A03E20A0003FE1B /* DerivedKeys.swift */; };
Expand Down Expand Up @@ -652,6 +655,7 @@
DC1244E729BB9E0C0037BC05 /* ExtendedKeySerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedKeySerializer.swift; sourceTree = "<group>"; };
DC22228629D431AB001129F8 /* SetUserCodeRecoveryAllowed.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SetUserCodeRecoveryAllowed.json; sourceTree = "<group>"; };
DC234CC529F1A3F100082063 /* ImportWalletMnemonic.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ImportWalletMnemonic.json; sourceTree = "<group>"; };
DC30D7092AF024D000744FA4 /* PreflightReadFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreflightReadFilter.swift; sourceTree = "<group>"; };
DC3D97F82A77D3C6001EEE7A /* SLIP23.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLIP23.swift; sourceTree = "<group>"; };
DC3D97FA2A77E079001EEE7A /* SLIP23Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SLIP23Tests.swift; sourceTree = "<group>"; };
DC3D97FD2A77F5C6001EEE7A /* BIP32MasterKeyFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BIP32MasterKeyFactory.swift; sourceTree = "<group>"; };
Expand All @@ -665,6 +669,8 @@
DC59CB0929AF6F9C00EC14E1 /* EntropyLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntropyLength.swift; sourceTree = "<group>"; };
DC59CB0B29AF706100EC14E1 /* MnemonicError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicError.swift; sourceTree = "<group>"; };
DC59CB0D29AF70C700EC14E1 /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = "<group>"; };
DC612D6B2AFD58A3005A547F /* CardIdPreflightReadFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardIdPreflightReadFilter.swift; sourceTree = "<group>"; };
DC612D712AFD60C2005A547F /* SessionFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionFilter.swift; sourceTree = "<group>"; };
DC70AD642A80FC9F00928836 /* CommonFirmwareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonFirmwareTests.swift; sourceTree = "<group>"; };
DC70AD662A8115BB00928836 /* FWTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FWTestCase.swift; sourceTree = "<group>"; };
DC72548F2A03E20A0003FE1B /* DerivedKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DerivedKeys.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1067,6 +1073,7 @@
children = (
5D58202124221E2B0057EF40 /* TangemSdkError.swift */,
5D58201F24221E060057EF40 /* CardSession.swift */,
DC612D712AFD60C2005A547F /* SessionFilter.swift */,
5D7D5FB123449D4000058D69 /* SessionEnvironment.swift */,
5D705B5A23DAF2BB002CCD7A /* Config.swift */,
5D379C28268FA4AC00C7F473 /* CardSessionRunnable.swift */,
Expand Down Expand Up @@ -1107,6 +1114,7 @@
5D3F77B424B9B8E700E8695B /* Attestation */,
5D86CBD624A10F3D00FB5BA7 /* Personalization */,
B0BD1C8E255E58BE00119D82 /* Wallet */,
DC612D6A2AFD585B005A547F /* PreflightReadFilter */,
5D7D5FB323449F2300058D69 /* Command.swift */,
5D6A92ED23463D1200158457 /* ScanTask.swift */,
B006971325FFA4420040D203 /* PreflightReadTask.swift */,
Expand Down Expand Up @@ -1536,6 +1544,15 @@
path = Wordlists;
sourceTree = "<group>";
};
DC612D6A2AFD585B005A547F /* PreflightReadFilter */ = {
isa = PBXGroup;
children = (
DC30D7092AF024D000744FA4 /* PreflightReadFilter.swift */,
DC612D6B2AFD58A3005A547F /* CardIdPreflightReadFilter.swift */,
);
path = PreflightReadFilter;
sourceTree = "<group>";
};
DCB5A4E12A1FAB190021E12D /* BLS */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1936,6 +1953,7 @@
5DFCE40524B30589007B95AF /* Storage.swift in Sources */,
5DDD6C7325D30C1400E48D7B /* TangemSdkLogger.swift in Sources */,
DCD2D9162A1CF64A00AB00B6 /* precomputed_ecmult.c in Sources */,
DC612D6C2AFD58A3005A547F /* CardIdPreflightReadFilter.swift in Sources */,
5D81348426D62F8900494A71 /* StringCodable.swift in Sources */,
B091C0F8253EF3EE003D57E7 /* FileHashData.swift in Sources */,
5DF5FB1C244F2C15002DB244 /* IssuerDataVerifier.swift in Sources */,
Expand Down Expand Up @@ -2130,6 +2148,7 @@
B006971425FFA4420040D203 /* PreflightReadTask.swift in Sources */,
5DA3A2EF251CA507009A8E08 /* CheckUserCodesCommand.swift in Sources */,
5D705B5B23DAF2BB002CCD7A /* Config.swift in Sources */,
DC612D722AFD60C2005A547F /* SessionFilter.swift in Sources */,
5D6A92EC2346069700158457 /* TangemSdk.swift in Sources */,
DC1244B329B60B6F0037BC05 /* BIP39.swift in Sources */,
5DFFC49F233B9D69004964E8 /* NFCReader.swift in Sources */,
Expand All @@ -2142,6 +2161,7 @@
5DB4406F234B750200AC39F1 /* String+.swift in Sources */,
5D2FE06524DD82BA0086B5E8 /* VerifyCardRequest.swift in Sources */,
5DFFC4A2233BA5E0004964E8 /* Byte+.swift in Sources */,
DC30D70A2AF024D000744FA4 /* PreflightReadFilter.swift in Sources */,
B06947BF2534570B0056A887 /* DeleteFilesTask.swift in Sources */,
DC1244DA29BB65970037BC05 /* ExtendedKeySerializable.swift in Sources */,
5DD6762C26CD621500D8C909 /* UIScreen+.swift in Sources */,
Expand Down
33 changes: 23 additions & 10 deletions TangemSdk/TangemSdk/Common/Core/CardSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ public class CardSession {
public let viewDelegate: SessionViewDelegate

var state: CardSessionState = .inactive

/// Contains data relating to the current Tangem card. It is used in constructing all the commands,
/// and commands can modify `SessionEnvironment`.

private(set) var cardId: String?

var cardId: String? {
switch filter {
case .cardId(let cardId):
return cardId
default:
return nil
}
}

// initial environment to be able to reset a current one
private let _environment: SessionEnvironment
public internal(set) var environment: SessionEnvironment

private let reader: CardReader
private let jsonConverter: JSONRPCConverter
private let initialMessage: Message?
Expand All @@ -39,7 +48,8 @@ public class CardSession {
private var resetCodesController: ResetCodesController? = nil
/// Allows access codes to be stored in a secure location
private var accessCodeRepository: AccessCodeRepository? = nil

private let filter: SessionFilter?

private var shouldRequestBiometrics: Bool {
guard let accessCodeRepository = self.accessCodeRepository else {
return false
Expand All @@ -55,24 +65,25 @@ public class CardSession {
/// Main initializer
/// - Parameters:
/// - environment: Contains data relating to a Tangem card
/// - cardId: CID, Unique Tangem card ID number. If not nil, the SDK will check that you tapped the card with this cardID and will return the `wrongCard` error' otherwise
/// - filter: Filters card to be read. Optional.
/// - initialMessage: A custom description that shows at the beginning of the NFC session. If nil, default message will be used
/// - cardReader: NFC-reader implementation
/// - viewDelegate: viewDelegate implementation
/// - jsonConverter: JSONRPCConverter
/// - accessCodeRepository: Optional AccessCodeRepository that saves access codes to Apple Keychain
init(environment: SessionEnvironment,
cardId: String? = nil,
filter: SessionFilter? = nil,
initialMessage: Message? = nil,
cardReader: CardReader,
viewDelegate: SessionViewDelegate,
jsonConverter: JSONRPCConverter,
accessCodeRepository: AccessCodeRepository?) {
self.reader = cardReader
self.viewDelegate = viewDelegate
self._environment = environment
self.environment = environment
self.initialMessage = initialMessage
self.cardId = cardId
self.filter = filter
self.jsonConverter = jsonConverter
self.accessCodeRepository = accessCodeRepository
}
Expand Down Expand Up @@ -399,7 +410,7 @@ public class CardSession {
// MARK: - Preflight check
private func preflightCheck(_ onSessionStarted: @escaping (CardSession, TangemSdkError?) -> Void) {
Log.session("Start preflight check")
let preflightTask = PreflightReadTask(readMode: preflightReadMode, cardId: cardId)
let preflightTask = PreflightReadTask(readMode: preflightReadMode, filter: filter?.preflightReadFilter)
preflightTask.run(in: self) { [weak self] readResult in
guard let self = self else { return }

Expand All @@ -408,8 +419,10 @@ public class CardSession {
onSessionStarted(self, nil)
case .failure(let error):
switch error {
case .wrongCardType, .wrongCardNumber:
case .preflightFiltered:
self.viewDelegate.wrongCard(message: error.localizedDescription)
// We have to return environment to initial state to reset all the changes
self.environment = self._environment
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
guard self.reader.isReady else {
onSessionStarted(self, .userCancelled)
Expand Down
35 changes: 35 additions & 0 deletions TangemSdk/TangemSdk/Common/Core/SessionFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// SessionFilter.swift
// TangemSdk
//
// Created by Alexander Osokin on 09.11.2023.
// Copyright © 2023 Tangem AG. All rights reserved.
//

import Foundation

@available(iOS 13.0, *)
public enum SessionFilter {
case cardId(String)
case custom(PreflightReadFilter)
}

@available(iOS 13.0, *)
extension SessionFilter {
var preflightReadFilter: PreflightReadFilter {
switch self {
case .cardId(let cardId):
return CardIdPreflightReadFilter(cardId: cardId)
case .custom(let filter):
return filter
}
}

init?(from cardId: String?) {
guard let cardId else {
return nil
}

self = .cardId(cardId)
}
}
9 changes: 6 additions & 3 deletions TangemSdk/TangemSdk/Common/Core/TangemSdkError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {
/// This error is returned when a user scans a card of a [com.tangem.common.extensions.CardType]
/// that is not specified in [Config.cardFilter].
case wrongCardType(_ localizedDescription: String?)


case preflightFiltered(_ error: Error)

/// This error is returned when the scanned card doesn't have some essential fields.
case cardError

Expand Down Expand Up @@ -429,7 +431,8 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {
case .underlying: return 50012
case .userForgotTheCode: return 50013
case .biometricsUnavailable: return 50014

case .preflightFiltered: return 50015

case .wrongInteractionMode: return 50027

// MARK: 9xxxx Errors
Expand Down Expand Up @@ -498,7 +501,7 @@ public enum TangemSdkError: Error, LocalizedError, Encodable {
case .wrongCardType(let localizedDescription): return localizedDescription ?? "error_wrong_card_type".localized
case .accessCodeRequired: return "error_pin_required_format".localized(UserCodeType.accessCode.name)
case .passcodeRequired: return "error_pin_required_format".localized(UserCodeType.passcode.name)
case .underlying(let error): return error.localizedDescription
case .underlying(let error), .preflightFiltered(let error): return error.localizedDescription
case .fileNotFound: return "error_file_not_found".localized
case .walletNotFound: return "wallet_not_found".localized
case .wrongAccessCode: return "error_wrong_pin_format".localized(UserCodeType.accessCode.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ public class BackupService: ObservableObject {
currentCommand = command

sdk.startSession(with: command,
filter: nil,
initialMessage: Message(header: nil,
body: "backup_add_backup_card_message".localized)) {[weak self] result in
guard let self = self else { return }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class PersonalizeCommand: Command {
}

public func run(in session: CardSession, completion: @escaping CompletionResult<Card>) {
let read = PreflightReadTask(readMode: .readCardOnly, cardId: nil) //We have to run preflight read ourseleves to catch the notPersonalized error
let read = PreflightReadTask(readMode: .readCardOnly, filter: nil) //We have to run preflight read ourseleves to catch the notPersonalized error
read.run(in: session) { readResult in
switch readResult {
case .success:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// CardIdPreflightReadFilter.swift
// TangemSdk
//
// Created by Alexander Osokin on 09.11.2023.
// Copyright © 2023 Tangem AG. All rights reserved.
//

import Foundation

@available(iOS 13.0, *)
struct CardIdPreflightReadFilter: PreflightReadFilter {
private let expectedCardId: String

init(cardId: String) {
expectedCardId = cardId
}

func onCardRead(_ card: Card, environment: SessionEnvironment) throws {
if expectedCardId.caseInsensitiveCompare(card.cardId) == .orderedSame {
return
}

let formatter = CardIdFormatter(style: environment.config.cardIdDisplayFormat)
let expectedCardIdFormatted = formatter.string(from: expectedCardId)
throw TangemSdkError.wrongCardNumber(expectedCardId: expectedCardIdFormatted)
}

func onFullCardRead(_ card: Card, environment: SessionEnvironment) throws {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// PreflightReadFilter.swift
// TangemSdk
//
// Created by Alexander Osokin on 30.10.2023.
// Copyright © 2023 Tangem AG. All rights reserved.
//

import Foundation

@available(iOS 13.0, *)
/// Use this filter to filter out cards on preflight read stage. If preflight mode is set to `readCardOnly` or `fullCardRead`. `HandleErrors` flag must be switched on.
public protocol PreflightReadFilter {
/// This method calls right after public information is read. User code is not required. If preflight mode is set to `readCardOnly` or `fullCardRead`
/// - Parameter card: The card that was read
/// - Parameter environment: Current environment
/// - Throws: Throw an error with a localized message to the user, if the card should not be worked with.
func onCardRead(_ card: Card, environment: SessionEnvironment) throws

/// This method calls right after full card information is read. User code is required. If preflight mode is set to `fullCardRead`
/// - Parameter card: The card that was read
/// - Parameter environment: Current environment
/// - Throws: Throw an error with a localized message to the user, if the card should not be worked with.
func onFullCardRead(_ card: Card, environment: SessionEnvironment) throws
}
Loading
Loading