Skip to content

Commit

Permalink
Base Card, PDF417 and MRZ Scanners (#29)
Browse files Browse the repository at this point in the history
- generic multi-credential base card (+ credential pack)
- a generic PDF417 scanner
- an initial MRZ (OCR) scanner based on the first QR Code scanner implementation
- refactors the previous QR Code Scanner version

---------

Co-authored-by: Gregorio <[email protected]>
  • Loading branch information
Juliano1612 and w4ll3 authored Aug 27, 2024
1 parent 6067a78 commit 80e798d
Show file tree
Hide file tree
Showing 12 changed files with 1,311 additions and 254 deletions.
3 changes: 3 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ excluded:
disabled_rules:
- cyclomatic_complexity
- todo
- file_length
- force_try
- non_optional_string_data_conversion
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let package = Package(
],
dependencies: [
// .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", .branch("main")),
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.27"),
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.28"),
// .package(path: "../mobile-sdk-rs"),
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0")
],
Expand Down
10 changes: 9 additions & 1 deletion Sources/MobileSdk/Credential.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import Foundation

public class Credential: Identifiable {
open class Credential: Identifiable {
public var id: String

public init(id: String) {
self.id = id
}

open func get(keys: [String]) -> [String: GenericJSON] {
if keys.contains("id") {
return ["id": GenericJSON.string(self.id)]
} else {
return [:]
}
}
}
53 changes: 53 additions & 0 deletions Sources/MobileSdk/CredentialPack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation
import CryptoKit

public class CredentialPack {

private var credentials: [Credential]

public init() {
self.credentials = []
}

public init(credentials: [Credential]) {
self.credentials = credentials
}

public func addW3CVC(credentialString: String) throws -> [Credential]? {
do {
let credential = try W3CVC(credentialString: credentialString)
self.credentials.append(credential)
return self.credentials
} catch {
throw error
}
}

public func addMDoc(mdocBase64: String, keyAlias: String = UUID().uuidString) throws -> [Credential]? {
let mdocData = Data(base64Encoded: mdocBase64)!
let credential = MDoc(fromMDoc: mdocData, namespaces: [:], keyAlias: keyAlias)!
self.credentials.append(credential)
return self.credentials
}

public func get(keys: [String]) -> [String: [String: GenericJSON]] {
var values: [String: [String: GenericJSON]] = [:]
for cred in self.credentials {
values[cred.id] = cred.get(keys: keys)
}

return values
}

public func get(credentialsIds: [String]) -> [Credential] {
return self.credentials.filter { credentialsIds.contains($0.id) }
}

public func get(credentialId: String) -> Credential? {
if let credential = self.credentials.first(where: { $0.id == credentialId }) {
return credential
} else {
return nil
}
}
}
139 changes: 139 additions & 0 deletions Sources/MobileSdk/GenericJSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// GenericJSON implementation based on https://github.com/iwill/generic-json-swift
import Foundation

public enum GenericJSON {
case string(String)
case number(Double)
case object([String: GenericJSON])
case array([GenericJSON])
case bool(Bool)
case null
}

extension GenericJSON: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .array(array):
try container.encode(array)
case let .object(object):
try container.encode(object)
case let .string(string):
try container.encode(string)
case let .number(number):
try container.encode(number)
case let .bool(bool):
try container.encode(bool)
case .null:
try container.encodeNil()
}
}

public func toString() -> String {
switch self {
case .string(let str):
return str
case .number(let num):
return num.debugDescription
case .bool(let bool):
return bool.description
case .null:
return "null"
default:
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try! String(data: encoder.encode(self), encoding: .utf8)!
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let object = try? container.decode([String: GenericJSON].self) {
self = .object(object)
} else if let array = try? container.decode([GenericJSON].self) {
self = .array(array)
} else if let string = try? container.decode(String.self) {
self = .string(string)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let number = try? container.decode(Double.self) {
self = .number(number)
} else if container.decodeNil() {
self = .null
} else {
throw DecodingError.dataCorrupted(
.init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.")
)
}
}
}

extension GenericJSON: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .string(let str):
return str.debugDescription
case .number(let num):
return num.debugDescription
case .bool(let bool):
return bool.description
case .null:
return "null"
default:
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try! String(data: encoder.encode(self), encoding: .utf8)!
}
}
}

public extension GenericJSON {
var dictValue: [String: GenericJSON]? {
if case .object(let value) = self {
return value
}
return nil
}
var arrayValue: [GenericJSON]? {
if case .array(let value) = self {
return value
}
return nil
}
subscript(index: Int) -> GenericJSON? {
if case .array(let arr) = self, arr.indices.contains(index) {
return arr[index]
}
return nil
}

subscript(key: String) -> GenericJSON? {
if case .object(let dict) = self {
return dict[key]
}
return nil
}

subscript(dynamicMember member: String) -> GenericJSON? {
return self[member]
}

subscript(keyPath keyPath: String) -> GenericJSON? {
return queryKeyPath(keyPath.components(separatedBy: "."))
}

func queryKeyPath<T>(_ path: T) -> GenericJSON? where T: Collection, T.Element == String {
guard case .object(let object) = self else {
return nil
}
guard let head = path.first else {
return nil
}
guard let value = object[head] else {
return nil
}
let tail = path.dropFirst()
return tail.isEmpty ? value : value.queryKeyPath(tail)
}

}
36 changes: 36 additions & 0 deletions Sources/MobileSdk/W3CVC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

enum W3CError: Error {
case initializationError(String)
}

public class W3CVC: Credential {
private let credentialString: String
private let credential: GenericJSON?

public init(credentialString: String) throws {
self.credentialString = credentialString
if let data = credentialString.data(using: .utf8) {
do {
let json = try JSONDecoder().decode(GenericJSON.self, from: data)
self.credential = json
super.init(id: json["id"]!.toString())
} catch let error as NSError {
throw error
}
} else {
self.credential = nil
super.init(id: "")
throw W3CError.initializationError("Failed to process credential string.")
}
}

override public func get(keys: [String]) -> [String: GenericJSON] {
if let cred = credential!.dictValue {
return cred.filter { keys.contains($0.key) }
} else {
return [:]
}

}
}
Loading

0 comments on commit 80e798d

Please sign in to comment.