Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
14 changes: 7 additions & 7 deletions FullStackTests/Tests/ConnectionFullStackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ struct ConnectionFullStackTests {

typealias Connection = USBSmartCardConnection

@Test("Single Connection")
@Test("Single Connection", .timeLimit(.minutes(1)))
func singleConnection() async throws {
let connection = try await Connection.connection()
#expect(true, "✅ Got connection \(connection)")
await connection.close(error: nil)
}

@Test("Serial Connections")
@Test("Serial Connections", .timeLimit(.minutes(1)))
func serialConnections() async throws {
let firstConnection = try await Connection.connection()
#expect(true, "✅ Got first connection \(firstConnection)")
Expand Down Expand Up @@ -59,7 +59,7 @@ struct ConnectionFullStackTests {
await secondConnection.close(error: nil)
}

@Test("Connection Cancellation")
@Test("Connection Cancellation", .timeLimit(.minutes(1)))
func connectionCancellation() async {
let task1 = Task {
try await Connection.connection()
Expand Down Expand Up @@ -90,7 +90,7 @@ struct ConnectionFullStackTests {
await connections.first?.close(error: nil)
}

@Test("Send Manually")
@Test("Send Manually", .timeLimit(.minutes(1)))
func sendManually() async throws {
let connection = try await Connection.connection()
// Select Management application
Expand Down Expand Up @@ -142,15 +142,15 @@ struct ConnectionFullStackTests {
@Suite("NFC Full Stack Tests", .serialized)
struct NFCFullStackTests {

@Test("NFC Alert Message")
@Test("NFC Alert Message", .timeLimit(.minutes(1)))
func nfcAlertMessage() async throws {
let connection = try await TestableConnections.create(with: .nfc(alertMessage: "Test Alert Message"))
await connection.nfcConnection?.setAlertMessage("Updated Alert Message")
try? await Task.sleep(for: .seconds(1))
await connection.nfcConnection?.close(message: "Closing Alert Message")
}

@Test("NFC Closing Error Message")
@Test("NFC Closing Error Message", .timeLimit(.minutes(1)))
func nfcClosingErrorMessage() async throws {
let connection = try await TestableConnections.create(with: .nfc(alertMessage: "Test Alert Message"))
await connection.close(error: nil)
Expand All @@ -162,7 +162,7 @@ struct NFCFullStackTests {
@Suite("SmartCard Connection Full Stack Tests", .serialized)
struct SmartCardConnectionFullStackTests {

@Test("SmartCard Connection With Slot")
@Test("SmartCard Connection With Slot", .timeLimit(.minutes(1)))
func smartCardConnectionWithSlot() async throws {
let allSlots = try await USBSmartCardConnection.availableSlots
allSlots.enumerated().forEach { index, slot in
Expand Down
16 changes: 8 additions & 8 deletions Samples/OATHSample/OATHSample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
51A1AC10273D537500F999A4 /* OATHListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1AC0F273D537500F999A4 /* OATHListView.swift */; };
51A1AC12273D537800F999A4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51A1AC11273D537800F999A4 /* Assets.xcassets */; };
51A1AC15273D537800F999A4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51A1AC14273D537800F999A4 /* Preview Assets.xcassets */; };
6C54A5412E40BA1E00409BD5 /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C54A5402E40BA1E00409BD5 /* ConnectionManager.swift */; };
6C8465302DADB11200788FB7 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C84652F2DADB11200788FB7 /* YubiKit */; };
6C8465332DADB13300788FB7 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C8465322DADB13300788FB7 /* YubiKit */; };
B41B61882743FE18004C37BF /* OATHListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B41B61872743FE18004C37BF /* OATHListModel.swift */; };
B456E217274E750D004471DE /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B456E216274E750D004471DE /* SettingsView.swift */; };
B456E219274FC967004471DE /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B456E218274FC967004471DE /* SettingsModel.swift */; };
B456E219274FC967004471DE /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = B456E218274FC967004471DE /* Model.swift */; };
B4F937582B5150D60007D394 /* YubiKit in Frameworks */ = {isa = PBXBuildFile; productRef = B4F937572B5150D60007D394 /* YubiKit */; };
/* End PBXBuildFile section */

Expand All @@ -26,12 +26,12 @@
51A1AC11273D537800F999A4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
51A1AC14273D537800F999A4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
51A1AC29273D5D0A00F999A4 /* YubiKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = YubiKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B41B61872743FE18004C37BF /* OATHListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OATHListModel.swift; sourceTree = "<group>"; };
6C54A5402E40BA1E00409BD5 /* ConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = "<group>"; };
B4451EFB275F924D002690BB /* OATHSample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OATHSample.entitlements; sourceTree = "<group>"; };
B4451EFC275F924D002690BB /* ExternalAccessory.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ExternalAccessory.framework; path = System/Library/Frameworks/ExternalAccessory.framework; sourceTree = SDKROOT; };
B4451EFE275F940E002690BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
B456E216274E750D004471DE /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
B456E218274FC967004471DE /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = "<group>"; };
B456E218274FC967004471DE /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -72,9 +72,9 @@
B4451EFB275F924D002690BB /* OATHSample.entitlements */,
51A1AC0D273D537500F999A4 /* OATHSampleApp.swift */,
51A1AC0F273D537500F999A4 /* OATHListView.swift */,
B41B61872743FE18004C37BF /* OATHListModel.swift */,
B456E216274E750D004471DE /* SettingsView.swift */,
B456E218274FC967004471DE /* SettingsModel.swift */,
B456E218274FC967004471DE /* Model.swift */,
6C54A5402E40BA1E00409BD5 /* ConnectionManager.swift */,
51A1AC11273D537800F999A4 /* Assets.xcassets */,
51A1AC13273D537800F999A4 /* Preview Content */,
);
Expand Down Expand Up @@ -176,9 +176,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B456E219274FC967004471DE /* SettingsModel.swift in Sources */,
B456E219274FC967004471DE /* Model.swift in Sources */,
6C54A5412E40BA1E00409BD5 /* ConnectionManager.swift in Sources */,
B456E217274E750D004471DE /* SettingsView.swift in Sources */,
B41B61882743FE18004C37BF /* OATHListModel.swift in Sources */,
51A1AC10273D537500F999A4 /* OATHListView.swift in Sources */,
51A1AC0E273D537500F999A4 /* OATHSampleApp.swift in Sources */,
);
Expand Down
109 changes: 109 additions & 0 deletions Samples/OATHSample/OATHSample/ConnectionManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright Yubico AB
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation
import SwiftUI
import YubiKit

