diff --git a/PatientScannerDemo.xcodeproj/project.pbxproj b/PatientScannerDemo.xcodeproj/project.pbxproj index 7f197b0..f5127a5 100644 --- a/PatientScannerDemo.xcodeproj/project.pbxproj +++ b/PatientScannerDemo.xcodeproj/project.pbxproj @@ -7,13 +7,29 @@ objects = { /* Begin PBXBuildFile section */ + CE13CF00262DCC180070C80E /* FloatingPanel in Frameworks */ = {isa = PBXBuildFile; productRef = CE13CEFF262DCC180070C80E /* FloatingPanel */; }; + CE13CF05262DCDCD0070C80E /* CertificateViewer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE13CF04262DCDCD0070C80E /* CertificateViewer.storyboard */; }; + CE13CF0A262DCDDA0070C80E /* CertificateViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13CF09262DCDDA0070C80E /* CertificateViewer.swift */; }; + CE13CF0F262DD0D80070C80E /* FullFloatingPanelLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13CF0E262DD0D80070C80E /* FullFloatingPanelLayout.swift */; }; + CE13CF23262DDF810070C80E /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13CF22262DDF810070C80E /* RoundedButton.swift */; }; + CE157F81262E1F7A00FE4821 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE157F80262E1F7A00FE4821 /* Date.swift */; }; + CE157F87262E24DE00FE4821 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = CE157F86262E24DE00FE4821 /* SwiftyJSON */; }; + CE157F8D262E24F900FE4821 /* HCert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE157F8C262E24F900FE4821 /* HCert.swift */; }; + CE157F9B262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE157F9A262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift */; }; CE1BDF99262A4CD600766F97 /* X509.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1BDF98262A4CD600766F97 /* X509.swift */; }; CE3CC93C2628A7820079FB78 /* ASN1.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3CC93B2628A7820079FB78 /* ASN1.swift */; }; CE3CC9442628C2130079FB78 /* CBOR.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3CC9432628C2130079FB78 /* CBOR.swift */; }; + CE44798D26304D8F009A836B /* JSONSchema in Frameworks */ = {isa = PBXBuildFile; productRef = CE44798C26304D8F009A836B /* JSONSchema */; }; + CE44799226306C86009A836B /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE44799126306C86009A836B /* String.swift */; }; + CE44799726306C9B009A836B /* Data+Base45.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE44799626306C9B009A836B /* Data+Base45.swift */; }; CE7DE7FA2625EF18007E6694 /* SwiftCBOR in Frameworks */ = {isa = PBXBuildFile; productRef = CE7DE7F92625EF18007E6694 /* SwiftCBOR */; }; + CEA1555D262F63B30024B7AC /* EuDgcSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA1555C262F63B30024B7AC /* EuDgcSchema.swift */; }; + CEA15563262F6DAB0024B7AC /* ChildDismissedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA15562262F6DAB0024B7AC /* ChildDismissedDelegate.swift */; }; + CEA1556B262F784E0024B7AC /* SelfSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */; }; + CEA15570262F79DE0024B7AC /* InfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA1556F262F79DE0024B7AC /* InfoCell.swift */; }; CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D6EB261F8D2700715333 /* AppDelegate.swift */; }; CEA6D6EE261F8D2700715333 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D6ED261F8D2700715333 /* SceneDelegate.swift */; }; - CEA6D6F0261F8D2700715333 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D6EF261F8D2700715333 /* ViewController.swift */; }; + CEA6D6F0261F8D2700715333 /* Scan.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D6EF261F8D2700715333 /* Scan.swift */; }; CEA6D6F3261F8D2700715333 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEA6D6F1261F8D2700715333 /* Main.storyboard */; }; CEA6D6F5261F8D2900715333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEA6D6F4261F8D2900715333 /* Assets.xcassets */; }; CEA6D6F8261F8D2900715333 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEA6D6F6261F8D2900715333 /* LaunchScreen.storyboard */; }; @@ -47,13 +63,26 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + CE13CF04262DCDCD0070C80E /* CertificateViewer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = CertificateViewer.storyboard; sourceTree = ""; }; + CE13CF09262DCDDA0070C80E /* CertificateViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateViewer.swift; sourceTree = ""; }; + CE13CF0E262DD0D80070C80E /* FullFloatingPanelLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullFloatingPanelLayout.swift; sourceTree = ""; }; + CE13CF22262DDF810070C80E /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; + CE157F80262E1F7A00FE4821 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; + CE157F8C262E24F900FE4821 /* HCert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HCert.swift; sourceTree = ""; }; + CE157F9A262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftCBOR.CBOR.swift; sourceTree = ""; }; CE1BDF98262A4CD600766F97 /* X509.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = X509.swift; sourceTree = ""; }; CE3CC93B2628A7820079FB78 /* ASN1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASN1.swift; sourceTree = ""; }; CE3CC9432628C2130079FB78 /* CBOR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBOR.swift; sourceTree = ""; }; + CE44799126306C86009A836B /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; + CE44799626306C9B009A836B /* Data+Base45.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Base45.swift"; sourceTree = ""; }; + CEA1555C262F63B30024B7AC /* EuDgcSchema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EuDgcSchema.swift; sourceTree = ""; }; + CEA15562262F6DAB0024B7AC /* ChildDismissedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildDismissedDelegate.swift; sourceTree = ""; }; + CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizedTableView.swift; sourceTree = ""; }; + CEA1556F262F79DE0024B7AC /* InfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoCell.swift; sourceTree = ""; }; CEA6D6E8261F8D2700715333 /* PatientScannerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PatientScannerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CEA6D6EB261F8D2700715333 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CEA6D6ED261F8D2700715333 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - CEA6D6EF261F8D2700715333 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + CEA6D6EF261F8D2700715333 /* Scan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scan.swift; sourceTree = ""; }; CEA6D6F2261F8D2700715333 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CEA6D6F4261F8D2900715333 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CEA6D6F7261F8D2900715333 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -79,7 +108,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CE157F87262E24DE00FE4821 /* SwiftyJSON in Frameworks */, + CE44798D26304D8F009A836B /* JSONSchema in Frameworks */, CE7DE7FA2625EF18007E6694 /* SwiftCBOR in Frameworks */, + CE13CF00262DCC180070C80E /* FloatingPanel in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -100,6 +132,92 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + CE13CF19262DDE200070C80E /* Storyboards */ = { + isa = PBXGroup; + children = ( + CEA6D6F6261F8D2900715333 /* LaunchScreen.storyboard */, + CEA6D6F1261F8D2700715333 /* Main.storyboard */, + CE13CF04262DCDCD0070C80E /* CertificateViewer.storyboard */, + ); + path = Storyboards; + sourceTree = ""; + }; + CE13CF1A262DDE330070C80E /* Services */ = { + isa = PBXGroup; + children = ( + CEC2C4C02625ED030056E406 /* JWK.swift */, + CEC2C4BF2625ED030056E406 /* ZLib.swift */, + CEC2C4C12625ED030056E406 /* Base45.swift */, + CEFAD86C2625F164009AFEF9 /* Signature.swift */, + CE3CC93B2628A7820079FB78 /* ASN1.swift */, + CEFAD87926271414009AFEF9 /* COSE.swift */, + CE3CC9432628C2130079FB78 /* CBOR.swift */, + CE1BDF98262A4CD600766F97 /* X509.swift */, + ); + path = Services; + sourceTree = ""; + }; + CE13CF1B262DDE540070C80E /* Extensions */ = { + isa = PBXGroup; + children = ( + CEFAD88626271B9A009AFEF9 /* Data+hexString.swift */, + CEFAD8712625F29E009AFEF9 /* String+JSON.swift */, + CE157F80262E1F7A00FE4821 /* Date.swift */, + CE157F9A262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift */, + CE44799126306C86009A836B /* String.swift */, + CE44799626306C9B009A836B /* Data+Base45.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + CE13CF1C262DDE600070C80E /* ViewControllers */ = { + isa = PBXGroup; + children = ( + CE13CF09262DCDDA0070C80E /* CertificateViewer.swift */, + CEA6D6EF261F8D2700715333 /* Scan.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + CE13CF1D262DDE730070C80E /* Components */ = { + isa = PBXGroup; + children = ( + CE13CF0E262DD0D80070C80E /* FullFloatingPanelLayout.swift */, + CE13CF22262DDF810070C80E /* RoundedButton.swift */, + CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */, + CEA1556F262F79DE0024B7AC /* InfoCell.swift */, + ); + path = Components; + sourceTree = ""; + }; + CE13CF1E262DDE800070C80E /* SupportingFiles */ = { + isa = PBXGroup; + children = ( + CEA6D6EB261F8D2700715333 /* AppDelegate.swift */, + CEA6D6ED261F8D2700715333 /* SceneDelegate.swift */, + CEA6D6F4261F8D2900715333 /* Assets.xcassets */, + CEA6D6F9261F8D2900715333 /* Info.plist */, + CEA1555C262F63B30024B7AC /* EuDgcSchema.swift */, + ); + path = SupportingFiles; + sourceTree = ""; + }; + CE157F8B262E24EC00FE4821 /* Models */ = { + isa = PBXGroup; + children = ( + CE157F8C262E24F900FE4821 /* HCert.swift */, + ); + path = Models; + sourceTree = ""; + }; + CEA15561262F6DA10024B7AC /* Protocols */ = { + isa = PBXGroup; + children = ( + CEA15562262F6DAB0024B7AC /* ChildDismissedDelegate.swift */, + ); + path = Protocols; + sourceTree = ""; + }; CEA6D6DF261F8D2700715333 = { isa = PBXGroup; children = ( @@ -123,23 +241,14 @@ CEA6D6EA261F8D2700715333 /* PatientScannerDemo */ = { isa = PBXGroup; children = ( - CEFAD8712625F29E009AFEF9 /* String+JSON.swift */, - CEC2C4C12625ED030056E406 /* Base45.swift */, - CEC2C4C02625ED030056E406 /* JWK.swift */, - CEC2C4BF2625ED030056E406 /* ZLib.swift */, - CEA6D6EB261F8D2700715333 /* AppDelegate.swift */, - CEA6D6ED261F8D2700715333 /* SceneDelegate.swift */, - CEA6D6EF261F8D2700715333 /* ViewController.swift */, - CEA6D6F1261F8D2700715333 /* Main.storyboard */, - CEA6D6F4261F8D2900715333 /* Assets.xcassets */, - CEA6D6F6261F8D2900715333 /* LaunchScreen.storyboard */, - CEA6D6F9261F8D2900715333 /* Info.plist */, - CEFAD86C2625F164009AFEF9 /* Signature.swift */, - CE3CC93B2628A7820079FB78 /* ASN1.swift */, - CEFAD87926271414009AFEF9 /* COSE.swift */, - CEFAD88626271B9A009AFEF9 /* Data+hexString.swift */, - CE3CC9432628C2130079FB78 /* CBOR.swift */, - CE1BDF98262A4CD600766F97 /* X509.swift */, + CEA15561262F6DA10024B7AC /* Protocols */, + CE157F8B262E24EC00FE4821 /* Models */, + CE13CF1E262DDE800070C80E /* SupportingFiles */, + CE13CF1D262DDE730070C80E /* Components */, + CE13CF1C262DDE600070C80E /* ViewControllers */, + CE13CF1B262DDE540070C80E /* Extensions */, + CE13CF1A262DDE330070C80E /* Services */, + CE13CF19262DDE200070C80E /* Storyboards */, ); path = PatientScannerDemo; sourceTree = ""; @@ -181,6 +290,9 @@ name = PatientScannerDemo; packageProductDependencies = ( CE7DE7F92625EF18007E6694 /* SwiftCBOR */, + CE13CEFF262DCC180070C80E /* FloatingPanel */, + CE157F86262E24DE00FE4821 /* SwiftyJSON */, + CE44798C26304D8F009A836B /* JSONSchema */, ); productName = PatientScannerDemo; productReference = CEA6D6E8261F8D2700715333 /* PatientScannerDemo.app */; @@ -255,6 +367,9 @@ mainGroup = CEA6D6DF261F8D2700715333; packageReferences = ( CE7DE7F82625EF18007E6694 /* XCRemoteSwiftPackageReference "SwiftCBOR" */, + CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */, + CE157F85262E24DE00FE4821 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, + CE44798B26304D8F009A836B /* XCRemoteSwiftPackageReference "JSONSchema" */, ); productRefGroup = CEA6D6E9261F8D2700715333 /* Products */; projectDirPath = ""; @@ -272,6 +387,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CE13CF05262DCDCD0070C80E /* CertificateViewer.storyboard in Resources */, CEA6D6F8261F8D2900715333 /* LaunchScreen.storyboard in Resources */, CEA6D6F5261F8D2900715333 /* Assets.xcassets in Resources */, CEA6D6F3261F8D2700715333 /* Main.storyboard in Resources */, @@ -299,19 +415,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CE157F8D262E24F900FE4821 /* HCert.swift in Sources */, CEFAD88726271B9A009AFEF9 /* Data+hexString.swift in Sources */, + CE13CF0A262DCDDA0070C80E /* CertificateViewer.swift in Sources */, CEC2C4C32625ED030056E406 /* JWK.swift in Sources */, CEC2C4C42625ED030056E406 /* Base45.swift in Sources */, CE3CC9442628C2130079FB78 /* CBOR.swift in Sources */, + CE44799226306C86009A836B /* String.swift in Sources */, + CE44799726306C9B009A836B /* Data+Base45.swift in Sources */, + CE13CF0F262DD0D80070C80E /* FullFloatingPanelLayout.swift in Sources */, + CEA1556B262F784E0024B7AC /* SelfSizedTableView.swift in Sources */, + CE157F81262E1F7A00FE4821 /* Date.swift in Sources */, CE1BDF99262A4CD600766F97 /* X509.swift in Sources */, - CEA6D6F0261F8D2700715333 /* ViewController.swift in Sources */, + CEA1555D262F63B30024B7AC /* EuDgcSchema.swift in Sources */, + CEA6D6F0261F8D2700715333 /* Scan.swift in Sources */, CE3CC93C2628A7820079FB78 /* ASN1.swift in Sources */, CEFAD87A26271414009AFEF9 /* COSE.swift in Sources */, CEFAD86D2625F164009AFEF9 /* Signature.swift in Sources */, + CE13CF23262DDF810070C80E /* RoundedButton.swift in Sources */, CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */, + CEA15563262F6DAB0024B7AC /* ChildDismissedDelegate.swift in Sources */, CEFAD8722625F29E009AFEF9 /* String+JSON.swift in Sources */, CEC2C4C22625ED030056E406 /* ZLib.swift in Sources */, + CEA15570262F79DE0024B7AC /* InfoCell.swift in Sources */, CEA6D6EE261F8D2700715333 /* SceneDelegate.swift in Sources */, + CE157F9B262E2A9F00FE4821 /* SwiftCBOR.CBOR.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -418,7 +546,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -473,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -490,7 +618,8 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = KH99XNF745; - INFOPLIST_FILE = PatientScannerDemo/Info.plist; + INFOPLIST_FILE = PatientScannerDemo/SupportingFiles/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -509,7 +638,8 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = KH99XNF745; - INFOPLIST_FILE = PatientScannerDemo/Info.plist; + INFOPLIST_FILE = PatientScannerDemo/SupportingFiles/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -528,7 +658,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = KH99XNF745; - INFOPLIST_FILE = PatientScannerDemoTests/Info.plist; + INFOPLIST_FILE = PatientScannerDemo/SupportingFiles/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -550,7 +680,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = KH99XNF745; - INFOPLIST_FILE = PatientScannerDemoTests/Info.plist; + INFOPLIST_FILE = PatientScannerDemo/SupportingFiles/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -647,6 +777,30 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SCENEE/FloatingPanel"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.3.0; + }; + }; + CE157F85262E24DE00FE4821 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.1; + }; + }; + CE44798B26304D8F009A836B /* XCRemoteSwiftPackageReference "JSONSchema" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/eu-digital-green-certificates/JSONSchema.swift"; + requirement = { + branch = master; + kind = branch; + }; + }; CE7DE7F82625EF18007E6694 /* XCRemoteSwiftPackageReference "SwiftCBOR" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/unrelentingtech/SwiftCBOR"; @@ -658,6 +812,21 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + CE13CEFF262DCC180070C80E /* FloatingPanel */ = { + isa = XCSwiftPackageProductDependency; + package = CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */; + productName = FloatingPanel; + }; + CE157F86262E24DE00FE4821 /* SwiftyJSON */ = { + isa = XCSwiftPackageProductDependency; + package = CE157F85262E24DE00FE4821 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; + productName = SwiftyJSON; + }; + CE44798C26304D8F009A836B /* JSONSchema */ = { + isa = XCSwiftPackageProductDependency; + package = CE44798B26304D8F009A836B /* XCRemoteSwiftPackageReference "JSONSchema" */; + productName = JSONSchema; + }; CE7DE7F92625EF18007E6694 /* SwiftCBOR */ = { isa = XCSwiftPackageProductDependency; package = CE7DE7F82625EF18007E6694 /* XCRemoteSwiftPackageReference "SwiftCBOR" */; diff --git a/PatientScannerDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PatientScannerDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bc6ed56..f915462 100644 --- a/PatientScannerDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/PatientScannerDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,42 @@ { "object": { "pins": [ + { + "package": "FloatingPanel", + "repositoryURL": "https://github.com/SCENEE/FloatingPanel", + "state": { + "branch": null, + "revision": "16fea625be25d9a713630a4a43cbc0778740ebf4", + "version": "2.3.0" + } + }, + { + "package": "JSONSchema", + "repositoryURL": "https://github.com/eu-digital-green-certificates/JSONSchema.swift", + "state": { + "branch": "master", + "revision": "adb480fe1c1e285c131bff465f641aebc986d736", + "version": null + } + }, + { + "package": "PathKit", + "repositoryURL": "https://github.com/kylef/PathKit.git", + "state": { + "branch": null, + "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511", + "version": "1.0.0" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "f79d4ecbf8bc4e1579fbd86c3e1d652fb6876c53", + "version": "0.9.2" + } + }, { "package": "SwiftCBOR", "repositoryURL": "https://github.com/unrelentingtech/SwiftCBOR", @@ -9,6 +45,15 @@ "revision": "668c26fc3373d5f1bccbaad7665ca6048797e324", "version": "0.4.3" } + }, + { + "package": "SwiftyJSON", + "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON", + "state": { + "branch": null, + "revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", + "version": "5.0.1" + } } ] }, diff --git a/PatientScannerDemo.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist b/PatientScannerDemo.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist index 3949b55..1e971f7 100644 --- a/PatientScannerDemo.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/PatientScannerDemo.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,69 @@ orderHint 0 + Playground (Playground) 1.xcscheme + + isShown + + orderHint + 5 + + Playground (Playground) 2.xcscheme + + isShown + + orderHint + 6 + + Playground (Playground).xcscheme + + isShown + + orderHint + 4 + + Spectre (Playground) 1.xcscheme + + isShown + + orderHint + 2 + + Spectre (Playground) 2.xcscheme + + isShown + + orderHint + 3 + + Spectre (Playground) 3.xcscheme + + isShown + + orderHint + 7 + + Spectre (Playground) 4.xcscheme + + isShown + + orderHint + 8 + + Spectre (Playground) 5.xcscheme + + isShown + + orderHint + 9 + + Spectre (Playground).xcscheme + + isShown + + orderHint + 1 + diff --git a/PatientScannerDemo/Base45.swift b/PatientScannerDemo/Base45.swift deleted file mode 100644 index a9ea3e1..0000000 --- a/PatientScannerDemo/Base45.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// Base45.swift -// -// Created by Dirk-Willem van Gulik on 01/04/2021. -// - -import Foundation - -extension String { - enum Base45Error: Error { - case Base64InvalidCharacter - case Base64InvalidLength - } - - public func fromBase45() throws ->Data { - let BASE45_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" - var d = Data() - var o = Data() - - for c in self.uppercased() { - if let at = BASE45_CHARSET.firstIndex(of: c) { - let idx = BASE45_CHARSET.distance(from: BASE45_CHARSET.startIndex, to: at) - d.append(UInt8(idx)) - } else { - throw Base45Error.Base64InvalidCharacter - } - } - for i in stride(from:0, to:d.count, by: 3) { - if (d.count - i < 2) { - throw Base45Error.Base64InvalidLength - } - var x : UInt32 = UInt32(d[i]) + UInt32(d[i+1])*45 - if (d.count - i >= 3) { - x += 45 * 45 * UInt32(d[i+2]) - o.append(UInt8(x / 256)) - } - o.append(UInt8(x % 256)) - } - return o - } -} - -extension String { - subscript(i: Int) -> String { - return String(self[index(startIndex, offsetBy: i)]) - } -} - -extension Data { - public func toBase45()->String { - let BASE45_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" - var o = String() - for i in stride(from:0, to:self.count, by: 2) { - if (self.count - i > 1) { - let x : Int = (Int(self[i])<<8) + Int(self[i+1]) - let e : Int = x / (45*45) - let x2 : Int = x % (45*45) - let d : Int = x2 / 45 - let c : Int = x2 % 45 - o.append(BASE45_CHARSET[c]) - o.append(BASE45_CHARSET[d]) - o.append(BASE45_CHARSET[e]) - } else { - let x2 : Int = Int(self[i]) - let d : Int = x2 / 45 - let c : Int = x2 % 45 - o.append(BASE45_CHARSET[c]) - o.append(BASE45_CHARSET[d]) - } - } - return o - } -} diff --git a/PatientScannerDemo/Components/FullFloatingPanelLayout.swift b/PatientScannerDemo/Components/FullFloatingPanelLayout.swift new file mode 100644 index 0000000..a599406 --- /dev/null +++ b/PatientScannerDemo/Components/FullFloatingPanelLayout.swift @@ -0,0 +1,21 @@ +// +// FullFloatingPanelLayout.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/19/21. +// + +import FloatingPanel + +class FullFloatingPanelLayout: FloatingPanelLayout { + var position: FloatingPanelPosition = .bottom + + var initialState: FloatingPanelState = .full + + var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { + let top = FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea) + return [ + .full: top, + ] + } +} diff --git a/PatientScannerDemo/Components/InfoCell.swift b/PatientScannerDemo/Components/InfoCell.swift new file mode 100644 index 0000000..c72de62 --- /dev/null +++ b/PatientScannerDemo/Components/InfoCell.swift @@ -0,0 +1,18 @@ +// +// InfoCell.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/20/21. +// + +import UIKit + +class InfoCell: UITableViewCell { + @IBOutlet weak var headerLabel: UILabel! + @IBOutlet weak var contentLabel: UILabel! + + func draw(_ info: InfoSection) { + headerLabel?.text = info.header + contentLabel?.text = info.content + } +} diff --git a/PatientScannerDemo/Components/RoundedButton.swift b/PatientScannerDemo/Components/RoundedButton.swift new file mode 100644 index 0000000..2afdded --- /dev/null +++ b/PatientScannerDemo/Components/RoundedButton.swift @@ -0,0 +1,32 @@ +// +// RoundedButton.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/19/21. +// + +import Foundation +import UIKit + +@IBDesignable +class RoundedButton: UIButton { + @IBInspectable var radius: CGFloat = 6.0 { didSet(v) { initialize() } } + @IBInspectable var padding: CGFloat = 4.0 { didSet(v) { initialize() } } + + override init(frame: CGRect) { + super.init(frame: frame) + + initialize() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + + initialize() + } + + func initialize() { + layer.cornerRadius = radius + contentEdgeInsets = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) + } +} diff --git a/PatientScannerDemo/Components/SelfSizedTableView.swift b/PatientScannerDemo/Components/SelfSizedTableView.swift new file mode 100644 index 0000000..81cda48 --- /dev/null +++ b/PatientScannerDemo/Components/SelfSizedTableView.swift @@ -0,0 +1,25 @@ +// +// SelfSizedTableView.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/20/21. +// +// https://dushyant37.medium.com/swift-4-recipe-self-sizing-table-view-2635ac3df8ab +// + +import UIKit + +class SelfSizedTableView: UITableView { + var maxHeight: CGFloat = UIScreen.main.bounds.size.height + + override func reloadData() { + super.reloadData() + self.invalidateIntrinsicContentSize() + self.layoutIfNeeded() + } + + override var intrinsicContentSize: CGSize { + let height = min(contentSize.height, maxHeight) + return CGSize(width: contentSize.width, height: height) + } +} diff --git a/PatientScannerDemo/Extensions/Data+Base45.swift b/PatientScannerDemo/Extensions/Data+Base45.swift new file mode 100644 index 0000000..0105e67 --- /dev/null +++ b/PatientScannerDemo/Extensions/Data+Base45.swift @@ -0,0 +1,34 @@ +// +// Data+Base45.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/21/21. +// + +import Foundation + + +extension Data { + public func toBase45()->String { + var o = String() + for i in stride(from:0, to:self.count, by: 2) { + if (self.count - i > 1) { + let x : Int = (Int(self[i])<<8) + Int(self[i+1]) + let e : Int = x / (45*45) + let x2 : Int = x % (45*45) + let d : Int = x2 / 45 + let c : Int = x2 % 45 + o.append(BASE45_CHARSET[c]) + o.append(BASE45_CHARSET[d]) + o.append(BASE45_CHARSET[e]) + } else { + let x2 : Int = Int(self[i]) + let d : Int = x2 / 45 + let c : Int = x2 % 45 + o.append(BASE45_CHARSET[c]) + o.append(BASE45_CHARSET[d]) + } + } + return o + } +} diff --git a/PatientScannerDemo/Data+hexString.swift b/PatientScannerDemo/Extensions/Data+hexString.swift similarity index 83% rename from PatientScannerDemo/Data+hexString.swift rename to PatientScannerDemo/Extensions/Data+hexString.swift index 00cdd59..f283b8a 100644 --- a/PatientScannerDemo/Data+hexString.swift +++ b/PatientScannerDemo/Extensions/Data+hexString.swift @@ -26,4 +26,8 @@ extension Data { } var uint: [UInt8] { [UInt8](self) } + var hexString: String { + let format = "%02hhx" + return self.map { String(format: format, $0) }.joined() + } } diff --git a/PatientScannerDemo/Extensions/Date.swift b/PatientScannerDemo/Extensions/Date.swift new file mode 100644 index 0000000..42ec82f --- /dev/null +++ b/PatientScannerDemo/Extensions/Date.swift @@ -0,0 +1,55 @@ +// +// Date.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/19/21. +// + +import Foundation + +extension Date { + static func formatter(for locale: String, utcPosix: Bool = true, utc: Bool = false) -> DateFormatter { + let dateTimeFormatter = DateFormatter() + dateTimeFormatter.dateFormat = locale + dateTimeFormatter.timeZone = TimeZone.current + dateTimeFormatter.locale = Locale.current + if utcPosix || utc { + dateTimeFormatter.timeZone = TimeZone(secondsFromGMT: 0) + } + if utcPosix { + dateTimeFormatter.locale = Locale(identifier: "en_US_POSIX") + } + return dateTimeFormatter + } + + static let isoFormatter = formatter(for: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + static let dateFormatter = formatter(for: "yyyy-MM-dd") + + var isoString: String { + Date.isoFormatter.string(from: self) + } + var dateString: String { + Date.dateFormatter.string(from: self) + } + + init?(isoString: String) { + guard let date = Date.isoFormatter.date(from: isoString) else { + return nil + } + self = date + } + init?(dateString: String) { + guard let date = Date.dateFormatter.date(from: dateString) else { + return nil + } + self = date + } + + var localDateString: String { + let formatter = DateFormatter() + formatter.locale = .current + formatter.timeStyle = .none + formatter.dateStyle = .medium + return formatter.string(from: self) + } +} diff --git a/PatientScannerDemo/String+JSON.swift b/PatientScannerDemo/Extensions/String+JSON.swift similarity index 100% rename from PatientScannerDemo/String+JSON.swift rename to PatientScannerDemo/Extensions/String+JSON.swift diff --git a/PatientScannerDemo/Extensions/String.swift b/PatientScannerDemo/Extensions/String.swift new file mode 100644 index 0000000..0adc69e --- /dev/null +++ b/PatientScannerDemo/Extensions/String.swift @@ -0,0 +1,14 @@ +// +// String.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/21/21. +// + +import Foundation + +extension String { + subscript(i: Int) -> String { + return String(self[index(startIndex, offsetBy: i)]) + } +} diff --git a/PatientScannerDemo/Extensions/SwiftCBOR.CBOR.swift b/PatientScannerDemo/Extensions/SwiftCBOR.CBOR.swift new file mode 100644 index 0000000..d240400 --- /dev/null +++ b/PatientScannerDemo/Extensions/SwiftCBOR.CBOR.swift @@ -0,0 +1,62 @@ +// +// SwiftCBOR.CBOR.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/19/21. +// + +import Foundation +import SwiftCBOR + +extension SwiftCBOR.CBOR { + func toString() -> String { + switch self { + case let .byteString(val): + let fallBack = "[" + val.map { + "\($0)" + }.joined(separator: ", ") + "]" + if + let child = try? SwiftCBOR.CBOR.decode(val), + case .map(_) = child + { + return child.toString() + } + return fallBack + case let .unsignedInt(val): + return "\(val)" + case let .negativeInt(val): + return "-\(val + 1)" + case let .utf8String(val): + return "\"\(val)\"" + case let .array(vals): + var s = "" + for val in vals { + s += (s.isEmpty ? "" : ", ") + val.toString() + } + return "[\(s)]" + case let .map(vals): + var s = "" + for pair in vals { + var key = pair.key.toString() + key = key.trimmingCharacters(in: ["\""]) + key = "\"\(key)\"" + s += (s.isEmpty ? "" : ", ") + "\(key): \(pair.value.toString())" + } + return "{\(s)}" + case let .boolean(val): + return String(describing: val) + case .null: + return "null" + case .undefined: + return "null" + case let .float(val): + return "\(val)" + case let .double(val): + return "\(val)" + case let .date(val): + return "\"\(val.isoString)\"" + default: + return "\"unsupported data\"" + } + } +} diff --git a/PatientScannerDemo/Models/HCert.swift b/PatientScannerDemo/Models/HCert.swift new file mode 100644 index 0000000..859da74 --- /dev/null +++ b/PatientScannerDemo/Models/HCert.swift @@ -0,0 +1,213 @@ +// +// HCert.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/19/21. +// + +import Foundation +import SwiftyJSON +import JSONSchema + +enum ClaimKey: String { + case HCERT = "-260" + case EU_DGC_V1 = "1" +} + +enum AttributeKey: String { + case firstName + case lastName + case gender + case dateOfBirth + case testStatements + case vaccineStatements + case recoveryStatements + case personIdentifiers + case identifierType = "t" + case identifierCountry = "c" + case identifierValue = "i" + case vaccineShotNo = "seq" + case vaccineShotTotal = "tot" +} + +enum HCertType: String { + case test = "Test" + case vaccineOne = "First Vaccine Shot" + case vaccineTwo = "Last Vaccine Shot" + case recovery = "Recovery" +} + +enum HCertValidity { + case valid + case invalid +} + +let identifierNames: [String: String] = [ + "PP": "Passport Number", + "NN": "National Person Identifier", + "CZ": "Citizenship Card Number", + "HC": "Health Card Number", +] + +let attributeKeys: [AttributeKey: [String]] = [ + .firstName: ["sub", "gn"], + .lastName: ["sub", "fn"], + .gender: ["sub", "gen"], + .dateOfBirth: ["sub", "dob"], + .personIdentifiers: ["sub", "id"], + .testStatements: ["tst"], + .vaccineStatements: ["vac"], + .recoveryStatements: ["rec"], +] + +struct InfoSection { + var header: String + var content: String +} + +struct HCert { + static let supportedPrefixes = [ + "HC1:" + ] + + mutating func parseBodyV1() -> Bool { + guard + let schema = JSON(parseJSON: EU_DGC_SCHEMA_V1).dictionaryObject, + let bodyDict = body.dictionaryObject + else { + return false + } + + guard + let validation = try? validate(bodyDict, schema: schema) + else { + return false + } + #if DEBUG + if let errors = validation.errors { + for err in errors { + print(err.description) + } + } + #else + if !validation.valid { + return false + } + #endif + print(header) + print(body) + return true + } + + init?(from cborData: Data) { + let headerStr = CBOR.header(from: cborData)?.toString() ?? "{}" + let bodyStr = CBOR.payload(from: cborData)?.toString() ?? "{}" + header = JSON(parseJSON: headerStr) + var body = JSON(parseJSON: bodyStr) + print(body) + if body[ClaimKey.HCERT.rawValue].exists() { + body = body[ClaimKey.HCERT.rawValue] + } + if body[ClaimKey.EU_DGC_V1.rawValue].exists() { + self.body = body[ClaimKey.EU_DGC_V1.rawValue] + if !parseBodyV1() { + return nil + } + } else { + print("Wrong EU_DGC Version!") + return nil + } + } + + func get(_ attribute: AttributeKey) -> JSON { + var object = body + for key in attributeKeys[attribute] ?? [] { + object = object[key] + } + return object + } + + var info: [InfoSection] { + var info = [ + InfoSection(header: "Certificate Type", content: type.rawValue), + ] + personIdentifiers + if let date = dateOfBirth { + info += [ + InfoSection(header: "Date of Birth", content: date.localDateString), + ] + } + return info + } + + var header: JSON + var body: JSON + + var fullName: String { + let first = get(.firstName).string ?? "" + let last = get(.lastName).string ?? "" + return "\(first) \(last)" + } + + var dateOfBirth: Date? { + guard let dateString = get(.dateOfBirth).string else { + return nil + } + return Date(dateString: dateString) + } + + var personIdentifiers: [InfoSection] { + guard let identifiers = get(.personIdentifiers).array else { + return [] + } + return identifiers.map { + let type = $0[AttributeKey.identifierType.rawValue].string ?? "" + let country = $0[AttributeKey.identifierCountry.rawValue].string + let value = $0[AttributeKey.identifierValue.rawValue].string ?? "" + + var header = identifierNames[type] ?? "Unknown Identifier" + if let country = country { + header += " (\(country))" + } + + return InfoSection(header: header, content: value) + } + } + + var testStatements: [JSON] { + return get(.testStatements).array ?? [] + } + var vaccineStatements: [JSON] { + return get(.vaccineStatements).array ?? [] + } + var recoveryStatements: [JSON] { + return get(.recoveryStatements).array ?? [] + } + var hasLastShot: Bool { + for statement in vaccineStatements { + let no = statement[AttributeKey.vaccineShotNo.rawValue].int ?? 1 + let total = statement[AttributeKey.vaccineShotTotal.rawValue].int ?? 2 + if no == total { + return true + } + } + return false + } + var type: HCertType { + if hasLastShot { + return .vaccineTwo + } + if !vaccineStatements.isEmpty { + return .vaccineOne + } + if !recoveryStatements.isEmpty { + return .recovery + } + return .test + } + var isValid: Bool { + return Int.random(in: 0...9) < 5 + } + var validity: HCertValidity { + return isValid ? .valid : .invalid + } +} diff --git a/PatientScannerDemo/Protocols/ChildDismissedDelegate.swift b/PatientScannerDemo/Protocols/ChildDismissedDelegate.swift new file mode 100644 index 0000000..60691c0 --- /dev/null +++ b/PatientScannerDemo/Protocols/ChildDismissedDelegate.swift @@ -0,0 +1,12 @@ +// +// ChildDismissedDelegate.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/20/21. +// + +import Foundation + +protocol ChildDismissedDelegate { + func childDismissed() +} diff --git a/PatientScannerDemo/ASN1.swift b/PatientScannerDemo/Services/ASN1.swift similarity index 100% rename from PatientScannerDemo/ASN1.swift rename to PatientScannerDemo/Services/ASN1.swift diff --git a/PatientScannerDemo/Services/Base45.swift b/PatientScannerDemo/Services/Base45.swift new file mode 100644 index 0000000..66cade7 --- /dev/null +++ b/PatientScannerDemo/Services/Base45.swift @@ -0,0 +1,46 @@ +// +// Base45.swift +// +// Created by Dirk-Willem van Gulik on 01/04/2021. +// + +import Foundation + + +let BASE45_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" + +extension String { + enum Base45Error: Error { + case Base64InvalidCharacter + case Base64InvalidLength + } + + public func fromBase45() throws -> Data { + var d = Data() + var o = Data() + + for c in self.uppercased() { + if let at = BASE45_CHARSET.firstIndex(of: c) { + let idx = BASE45_CHARSET.distance(from: BASE45_CHARSET.startIndex, to: at) + d.append(UInt8(idx)) + } else { + throw Base45Error.Base64InvalidCharacter + } + } + for i in stride(from: 0, to: d.count, by: 3) { + if (d.count - i < 2) { + throw Base45Error.Base64InvalidLength + } + var x: UInt32 = UInt32(d[i]) + UInt32(d[i + 1]) * 45 + if (d.count - i >= 3) { + x += 45 * 45 * UInt32(d[i + 2]) + if x >= 256 * 256 { + throw Base45Error.Base64InvalidCharacter + } + o.append(UInt8(x / 256)) + } + o.append(UInt8(x % 256)) + } + return o + } +} diff --git a/PatientScannerDemo/CBOR.swift b/PatientScannerDemo/Services/CBOR.swift similarity index 81% rename from PatientScannerDemo/CBOR.swift rename to PatientScannerDemo/Services/CBOR.swift index 02ae8b2..3af9f8d 100644 --- a/PatientScannerDemo/CBOR.swift +++ b/PatientScannerDemo/Services/CBOR.swift @@ -9,7 +9,7 @@ import Foundation import SwiftCBOR struct CBOR { - public static func payload(from data: Data) -> SwiftCBOR.CBOR? { + static func unwrap(data: Data) -> (SwiftCBOR.CBOR?, SwiftCBOR.CBOR?) { let COSE_TAG = UInt64(18) let decoder = SwiftCBOR.CBORDecoder(input: data.uint) @@ -18,26 +18,29 @@ struct CBOR { case let SwiftCBOR.CBOR.tagged(tag, cborElement) = cbor, tag.rawValue == COSE_TAG, // SIGN1 case let SwiftCBOR.CBOR.array(array) = cborElement, + case let SwiftCBOR.CBOR.byteString(protectedBytes) = array[0], + let protected = try? SwiftCBOR.CBOR.decode(protectedBytes), case let SwiftCBOR.CBOR.byteString(payloadBytes) = array[2], let payload = try? SwiftCBOR.CBOR.decode(payloadBytes) else { - return nil + return (nil, nil) } - return payload + return (payload, protected) + } + + public static func payload(from data: Data) -> SwiftCBOR.CBOR? { + return unwrap(data: data).0 + } + + public static func header(from data: Data) -> SwiftCBOR.CBOR? { + return unwrap(data: data).1 } public static func kid(from data: Data) -> [UInt8]? { - let COSE_TAG = UInt64(18) let COSE_PHDR_KID = SwiftCBOR.CBOR.unsignedInt(4) - let decoder = SwiftCBOR.CBORDecoder(input: data.uint) guard - let cbor = try? decoder.decodeItem(), - case let SwiftCBOR.CBOR.tagged(tag, cborElement) = cbor, - tag.rawValue == COSE_TAG, // SIGN1 - case let SwiftCBOR.CBOR.array(array) = cborElement, - case let SwiftCBOR.CBOR.byteString(protectedBytes) = array[0], - let protected = try? SwiftCBOR.CBOR.decode(protectedBytes), + let protected = unwrap(data: data).1, case let SwiftCBOR.CBOR.map(protectedMap) = protected else { return nil diff --git a/PatientScannerDemo/COSE.swift b/PatientScannerDemo/Services/COSE.swift similarity index 93% rename from PatientScannerDemo/COSE.swift rename to PatientScannerDemo/Services/COSE.swift index daa65cd..622a72b 100644 --- a/PatientScannerDemo/COSE.swift +++ b/PatientScannerDemo/Services/COSE.swift @@ -52,7 +52,7 @@ struct COSE { } return Signature.verify(s, for: d, with: key) } - public static func verify(_ cbor: SwiftCBOR.CBOR, with rsa: String) -> Bool { + public static func verify(_ cbor: SwiftCBOR.CBOR, with derPubKeyB64: String) -> Bool { let COSE_TAG = UInt64(18) guard @@ -74,7 +74,7 @@ struct COSE { ) let d = Data(signedPayload) let s = Data(signature) - guard let key = X509.pubKey(from: rsa) else { + guard let key = X509.pubKey(from: derPubKeyB64) else { return false } return Signature.verify(s, for: d, with: key) diff --git a/PatientScannerDemo/JWK.swift b/PatientScannerDemo/Services/JWK.swift similarity index 100% rename from PatientScannerDemo/JWK.swift rename to PatientScannerDemo/Services/JWK.swift diff --git a/PatientScannerDemo/Signature.swift b/PatientScannerDemo/Services/Signature.swift similarity index 100% rename from PatientScannerDemo/Signature.swift rename to PatientScannerDemo/Services/Signature.swift diff --git a/PatientScannerDemo/X509.swift b/PatientScannerDemo/Services/X509.swift similarity index 100% rename from PatientScannerDemo/X509.swift rename to PatientScannerDemo/Services/X509.swift diff --git a/PatientScannerDemo/ZLib.swift b/PatientScannerDemo/Services/ZLib.swift similarity index 100% rename from PatientScannerDemo/ZLib.swift rename to PatientScannerDemo/Services/ZLib.swift diff --git a/PatientScannerDemo/Base.lproj/LaunchScreen.storyboard b/PatientScannerDemo/Storyboards/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from PatientScannerDemo/Base.lproj/LaunchScreen.storyboard rename to PatientScannerDemo/Storyboards/Base.lproj/LaunchScreen.storyboard diff --git a/PatientScannerDemo/Base.lproj/Main.storyboard b/PatientScannerDemo/Storyboards/Base.lproj/Main.storyboard similarity index 61% rename from PatientScannerDemo/Base.lproj/Main.storyboard rename to PatientScannerDemo/Storyboards/Base.lproj/Main.storyboard index 25a7638..0c8a017 100644 --- a/PatientScannerDemo/Base.lproj/Main.storyboard +++ b/PatientScannerDemo/Storyboards/Base.lproj/Main.storyboard @@ -1,24 +1,26 @@ - + + - + + - + - + - + - + diff --git a/PatientScannerDemo/Storyboards/CertificateViewer.storyboard b/PatientScannerDemo/Storyboards/CertificateViewer.storyboard new file mode 100644 index 0000000..941cbdb --- /dev/null +++ b/PatientScannerDemo/Storyboards/CertificateViewer.storyboard @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PatientScannerDemo/AppDelegate.swift b/PatientScannerDemo/SupportingFiles/AppDelegate.swift similarity index 100% rename from PatientScannerDemo/AppDelegate.swift rename to PatientScannerDemo/SupportingFiles/AppDelegate.swift diff --git a/PatientScannerDemo/Assets.xcassets/AccentColor.colorset/Contents.json b/PatientScannerDemo/SupportingFiles/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from PatientScannerDemo/Assets.xcassets/AccentColor.colorset/Contents.json rename to PatientScannerDemo/SupportingFiles/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/PatientScannerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/PatientScannerDemo/SupportingFiles/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from PatientScannerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json rename to PatientScannerDemo/SupportingFiles/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/PatientScannerDemo/Assets.xcassets/Contents.json b/PatientScannerDemo/SupportingFiles/Assets.xcassets/Contents.json similarity index 100% rename from PatientScannerDemo/Assets.xcassets/Contents.json rename to PatientScannerDemo/SupportingFiles/Assets.xcassets/Contents.json diff --git a/PatientScannerDemo/SupportingFiles/EuDgcSchema.swift b/PatientScannerDemo/SupportingFiles/EuDgcSchema.swift new file mode 100644 index 0000000..a51a63b --- /dev/null +++ b/PatientScannerDemo/SupportingFiles/EuDgcSchema.swift @@ -0,0 +1,304 @@ +// +// EuDgcSchema.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/20/21. +// + +import Foundation + +let EU_DGC_SCHEMA_V1 = """ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$id": "https://github.com/ehn-digital-green-development/hcert-schema/eu_dgc_v1", + "title": "Digital Green Certificate", + "description": "Proof of vaccination, test results or recovery according to EU eHN, version 1.0, including certificate metadata; According to 1) REGULATION OF THE EUROPEAN PARLIAMENT AND OF THE COUNCIL on a framework for the issuance, verification and acceptance of interoperable certificates on vaccination, testing and recovery to facilitate free movement during the COVID-19 pandemic (Digital Green Certificate) - https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A52021PC0130 2) Document \\"Value Sets for the digital green certificate as stated in the Annex ...\\", abbr. \\"VS-2021-04-14\\" - https://webgate.ec.europa.eu/fpfis/wikis/x/05PuKg 3) Guidelines on verifiable vaccination certificates - basic interoperability elements - Release 2 - 2021-03-12, abbr. \\"guidelines\\"", + "type": "object", + "required": [ + "v", + "dgcid", + "sub" + ], + "properties": { + "v": { + "title": "Schema version", + "description": "Version of the schema, according to Semantic versioning (ISO, https://semver.org/ version 2.0.0 or newer) (viz. guidelines)", + "type": "string", + "example": "1.0.0" + }, + "dgcid": { + "title": "Identifier", + "description": "Unique identifier of the DGC (initially called UVCI (V for vaccination), later renamed to DGCI), format and composizion viz. guidelines", + "type": "string", + "example": "01AT42196560275230427402470256520250042" + }, + "sub": { + "description": "Subject", + "type": "object", + "required": [ + "gn", + "dob" + ], + "properties": { + "gn": { + "title": "Given name", + "description": "The given name(s) of the person addressed in the certificate", + "type": "string", + "example": "T\\u00f6lvan" + }, + "fn": { + "title": "Family name", + "description": "The family name(s) of the person addressed in the certificate", + "type": "string", + "example": "T\\u00f6lvansson" + }, + "gnt": { + "title": "Given name (transliterated)", + "description": "The given name(s) of the person addressed in the certificate transliterated into the OCR-B Characters from ISO 1073-2 according to the ICAO Doc 9303 part 3.", + "type": "string", + "example": "Toelvan" + }, + "fnt": { + "title": "Family name (transliterated)", + "description": "The family name(s) of the person addressed in the certificate transliterated into the OCR-B Characters from ISO 1073-2 according to the ICAO Doc 9303 part 3.", + "type": "string", + "example": "Toelvansson" + }, + "id": { + "title": "Person identifiers", + "description": "Identifiers of the vaccinated person, according to the policies applicable in each country", + "type": "array", + "items": { + "type": "object", + "required": [ + "t", + "c", + "i" + ], + "properties": { + "t": { + "title": "Identifier type", + "description": "The type of identifier (viz. VS-2021-04-08) PP = Passport Number NN = National Person Identifier (country specified in the 'c' parameter) CZ = Citizenship Card Number HC = Health Card Number", + "type": "string", + "enum": [ + "PP", + "NN", + "CZ", + "HC" + ], + "example": "NN" + }, + "c": { + "title": "Country", + "description": "Issuing country (ISO 3166-1 alpha-2 country code) of identifier", + "type": "string", + "example": "SE" + }, + "i": { + "title": "Identifier number or string", + "type": "string", + "example": "121212-1212" + } + } + } + }, + "dob": { + "title": "Date of birth", + "description": "The date of birth of the person addressed in the certificate", + "type": "string", + "format": "date", + "example": "2012-12-12" + } + } + }, + "vac": { + "description": "Vaccination/prophylaxis information", + "type": "array", + "items": { + "type": "object", + "required": [ + "dis", + "vap", + "mep", + "aut", + "seq", + "tot", + "dat", + "cou" + ], + "properties": { + "dis": { + "title": "Disease", + "description": "Disease or agent targeted (viz. VS-2021-04-14)", + "type": "string", + "example": "840539006" + }, + "vap": { + "title": "Vaccine/prophylaxis", + "description": "Generic description of the vaccine/prophylaxis or its component(s), (viz. VS-2021-04-14)", + "type": "string", + "example": "1119305005" + }, + "mep": { + "title": "Vaccine medicinal product", + "description": "Code of the medicinal product (viz. VS-2021-04-14)", + "type": "string", + "example": "EU/1/20/1528" + }, + "aut": { + "title": "Vaccine marketing authorization holder or Vaccine manufacturer", + "description": "Code as defined in EMA SPOR - Organisations Management System (viz. VS-2021-04-14)", + "type": "string", + "example": "ORG-100030215" + }, + "seq": { + "title": "Dose sequence number", + "description": "Number of dose administered in a cycle (viz. VS-2021-04-14)", + "type": "integer", + "minimum": 0, + "example": 1 + }, + "tot": { + "title": "Total number of doses", + "description": "Number of expected doses for a complete cycle (specific for a person at the time of administration) (viz. VS-2021-04-14)", + "type": "integer", + "minimum": 0, + "example": 2 + }, + "dat": { + "title": "Date of vaccination", + "description": "The date of the vaccination event", + "type": "string", + "format": "date", + "example": "2021-02-20" + }, + "cou": { + "title": "Country", + "description": "Country (member state) of vaccination (ISO 3166-1 alpha-2 Country Code) (viz. VS-2021-04-14)", + "type": "string", + "example": "SE" + }, + "lot": { + "title": "Batch/lot number", + "description": "A distinctive combination of numbers and/or letters which specifically identifies a batch, optional", + "type": "string" + }, + "adm": { + "title": "Administering centre", + "description": "Name/code of administering centre or a health authority responsible for the vaccination event, optional", + "type": "string", + "example": "Region Halland" + } + } + } + }, + "tst": { + "description": "Test result statement", + "type": "array", + "items": { + "type": "object", + "required": [ + "dis", + "typ", + "dts", + "dtr", + "res", + "fac", + "cou" + ], + "properties": { + "dis": { + "title": "Disease", + "description": "Disease or agent targeted (viz. VS-2021-04-14)", + "type": "string", + "example": "840539006" + }, + "typ": { + "title": "Type of test", + "description": "Code of the type of test that was conducted", + "type": "string", + "example": "LP6464-4" + }, + "tma": { + "title": "Manufacturer and test name", + "description": "Manufacturer and commercial name of the test used (optional for NAAT test) (viz. VS-2021-04-14)", + "type": "string", + "example": "tbd" + }, + "ori": { + "title": "Sample origin", + "description": "Origin of sample that was taken (e.g. nasopharyngeal swab, oropharyngeal swab etc.) (viz. VS-2021-04-14) optional", + "type": "string", + "example": "258500001" + }, + "dts": { + "title": "Date and time sample", + "description": "Date and time when the sample for the test was collected (seconds since epoch)", + "type": "integer", + "minimum": 0, + "example": 441759600 + }, + "dtr": { + "title": "Date and time test result", + "description": "Date and time when the test result was produced (seconds since epoch)", + "type": "integer", + "minimum": 0, + "example": 441759600 + }, + "res": { + "title": "Result of test", + "description": "Result of the test according to SNOMED CT (viz. VS-2021-04-14)", + "type": "string", + "example": "1240591000000104" + }, + "fac": { + "title": "Testing centre or facility", + "description": "Name/code of testing centre, facility or a health authority responsible for the testing event.", + "type": "string", + "example": "tbd" + }, + "cou": { + "title": "Country", + "description": "Country (member state) of test (ISO 3166-1 alpha-2 Country Code)", + "type": "string", + "example": "SE" + } + } + } + }, + "rec": { + "description": "Recovery statement", + "type": "array", + "items": { + "type": "object", + "required": [ + "dis", + "dat", + "cou" + ], + "properties": { + "dis": { + "title": "Disease", + "description": "Disease or agent the citizen has recovered from", + "type": "string", + "example": "840539006" + }, + "dat": { + "title": "Date of first positive test result", + "description": "The date when the sample for the test was collected that led to a positive test result", + "type": "string", + "format": "date", + "example": "2021-02-20" + }, + "cou": { + "title": "Country of test", + "description": "Country (member state) in which the first positive test was performed (ISO 3166-1 alpha-2 Country Code)", + "type": "string", + "example": "SE" + } + } + } + } + } +} +""" diff --git a/PatientScannerDemo/Info.plist b/PatientScannerDemo/SupportingFiles/Info.plist similarity index 97% rename from PatientScannerDemo/Info.plist rename to PatientScannerDemo/SupportingFiles/Info.plist index 8c2a309..e24a474 100644 --- a/PatientScannerDemo/Info.plist +++ b/PatientScannerDemo/SupportingFiles/Info.plist @@ -2,6 +2,8 @@ + UIUserInterfaceStyle + Light NSCameraUsageDescription Scan barcodes with your camera. CFBundleDevelopmentRegion diff --git a/PatientScannerDemo/SceneDelegate.swift b/PatientScannerDemo/SupportingFiles/SceneDelegate.swift similarity index 100% rename from PatientScannerDemo/SceneDelegate.swift rename to PatientScannerDemo/SupportingFiles/SceneDelegate.swift diff --git a/PatientScannerDemo/ViewControllers/CertificateViewer.swift b/PatientScannerDemo/ViewControllers/CertificateViewer.swift new file mode 100644 index 0000000..ee686a9 --- /dev/null +++ b/PatientScannerDemo/ViewControllers/CertificateViewer.swift @@ -0,0 +1,117 @@ +// +// CertificateViewer.swift +// PatientScannerDemo +// +// Created by Yannick Spreen on 4/19/21. +// + +import Foundation +import UIKit +import FloatingPanel + +let DISMISS_TIMEOUT = 15.0 + +let validityString = [ + HCertValidity.valid: "Valid ✓", + HCertValidity.invalid: "Invalid Ⅹ", +] +let buttonText = [ + HCertValidity.valid: "Okay", + HCertValidity.invalid: "Retry", +] +let backgroundColor = [ + HCertValidity.valid: UIColor(red: 0, green: 0.32708, blue: 0.08872, alpha: 1), + HCertValidity.invalid: UIColor(red: 0.36290, green: 0, blue: 0, alpha: 1), +] +let textColor = [ + HCertValidity.valid: UIColor(red: 0.37632, green: 1, blue: 0.54549, alpha: 1), + HCertValidity.invalid: UIColor(red: 1, green: 0.14316, blue: 0.14316, alpha: 1), +] + +class CertificateViewerVC: UIViewController { + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var validityLabel: UILabel! + @IBOutlet weak var loadingBackground: UIView! + @IBOutlet weak var loadingBackgroundTrailing: NSLayoutConstraint! + @IBOutlet weak var typeSegments: UISegmentedControl! + @IBOutlet weak var infoTable: UITableView! + @IBOutlet weak var dismissButton: UIButton! + + var hCert: HCert! { + didSet { + self.draw() + } + } + + var childDismissedDelegate: ChildDismissedDelegate? + + func draw() { + nameLabel.text = hCert.fullName + infoTable.reloadData() + typeSegments.selectedSegmentIndex = [ + HCertType.test, + HCertType.vaccineOne, + HCertType.vaccineTwo, + HCertType.recovery + ].firstIndex(of: hCert.type) ?? 0 + let validity = hCert.validity + dismissButton.setTitle(buttonText[validity], for: .normal) + dismissButton.backgroundColor = textColor[validity] + dismissButton.setTitleColor(backgroundColor[validity], for: .normal) + validityLabel.text = validityString[validity] + validityLabel.textColor = textColor[validity] + view.backgroundColor = backgroundColor[validity] + } + + override func viewDidLoad() { + super.viewDidLoad() + // selected option color + typeSegments.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: .selected) + // color of other options + typeSegments.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .normal) + + infoTable.dataSource = self + + return + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + loadingBackground.layer.zPosition = -1 + loadingBackgroundTrailing.priority = .init(200) + UIView.animate(withDuration: DISMISS_TIMEOUT, delay: 0, options: .curveLinear) { [weak self] in + self?.view.layoutIfNeeded() + } + DispatchQueue.main.asyncAfter(deadline: .now() + DISMISS_TIMEOUT) { [weak self] in + self?.dismiss(animated: true, completion: nil) + } + + return + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + childDismissedDelegate?.childDismissed() + } + + @IBAction + func closeButton() { + dismiss(animated: true, completion: nil) + } +} + +extension CertificateViewerVC: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return hCert.info.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let base = tableView.dequeueReusableCell(withIdentifier: "infoCell", for: indexPath) + guard let cell = base as? InfoCell else { + return base + } + cell.draw(hCert.info[indexPath.row]) + return cell + } +} diff --git a/PatientScannerDemo/ViewController.swift b/PatientScannerDemo/ViewControllers/Scan.swift similarity index 67% rename from PatientScannerDemo/ViewController.swift rename to PatientScannerDemo/ViewControllers/Scan.swift index b987a5f..a9bc4cd 100644 --- a/PatientScannerDemo/ViewController.swift +++ b/PatientScannerDemo/ViewControllers/Scan.swift @@ -11,10 +11,10 @@ import UIKit import Vision import AVFoundation import SwiftCBOR -//import CryptorECC +import FloatingPanel -class ViewController: UIViewController { +class ScanVC: UIViewController { var captureSession = AVCaptureSession() lazy var detectBarcodeRequest = VNDetectBarcodesRequest { request, error in @@ -27,16 +27,20 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - checkPermissions() - setupCameraLiveView() -// observationHandler(payloadS: nil) +// checkPermissions() +// setupCameraLiveView() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.observationHandler(payloadS: nil) + } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) captureSession.stopRunning() } + // let curve: EllipticCurve = .prime256v1 let name: String = "ECDSA" + var presentingViewer: CertificateViewerVC? // Send the base64URLencoded signature and `header.claims` to BlueECC for verification. // func verifySignature(key: Data, signature: Data, for data: Data) -> Bool { @@ -59,10 +63,34 @@ class ViewController: UIViewController { // } // // } + + func presentViewer(for certificate: HCert?) { + guard + presentingViewer == nil, + let certificate = certificate, + let contentVC = UIStoryboard(name: "CertificateViewer", bundle: nil) + .instantiateInitialViewController(), + let viewer = contentVC as? CertificateViewerVC + else { + return + } + + let fpc = FloatingPanelController() + fpc.set(contentViewController: viewer) + fpc.isRemovalInteractionEnabled = true // Let it removable by a swipe-down + fpc.layout = FullFloatingPanelLayout() + fpc.surfaceView.layer.cornerRadius = 24.0 + fpc.surfaceView.clipsToBounds = true + viewer.hCert = certificate + viewer.childDismissedDelegate = self + presentingViewer = viewer + + present(fpc, animated: true, completion: nil) + } } -extension ViewController { +extension ScanVC { private func checkPermissions() { switch AVCaptureDevice.authorizationStatus(for: .video) { case .notDetermined: @@ -128,22 +156,37 @@ extension ViewController { } func observationHandler(payloadS: String?) { - let payloadS: String? = "HC1:NCFOXNEG2NBJ5*H:QO-.O /MD064 PJ26UV0 XHLQ9HXKZNEQCSJ591MVBATW$4 S8$96NF6OR5UVBJUB4PJU47326/Z7PCL394Z/MWP4 N66ED6JC:JEG.CZJC0:C6JK:JM$JLAINN6BLHM035L8CCECS.CYMCPOJ5OI9YI:8DRFC%PD*ZLJ9CWVBREJFZM4A7Z/M*+Q.28+VQXCRAHAF27I9QQ60E2KYIJPOJI7J/VJ0 JYSJEZIK7B*IJS7BCLIOCISEBTKBRHSWKJ4:2POJ.GILYJ7GPSVBY4CJZIOMI$MI1VC3TCYR6HCRF46Q96W/6-DP+%PPMC1KJG%HJ*81.7 84-W6I RO5PH6UDUH+/F9PJCQFRVV 8UDJ5QEV2Y8%635OHH0E2:E5VUJZ4 VT3+6ZU598O:E0/ 0VP2IBO6ANG%6UD5RSRO4B6$ES40H/CQ1" + let payloadS: String? = payloadS ?? "HC1:NCFD:MFY7AP2.532EE3*45+G W84$2J85AUFA3QHN7SXNF$S-4THFSJFIMSKD3799U/LN71G32S0L43XHEIB*983A8RMF1/MMMIS6AQC0QNGU+I.AKYQKBPL%YA-ZVT28J3D+WNNL6SEV%GH$$3NRHO%L8BVLJBL:3F0AKIH3.1U2VS0WZLD75Q52EAENQ-HAYIXTGFH9%ZKA6Q$8A4J67E585MUDPCGCSY3SA7YPCPXCVDL-S0Z.CM6M7%H5POR-ACBI7MT3ZARJ7%S29G596OFMRF-65T6O*M.BJ3DI0W3%:ITBAF 9B1D3SQ$A5LOCTQQZ 40IPR:R3 PUXUN01XD8SJ62MRN$BV502.0HLGS/NXXJ AS.X5QZ2SGTEKLKHB4T8F%S664E02MH2F A.BH9+R7N6N5N.USZXL7DO2AIV2P0XU2.OPI6 C395EPMMGDAD4G-S1DC8ZVJT:3CR3-P8ZVGV8A$DD+*0/MH5+MPDO:L9IJ6MU278C924V:09%1MUQTR2FBL5 6CLODPT304L3GK6OT-7 97U*IO7J:1MB1EU0E.G3C/4P7FXRF-04F9O-9VREIPOSWTFU2W%2F4-B$CSSVNWKAHWJA4NCEU$KLU$GQ6OJ-97UN" guard - let payloadString = payloadS, - let compressed = try? String(payloadString.dropFirst(4)).fromBase45() - else { return } + var payloadString = payloadS + else { + return + } - let data = decompress(compressed) + for prefix in HCert.supportedPrefixes { + if payloadString.starts(with: prefix) { + payloadString = String(payloadString.dropFirst(prefix.count)) + } + } - /// TODO + guard + let compressed = try? payloadString.fromBase45() + else { + return + } + let data = decompress(compressed) +// let payload = CBOR.payload(from: data) +// presentViewer(for: payload) +// print(CBOR.payload(from: data)?.toString() ?? "") +// print(CBOR.header(from: data)?.toString() ?? "") + presentViewer(for: HCert(from: data)) } } -extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { +extension ScanVC: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } @@ -161,7 +204,7 @@ extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { } -extension ViewController { +extension ScanVC { private func configurePreviewLayer() { let cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) cameraPreviewLayer.videoGravity = .resizeAspectFill @@ -185,3 +228,9 @@ extension ViewController { ) } } + +extension ScanVC: ChildDismissedDelegate { + func childDismissed() { + presentingViewer = nil + } +} diff --git a/PatientScannerDemoTests/EHNTests.swift b/PatientScannerDemoTests/EHNTests.swift index b3e3b67..391a691 100644 --- a/PatientScannerDemoTests/EHNTests.swift +++ b/PatientScannerDemoTests/EHNTests.swift @@ -32,13 +32,10 @@ class EHNTests: XCTestCase { ] """ - // Remove HC1 header if any (v0.0.3 'HC1', v0.0.4 'HC1:' + // Remove HC1 header if any (v0.0.3 'HC1', v0.0.4 'HC1:') // - if (barcode.hasPrefix("HC1")) { - barcode = String(barcode.suffix(barcode.count-3)) - if (barcode.hasPrefix("1")) { - barcode = String(barcode.suffix(barcode.count-1)) - } + if (barcode.hasPrefix("HC1:")) { + barcode = String(barcode.suffix(barcode.count-4)) } guard @@ -78,18 +75,18 @@ class EHNTests: XCTestCase { XCTAssert(false) } func testCoseEcAT() throws { - let barcode = "HC1NCFI.L3B6AP2YQ2%MNBCHC51/CAOZDL+OL7S99U60SVOGSHD%24R4PCTH7Y9KL6EVJBIMBL364D X1KKLZ*I9K4RLDR7B011EROZS6022WR5VX4QG9W72HWCC12B:TWZG.%N8GWBCOV0O%5F9U7H 8P4L.%2/:6TXB/-J6ZJ.*QYIBYSD6/T.6RDMMK0PX6HTPO1REO*CWMFG5CJ38+FBS+8DB9*2FFP9Z8HEM137CRBQ.893$I.R7 DRFOVMUQRVDP4IU613/G-0LZ:43+MM:QO.CQPJU21-7PJ*L/*DVLO:Q5G-7%WF4ZO*JJR:KH+O1707M6.73VXG0+0050+IMIFH:3V+DL3JK/21/LEI$T5TUT8P:40P10AXGBRS%-GZS8TGF-IT3-HU2SVJUAVG%AGO98R00Y.QT23DVP0TB*-J6YJP 8TU9U/OJSQGGMK+K3MH IR U7H%9/UU07AGEWMVTOBAYIV4SPGC2Y5FJJ67I4/J4FXM3GP:SAOZQJPL%WGP4BT59Q8K5MB*M44%K9JTWB9K+LS-2I9ST+3J%0X%1YGN0E0KL3/FHTZHEF9WIC4Q1HVK74L186BWT9+TJR7C2M%9JXN8S%0PVU8GFBET%0PG RR1KD2F11G-CW8A4124N9VN-T-:292AX*B%ER*$12-O:6K:/N$ 7IYFFXQ/5F NC49M/W5501B2OKU7E:NHMM8SETWUK*I$JR" + let barcode = "HC1:NCFI.L3B6AP2YQ2%MNBCHC51/CAOZDL+OL7S99U60SVOGSHD%24R4PCTH7Y9KL6EVJBIMBL364D X1KKLZ*I9K4RLDR7B011EROZS6022WR5VX4QG9W72HWCC12B:TWZG.%N8GWBCOV0O%5F9U7H 8P4L.%2/:6TXB/-J6ZJ.*QYIBYSD6/T.6RDMMK0PX6HTPO1REO*CWMFG5CJ38+FBS+8DB9*2FFP9Z8HEM137CRBQ.893$I.R7 DRFOVMUQRVDP4IU613/G-0LZ:43+MM:QO.CQPJU21-7PJ*L/*DVLO:Q5G-7%WF4ZO*JJR:KH+O1707M6.73VXG0+0050+IMIFH:3V+DL3JK/21/LEI$T5TUT8P:40P10AXGBRS%-GZS8TGF-IT3-HU2SVJUAVG%AGO98R00Y.QT23DVP0TB*-J6YJP 8TU9U/OJSQGGMK+K3MH IR U7H%9/UU07AGEWMVTOBAYIV4SPGC2Y5FJJ67I4/J4FXM3GP:SAOZQJPL%WGP4BT59Q8K5MB*M44%K9JTWB9K+LS-2I9ST+3J%0X%1YGN0E0KL3/FHTZHEF9WIC4Q1HVK74L186BWT9+TJR7C2M%9JXN8S%0PVU8GFBET%0PG RR1KD2F11G-CW8A4124N9VN-T-:292AX*B%ER*$12-O:6K:/N$ 7IYFFXQ/5F NC49M/W5501B2OKU7E:NHMM8SETWUK*I$JR" return baseTestAT(barcode: barcode) } func testCoseRsaAT() throws { - let barcode = "HC1NCFI.LUZB.P2YQ2E R002$DLTLIYXM/+0JNEAFUP.CV5GKCL /A0SQT/O8ZA85D0IJ$5D4.J+MR8-EGK6DM1R58V1ERUCWM4.E4ZM4$WG O E6KX9$KHCQMFE0B$KPKA*A2+ELQ*5ZQE/*1ZMSM5S.:KGQ33LQOWN67QW68LXM867D6837QCWEU*E*OKZCJPSCPIM1:Q83FBODLRD/W8K$E4 SG4P2JM6GCMU9FFSZAKNJH6ZNZVSWU78MHB4TO5J0N51U2F:17AB8O0*N0CQSA-9VIN904*STT$6R1LLXDT8J1ZN4I4368L02N72ZMGV496.0NO3OQCPFVF+C$BKQH1$+8L:AAEGRIB548B68D/3-ZTZ.JNM84E0F59$N6DY2*6FACA448900U42ZEMMO7-LO.45*IMXYIODF:82RN4L8Q%20HDJ-YCHIA1YT87A2/C1NO1F6DPCLCK9+BDXEEK23JH0/I8XJQEQ1T8BQ5GNDRFPQKKV6WT$A+98TIOUSAFA0J8OMY87$P-LESPS1KO+:O%HOY*C8XH4ZE7D16QR WNN$EP:U7E2HTLS023X0GJBW887LCKVHDGJ+Q88I0A73$H6R30XYH-LDC$4+24*JSE*H98A471BJ3:R0LU3MZA+ 4/GMZ9J D4E7L.TI$L8L:TN4G2S552748V$%D0CPH9RJ-7Z4AXFN*SS/ZC44O3P5%%AE936OUSR1H7WP%CP8KQUCY9I%A5FDBV+0A%HPVP5SKP9TKAFKT9FYROAABDBEHVQ9317O0D7MS5Z.Q2+K2.1RGGS3B5OTO2ROD60Z6LE98E94TV09JXM395VA0N.98EP3*4E5SHA7I96Q70UF74SPSIJI.BUHQJ$:K$9M51H9Z6RPHR.K-XT855UJJZC8C*MSITGMRE%O+:JQXV$LS7SJ1:DE LCX7RP7T JH4RR-NK.PASIWKA8J5Z1NA*BE7KFTJXO4BM5QVF.S0-5CG8KQWB8RCA1VP*1Q.ELZ1Q21G.JEGLOFH1:FR09$SCY/OGBU1QIE/2 9SK3" + let barcode = "HC1:NCFI.LUZB.P2YQ2E R002$DLTLIYXM/+0JNEAFUP.CV5GKCL /A0SQT/O8ZA85D0IJ$5D4.J+MR8-EGK6DM1R58V1ERUCWM4.E4ZM4$WG O E6KX9$KHCQMFE0B$KPKA*A2+ELQ*5ZQE/*1ZMSM5S.:KGQ33LQOWN67QW68LXM867D6837QCWEU*E*OKZCJPSCPIM1:Q83FBODLRD/W8K$E4 SG4P2JM6GCMU9FFSZAKNJH6ZNZVSWU78MHB4TO5J0N51U2F:17AB8O0*N0CQSA-9VIN904*STT$6R1LLXDT8J1ZN4I4368L02N72ZMGV496.0NO3OQCPFVF+C$BKQH1$+8L:AAEGRIB548B68D/3-ZTZ.JNM84E0F59$N6DY2*6FACA448900U42ZEMMO7-LO.45*IMXYIODF:82RN4L8Q%20HDJ-YCHIA1YT87A2/C1NO1F6DPCLCK9+BDXEEK23JH0/I8XJQEQ1T8BQ5GNDRFPQKKV6WT$A+98TIOUSAFA0J8OMY87$P-LESPS1KO+:O%HOY*C8XH4ZE7D16QR WNN$EP:U7E2HTLS023X0GJBW887LCKVHDGJ+Q88I0A73$H6R30XYH-LDC$4+24*JSE*H98A471BJ3:R0LU3MZA+ 4/GMZ9J D4E7L.TI$L8L:TN4G2S552748V$%D0CPH9RJ-7Z4AXFN*SS/ZC44O3P5%%AE936OUSR1H7WP%CP8KQUCY9I%A5FDBV+0A%HPVP5SKP9TKAFKT9FYROAABDBEHVQ9317O0D7MS5Z.Q2+K2.1RGGS3B5OTO2ROD60Z6LE98E94TV09JXM395VA0N.98EP3*4E5SHA7I96Q70UF74SPSIJI.BUHQJ$:K$9M51H9Z6RPHR.K-XT855UJJZC8C*MSITGMRE%O+:JQXV$LS7SJ1:DE LCX7RP7T JH4RR-NK.PASIWKA8J5Z1NA*BE7KFTJXO4BM5QVF.S0-5CG8KQWB8RCA1VP*1Q.ELZ1Q21G.JEGLOFH1:FR09$SCY/OGBU1QIE/2 9SK3" return baseTestAT(barcode: barcode) } func baseTestAT(barcode: String) { var barcode = barcode // Remove HC1 header if any - if (barcode.hasPrefix("HC1")) { - barcode = String(barcode.suffix(barcode.count-3)) + if (barcode.hasPrefix("HC1:")) { + barcode = String(barcode.suffix(barcode.count-4)) } guard