Skip to content
Merged
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
15 changes: 10 additions & 5 deletions .github/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ when implementing a system that performs the communication between an iOS based
and a German Health Card (elektronische Gesundheitskarte) using an NFC, Blue Tooth oder USB interface.

This document describes the functionalitiy and structure of OpenHealthCardKit.

== API Documentation

Generated API docs are available at https://gematik.github.io/ref-OpenHealthCardKit.

== Getting Started

OpenHealthCardKit requires Swift 5.6.
Expand All @@ -32,7 +34,7 @@ OpenHealthCardKit requires Swift 5.6.

- **Swift Package Manager:** Put this in your `Package.swift`:

`.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.6.0"),`
.package(url: "https://github.com/gematik/ref-OpenHealthCardKit", from: "5.6.0"),

- **Carthage:** Put this in your `Cartfile`:

Expand All @@ -57,12 +59,13 @@ OpenHealthCardKit consists of the submodules
- HealthCardControl
- NFCCardReaderProvider

As a reference for each submodule see also the `IntegrationTests`.
Also see a https://github.com/gematik/ref-OpenHealthCardApp-iOS[Demo App] on GitHub using this framework.
As a reference for the usage of each submodule see also the `IntegrationTests`.

[#CardReaderProviderApi]
=== CardReaderProviderApi

(Smart)CardReader protocols for interacting with `HealthCardAccess`.

[#HealthCardAccess]
=== HealthCardAccess
This library contains the classes for cards, commands, card file systems and error handling.
Expand Down Expand Up @@ -124,7 +127,7 @@ In the next example we use a `HealthCard` object representing an eGK (elektronis
as one kind of a `HealthCardType` implementing the `CardType` protocol and then send the command to the card (or card's channel):
[source,swift]
----
let healthCardResponse = try await selectEsignCommand.transmit(to: Self.healthCard)
let healthCardResponse = try await selectEsignCommand.transmitAsync(to: Self.healthCard)
guard healthCardResponse.responseStatus == ResponseStatus.success else {
throw HealthCard.Error.operational // TO-DO: handle this or throw a meaningful Error
}
Expand Down Expand Up @@ -199,6 +202,7 @@ readCertificate
}
)
----

[#HealthCardControl]
=== HealthCardControl

Expand All @@ -223,7 +227,7 @@ Take the necessary preparatory steps for signing a challenge on the Health Card,
let challenge = Data([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8])
let format2Pin = try Format2Pin(pincode: "123456")
_ = try await Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome)
let signResponse = try await Self.healthCard.sign(data: challenge)
let signResponse = try await Self.healthCard.signAsync(data: challenge)
expect(signResponse.responseStatus) == ResponseStatus.success
----

Expand All @@ -243,6 +247,7 @@ let secureMessaging = try await KeyAgreement.Algorithm.idPaceEcdhGmAesCbcCmac128

See the integration tests link:include::{integrationtestdir}/HealthCardControl/[IntegrationTests/HealthCardControl/]
for more already implemented use cases.

[#NFCCardReaderProvider]
=== NFCCardReaderProvider

Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ fastlane/test_output

iOSInjectionProject/

CardSimulationTestKit
devops

jenkinsfiles

4 changes: 4 additions & 0 deletions .spi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version: 1
builder:
configs:
- documentation_targets: [HealthCardControl, HealthCardAccess, NFCCardReaderProvider, CardReaderProviderApi]
14 changes: 14 additions & 0 deletions CardSimulationTestKit/.jazzy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
output: docs
author: gematik GmbH
author_url: http://www.gematik.de
exclude: /*/internal*
# module references the 'Documentation' targets PRODUCT_NAME
module: CardSimulationTestKit
github_url: https://gematik.github.io
theme: jony
swift_build_tool: xcodebuild
xcodebuild_arguments:
- "-project"
- 'CardSimulationTestKit.xcodeproj'
- "-scheme"
- 'Documentation'
59 changes: 59 additions & 0 deletions CardSimulationTestKit/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
opt_in_rules:
- attributes
- empty_count
- force_unwrapping
- unneeded_parentheses_in_closure_argument
- unavailable_function
- trailing_closure
- strict_fileprivate
- sorted_imports
- sorted_first_last
- single_test_class
- required_enum_case
- redundant_type_annotation
- redundant_nil_coalescing
- prohibited_super_call
- override_in_extension
- overridden_super_call
- operator_usage_whitespace
- no_extension_access_modifier
- multiline_function_chains
- multiline_arguments
- modifier_order
- missing_docs
- lower_acl_than_parent
- literal_expression_end_indentation
- first_where
- file_name
- fatal_error_message
- explicit_init
- empty_string
- discouraged_optional_collection
- closure_end_indentation
- file_header
excluded: # paths to ignore during linting. Takes precedence over `included`.
- Package.swift
- .build/
- vendor/
- Carthage/
custom_rules:
nimble_fail_with_description:
included: ".*Test\\.swift"
name: "Fail with description"
regex: "(Nimble.fail\\(\\))"
message: "Failures need a description"
severity: warning
must_not_contain_author:
included:
- ".*Test\\.swift"
- ".*Sources\\.swift"
name: "must not contain author"
regex: "(\/\/[[:space:]]*Created by)"
message: "Source must not contain author"
severity: warning

file_header:
required_pattern: |
\/\/
\/\/ Copyright \(c\) \d{4} gematik GmbH
\/\/
164 changes: 164 additions & 0 deletions CardSimulationTestKit/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
= CardSimulationTestKit

NOTE: This framework rather is meant to be used for gematik-internal development purposes (since the actual CardSimulation application can neither be open sourced nor provided as of now). We publish this code anyway for further reference/usage.

CardSimulationTestKit provides an easy-to-use interface for testing against a German Health Card Simulator.
It comes bundled with

* `CardSimulationLoader`: loads and starts a card simulation as a Java process listening on a TCP port
* `CardSimulationCardReaderProvider`: implements the `CardReaderProviderApi` interface
* `CardSimulationTerminalTestCase`: provides a fully initialized and functional `HealthCard` object to send commands to and receive responses from.


The intended usage of this project is to ease integration and use the G2-Kartensimulation with Swift projects, specifically the test cases in this project.
This guide is separated in two (2) main parts. Describing

. the usage of the `CardSimulation-Loader` framework (How-to) and
. how to maintain the technical consideration(s) and implementation(s).

== Frameworks

=== CardSimulation-Loader

The sole purpose of this framework is to launch and monitor a G2-Kartensimulation Java process.
For detailed usage information see the inlined documentation on `SimulationManager`
and `SimulationRunner`.

*Starting the simulator*:

Of course the best way to find out how-to use the CardSimulation-Loader is by checking the `SimulationManagerTest` and `SimulationRunnerTest` to see their intended and tested use-cases.
Next to checking the test-cases you also find some (example) configuration files in the _Configuration.bundle_ file.

In general, you would prepare such a *card-configuration* XML as in the Configuration.bundle and pass it to the `SimulationManager.shared` by invoking its:

[source,Swift]
----
func createSimulation(
configFile: URL,
preprocessor manipulators: [XMLPathManipulator] = [],
simulatorVersion: String = "2.7.6-352",
simulatorDirectory: String = "simulator",
)
----

Note: you can specify the G2-Kartensimulation version it needs to download/use.

The returned `SimulationRunnerType` can be used to monitor the newly started G2-Kartensimulation instance. To - for instance - figure out on which TLV-port the simulator is registered, just check the `SimulationRunnerType.mode`. When `running` the TLV TCP/IP port is projected there. And for convenience reasons made available through `var tlvPort: Int?` on SimulationRunnerType(s).

This SimulationRunnerType instance will need a CardTerminalControllerType to expose this G2-Kartensimulation virtual `HealthCard` to the HealthCardAccess/Control realm.

*Example*:

[source,Swift]
----
/// Read configFile from included Resources Bundle
let simulatorConfig = Bundle(for: MyClass.self)
.resourceFilePath(in: "Configuration", for: "configuration_EGKG2_80276883110000017222_gema5_TCP.xml")
.asURL
/// Launch a G2-Kartensimulation with this configuration file
let runner = try SimulationManager.shared.startSimulation(
configFile: simulatorConfig,
preprocessor: [
XMLPathManipulatorHolder.TLVPortManipulator(port: "0"),
XMLPathManipulatorHolder.RelativeToAbsolutePathManipulator(with: XMLPathManipulatorHolder.CardConfigFileXMLPath, absolutePath: simulatorConfig.deletingLastPathComponent()),
XMLPathManipulatorHolder.RelativeToAbsolutePathManipulator(with: XMLPathManipulatorHolder.ChannelConfigFileXMLPath, absolutePath: simulatorConfig.deletingLastPathComponent())
],
waitUntilLaunched: true
)

// ... Do amazing things with the runner

/// Stop the runner when done
runner.stop(waitUntilTerminated: true)
----

==== Technical overview

As described in the previous section(s) the CardSimulationLoader provides an easy-to-use API to launch and manage a G2-Kartensimulation.
In order to achieve this we need to combine some various technologies/environments (read: Nexus <--> Java <--> Swift -> CardSimulationLoader API).

The main components for this project to work:

* Download G2-Kartensimulation Nexus artifacts
* Launch and monitor Java Process

These two (2) steps are taken care of when using the `SimulationManager` to launch a simulation.

==== Maven step

The `SimulationManager` reads the `pom.xml` and executes a shell script to run `mvn dependency:copy-dependencies`.
And puts these artifacts in the same transient environment to be cleaned (manually) by calling `SimulationManager.clean` upon
finishing with the simulator(s). Reason for this is to not download the artifacts for every simulator instance in case they
are launch sequentially - which is reasonable to assume.

==== Java process

When the artifacts are in place, the `SimulationRunner` creates a JavaProcess that will be launched/forked in a separate process.
And monitors this process by reading/parsing the `stdout` and `stderr` to detect the tlv-port number and successful initialization.

To start developing the project follow the Project Setup section below 👇.

=== CardSimulation-CardReaderProvider

CardTerminalProvider for communication with G2-Kartensimulation

=== CardSimulationTerminalTestCase

CardSimulationTerminalTestCase provides a fully initialized and functional `HealthCard` object to send commands
against and receive responses from.

== Getting Started

CardSimulationLoader requires Swift 5.1.

=== Usage

In your Test class, derive from the `CardSimulationTerminalTestCase` which itself is a `XCTestCase`.
You then have a `HealthCard` and a `CardTerminal` object directly link to an up and running CardSimulation at your disposal.

[source,Swift]
----
final class SelectCommandIntegrationTest: CardSimulationTerminalTestCase {
func testSelectRoot() {
let healthCard = CardSimulationTerminalTestCase.healthCard
HealthCardCommand.Select.selectRoot()
.execute(on: healthCard)
.run(on: Executor.trampoline)
}
}
----

CardSimulationTestKit comes with various CardImage configuration files.
You can choose between the following images

* configuration_EGK_G2_1_80276883110000095711_GuD_TCP.xml (default)
* configuration_EGK_G2_1_ecc.xml
* configuration_EGKG2_80276883110000017222_gema5_TCP.xml
* configuration_HBA_G2_1_80276883110000205690_gema5_TCP.xml
* configuration_HBAG2_80276883110000017289_gema5_TCP.xml
* configuration_TLK_COS_image-kontaktlos128.xml

by overwriting the `class func configFile() -> URL?` like this:

[source,Swift]
----
final class SelectCommandIntegrationTest: CardSimulationTerminalTestCase {
override class func configFile() -> URL? {
let bundle = Bundle(for: CardSimulationTerminalTestCase.self)
let path = bundle.resourceFilePath(in: "Resources", for: "Configuration/configuration_EGK_G2_1_ecc.xml")
return path.asURL
}
}
----
or bring your own image:

[source,Swift]
----
final class SelectCommandIntegrationTest: CardSimulationTerminalTestCase {
override class func configFile() -> URL? {
// this assumes, your use a test class and have a resource bundle called "Resources2.bundle"
let bundle = Bundle(for: self)
let path = bundle.testResourceFilePath(in: "Resources2", for: "Configuration/configuration_EGK_G2_1_ecc.xml")
return path.asURL
}
}
22 changes: 22 additions & 0 deletions CardSimulationTestKit/Resources/AEXMLExt_Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
22 changes: 22 additions & 0 deletions CardSimulationTestKit/Resources/CardSimulationLoader_Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
Loading