@MainActor
final class ConnectionManager: ObservableObject {

static let shared = ConnectionManager()

@Published private(set) var wiredConnection: SmartCardConnection?
#if os(iOS)
@Published private(set) var nfcConnection: NFCSmartCardConnection?
#endif

@Published var error: Error?

private var wiredConnectionTask: Task<Void, Never>?

private init() {
startWiredConnection()
}

private func startWiredConnection() {
wiredConnectionTask = Task { @MainActor in
while !Task.isCancelled {
do {
error = nil
guard !Task.isCancelled else { return }

let newConnection = try await WiredSmartCardConnection.connection()
guard !Task.isCancelled else { return }

wiredConnection = newConnection

let closeError = await newConnection.connectionDidClose()

wiredConnection = nil

if let closeError = closeError {
error = closeError
}
} catch {
// Ignore cancellation errors
if let cancellationError = error as? CancellationError { return }
self.error = error
}
}
}
}

#if os(iOS)
func requestNFCConnection() async {
error = nil

do {
nfcConnection = try await NFCSmartCardConnection.connection() as? NFCSmartCardConnection
} catch {
self.error = error
}
}

func closeNFCConnection(message: String? = nil) async {
error = nil

await nfcConnection?.close(message: message)
}
#endif
}

extension SmartCardConnection {
var connectionType: String {
switch self {
#if os(iOS)
case _ as NFCSmartCardConnection:
return "NFC"
case _ as LightningSmartCardConnection:
return "Lightning"
#endif
case _ as USBSmartCardConnection:
return "USB"
default:
return "Unknown"
}
}
}

extension Optional where Wrapped == SmartCardConnection {
var connectionType: String {
guard let connection = self else {
return "No Connection"
}

return connection.connectionType
}
}
71 changes: 71 additions & 0 deletions Samples/OATHSample/OATHSample/Model.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright Yubico AB
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation
import YubiKit

struct Account: Identifiable {
var id = UUID()
let label: String
let code: String?
let issuer: String?
let type: OATHSession.CredentialType
}

@MainActor
class Model: ObservableObject {

@Published private(set) var accounts = [Account]()
@Published private(set) var keyVersion: String?
@Published private(set) var connectionType: String?
@Published var error: Error?

func update(using connection: SmartCardConnection) async {
await calculateCodes(using: connection)
await getKeyVersion(using: connection)
connectionType = connection.connectionType
}

func clear() {
accounts = []
keyVersion = nil
connectionType = nil
}

private func getKeyVersion(using connection: SmartCardConnection) async {
do {
let session = try await ManagementSession.session(withConnection: connection)
self.keyVersion = session.version.description
} catch {
self.error = error
}
}

private func calculateCodes(using connection: SmartCardConnection) async {
do {
let session = try await OATHSession.session(withConnection: connection)
let result = try await session.calculateCodes()
accounts = result.map { credential, code in
Account(
label: credential.label,
code: code?.code,
issuer: credential.issuer,
type: credential.type
)
}
} catch {
self.error = error
}
}
}
Loading
Loading