@vppos/react-native-nfc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/NFCSDK.podspec +40 -0
- package/android/build.gradle +85 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +31 -0
- package/android/src/main/AndroidManifestNew.xml +30 -0
- package/android/src/main/java/com/nfcsdk/ChipReader.kt +237 -0
- package/android/src/main/java/com/nfcsdk/NFCSDKModule.kt +78 -0
- package/android/src/main/java/com/nfcsdk/NFCSDKPackage.kt +17 -0
- package/android/src/main/java/com/nfcsdk/NFCScanActivity.kt +299 -0
- package/android/src/main/java/com/nfcsdk/utils/Dg13Parser.kt +278 -0
- package/android/src/main/java/com/nfcsdk/utils/FaceExtractor.kt +40 -0
- package/android/src/main/java/com/nfcsdk/utils/MrzUtils.kt +75 -0
- package/android/src/main/res/drawable/bg_nfc_bottom_sheet.xml +13 -0
- package/android/src/main/res/drawable/bg_nfc_sheet_handle.xml +9 -0
- package/android/src/main/res/layout/activity_nfc.xml +110 -0
- package/android/src/main/res/values/styles.xml +10 -0
- package/android/src/main/res/xml/nfc_tech_filter.xml +8 -0
- package/ios/ChipReader.swift +258 -0
- package/ios/NFCSDK-Bridging-Header.h +2 -0
- package/ios/NFCSDK.mm +9 -0
- package/ios/NFCSDK.swift +112 -0
- package/ios/NFCSDKSession.swift +5 -0
- package/ios/utils/DG13Parser.swift +302 -0
- package/ios/utils/MrzUtils.swift +49 -0
- package/lib/module/errors.js +9 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +118 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/errors.d.ts +4 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +80 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +45 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +155 -0
- package/src/errors.ts +6 -0
- package/src/index.tsx +141 -0
- package/src/types.ts +45 -0
- package/vendor/ios-passport-reader/CHANGELOG +362 -0
- package/vendor/ios-passport-reader/CODE_OF_CONDUCT.md +77 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/AppDelegate.swift +39 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/Contents.json +158 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-40.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-72.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-76.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small-50.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/ios-marketing.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/Contents.json +6 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/background.imageset/Contents.json +21 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/background.imageset/background.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/head.imageset/Contents.json +21 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Assets.xcassets/head.imageset/head.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Base.lproj/LaunchScreen.storyboard +25 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Extensions/FileManagerExt.swift +16 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Extensions/StringExt.swift +40 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Extensions/UIApplicationExt.swift +21 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Info.plist +77 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Model/PassportUtils.swift +76 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Model/SettingsStore.swift +107 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/NFCPassportReader.entitlements +10 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/SceneDelegate.swift +57 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/DetailsView.swift +197 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/ExportPassportView.swift +164 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/HelperViews/CheckBoxView.swift +48 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/HelperViews/ViewExt.swift +20 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/HelperViews/ViewModifiers.swift +41 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/MRZEntryView.swift +125 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/MRZScannerViewController.swift +90 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/MainView.swift +214 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/PassportSummaryView.swift +111 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/PassportView.swift +73 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/SettingsView.swift +63 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/Views/StoredPassportView.swift +152 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/masterList.pem +32 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp/readme.md +10 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp.xcodeproj/project.pbxproj +695 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp.xcodeproj/xcshareddata/xcschemes/NFCPassportReader.xcscheme +106 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp.xcworkspace/contents.xcworkspacedata +10 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderApp.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderAppTests/DataGroupParsingTests.swift +189 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderAppTests/Info.plist +22 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderAppTests/NFCPassportReaderTests.swift +260 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/NFCPassportReaderAppTests/PACETests.swift +112 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/Podfile +22 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/Podfile.lock +35 -0
- package/vendor/ios-passport-reader/Examples/Example_CocoaPods/README.md +2 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/AppDelegate.swift +39 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/Contents.json +158 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-40.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-72.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-76.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small-50.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/icon@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/ios-marketing.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/Contents.json +6 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/background.imageset/Contents.json +21 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/background.imageset/background.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/head.imageset/Contents.json +21 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Assets.xcassets/head.imageset/head.png +0 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Base.lproj/LaunchScreen.storyboard +25 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Extensions/FileManagerExt.swift +16 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Extensions/StringExt.swift +40 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Extensions/UIApplicationExt.swift +21 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Info.plist +79 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Model/PassportUtils.swift +99 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Model/SettingsStore.swift +98 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Model/SettingsStoreCAN.swift +75 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/MrzScanner/LICENSE +21 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/MrzScanner/PreviewView.swift +34 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/MrzScanner/StringUtils.swift +160 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/MrzScanner/ViewController.swift +320 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/MrzScanner/VisionViewController.swift +163 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/NFCPassportReader.entitlements +10 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/SceneDelegate.swift +58 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/CANViews/CanKeyView.swift +251 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/CANViews/MRZEntryViewCanKey.swift +65 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/CANViews/PassportViewCAN.swift +73 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/DetailsView.swift +193 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/ExportPassportView.swift +164 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/HelperViews/CheckBoxView.swift +48 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/HelperViews/ViewExt.swift +20 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/HelperViews/ViewModifiers.swift +41 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/MRZEntryView.swift +125 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/MRZScannerViewController.swift +90 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/MainView.swift +264 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/PassportSummaryView.swift +111 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/PassportView.swift +73 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/SettingsView.swift +47 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/Views/StoredPassportView.swift +149 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/masterList.pem +32 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp/readme.md +10 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp.xcodeproj/project.pbxproj +683 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +25 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderApp.xcodeproj/xcshareddata/xcschemes/NFCPassportReader.xcscheme +106 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderAppTests/DataGroupParsingTests.swift +190 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderAppTests/Info.plist +22 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderAppTests/NFCPassportReaderTests.swift +260 -0
- package/vendor/ios-passport-reader/Examples/Example_SPM/NFCPassportReaderAppTests/PACETests.swift +112 -0
- package/vendor/ios-passport-reader/LICENSE +21 -0
- package/vendor/ios-passport-reader/NFCPassportReader.podspec +27 -0
- package/vendor/ios-passport-reader/Package.swift +29 -0
- package/vendor/ios-passport-reader/README.md +141 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/AES_3DES_DESEncryption.swift +377 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/BACHandler.swift +194 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/ChipAuthenticationHandler.swift +224 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroupHash.swift +16 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroupParser.swift +36 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/ActiveAuthenticationInfo.swift +69 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/COM.swift +61 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/CardAccess.swift +38 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/ChipAuthenticationInfo.swift +135 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/ChipAuthenticationPublicKeyInfo.swift +53 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup.swift +103 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup1.swift +111 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup11.swift +66 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup12.swift +75 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup14.swift +37 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup15.swift +46 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup2.swift +163 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroup7.swift +46 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/DataGroupId.swift +105 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/NotImplementedDG.swift +16 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/PACEInfo.swift +415 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/SOD.swift +240 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/DataGroups/SecurityInfo.swift +136 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/Errors.swift +148 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/Logging.swift +32 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/Models/FaceImageInfo.swift +161 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/NFCPassportModel.swift +540 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/NFCViewDisplayMessage.swift +60 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/OpenSSLUtils.swift +705 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/PACEHandler.swift +627 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/PassportReader.swift +387 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/Resources/PrivacyInfo.xcprivacy +14 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/ResponseAPDU.swift +25 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/SecureMessaging.swift +301 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/SecureMessagingSessionKeyGenerator.swift +156 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/SimpleASN1DumpParser.swift +173 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/TagReader.swift +374 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/Utils.swift +430 -0
- package/vendor/ios-passport-reader/Sources/NFCPassportReader/X509Wrapper.swift +168 -0
- package/vendor/ios-passport-reader/scripts/README.md +45 -0
- package/vendor/ios-passport-reader/scripts/extract.py +197 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
package com.nfcsdk
|
|
2
|
+
|
|
3
|
+
import android.app.PendingIntent
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.content.IntentFilter
|
|
6
|
+
import android.graphics.Color
|
|
7
|
+
import android.graphics.drawable.ColorDrawable
|
|
8
|
+
import android.nfc.NfcAdapter
|
|
9
|
+
import android.nfc.Tag
|
|
10
|
+
import android.nfc.tech.IsoDep
|
|
11
|
+
import android.os.Build
|
|
12
|
+
import android.os.Bundle
|
|
13
|
+
import android.os.VibrationEffect
|
|
14
|
+
import android.os.Vibrator
|
|
15
|
+
import android.provider.Settings
|
|
16
|
+
import android.util.Log
|
|
17
|
+
import android.view.Gravity
|
|
18
|
+
import android.view.View
|
|
19
|
+
import android.widget.Button
|
|
20
|
+
import android.widget.ProgressBar
|
|
21
|
+
import android.widget.TextView
|
|
22
|
+
import android.widget.Toast
|
|
23
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
24
|
+
import com.facebook.react.bridge.Arguments
|
|
25
|
+
import java.security.Security
|
|
26
|
+
|
|
27
|
+
class NFCScanActivity : AppCompatActivity() {
|
|
28
|
+
|
|
29
|
+
private var nfcAdapter: NfcAdapter? = null
|
|
30
|
+
|
|
31
|
+
private lateinit var progressBar: ProgressBar
|
|
32
|
+
private lateinit var progressPercentTextView: TextView
|
|
33
|
+
private lateinit var retryButton: Button
|
|
34
|
+
|
|
35
|
+
@Volatile
|
|
36
|
+
private var isReading = false
|
|
37
|
+
|
|
38
|
+
@Volatile
|
|
39
|
+
private var scanCompleted = false
|
|
40
|
+
|
|
41
|
+
// ── Lifecycle ──────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
44
|
+
super.onCreate(savedInstanceState)
|
|
45
|
+
|
|
46
|
+
setContentView(R.layout.activity_nfc)
|
|
47
|
+
setupWindow()
|
|
48
|
+
setupTransition()
|
|
49
|
+
setupSheetAnimation()
|
|
50
|
+
setupViews()
|
|
51
|
+
|
|
52
|
+
Security.insertProviderAt(
|
|
53
|
+
org.spongycastle.jce.provider.BouncyCastleProvider(),
|
|
54
|
+
1,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
|
58
|
+
if (nfcAdapter == null) {
|
|
59
|
+
Toast.makeText(this, "Thiết bị không hỗ trợ NFC", Toast.LENGTH_LONG).show()
|
|
60
|
+
finish()
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (nfcAdapter?.isEnabled != true) {
|
|
65
|
+
Toast.makeText(this, "Vui lòng bật NFC", Toast.LENGTH_LONG).show()
|
|
66
|
+
startActivity(Intent(Settings.ACTION_NFC_SETTINGS))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
updateProgress(0, "Đưa CCCD sát mặt lưng điện thoại")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override fun onResume() {
|
|
73
|
+
super.onResume()
|
|
74
|
+
|
|
75
|
+
val intent = Intent(this, javaClass)
|
|
76
|
+
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
77
|
+
|
|
78
|
+
val pendingIntent =
|
|
79
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
80
|
+
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE)
|
|
81
|
+
} else {
|
|
82
|
+
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
val filters = arrayOf(IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED))
|
|
86
|
+
val techList = arrayOf(arrayOf(IsoDep::class.java.name))
|
|
87
|
+
|
|
88
|
+
nfcAdapter?.enableForegroundDispatch(this, pendingIntent, filters, techList)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override fun onPause() {
|
|
92
|
+
super.onPause()
|
|
93
|
+
nfcAdapter?.disableForegroundDispatch(this)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onDestroy() {
|
|
97
|
+
super.onDestroy()
|
|
98
|
+
isReading = false
|
|
99
|
+
scanCompleted = false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override fun onNewIntent(intent: Intent) {
|
|
103
|
+
super.onNewIntent(intent)
|
|
104
|
+
|
|
105
|
+
if (isReading || scanCompleted) return
|
|
106
|
+
|
|
107
|
+
@Suppress("DEPRECATION")
|
|
108
|
+
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG) ?: return
|
|
109
|
+
|
|
110
|
+
startChipRead(tag)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Chip Reading ───────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
private fun startChipRead(tag: Tag) {
|
|
116
|
+
isReading = true
|
|
117
|
+
|
|
118
|
+
Thread {
|
|
119
|
+
try {
|
|
120
|
+
val citizenId = NFCSDKModule.documentNumber.trim()
|
|
121
|
+
if (citizenId.length < 6) {
|
|
122
|
+
updateProgress(-1, "CCCD không hợp lệ")
|
|
123
|
+
Log.e("NFCSDK", "startChipRead: invalid citizenId")
|
|
124
|
+
isReading = false
|
|
125
|
+
return@Thread
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
val result = ChipReader.read(tag, citizenId) { progress, message ->
|
|
129
|
+
updateProgress(progress, message)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
vibrate(200)
|
|
133
|
+
scanCompleted = true
|
|
134
|
+
sendResult(result)
|
|
135
|
+
|
|
136
|
+
runOnUiThread {
|
|
137
|
+
window.decorView.postDelayed({ finish() }, 800)
|
|
138
|
+
}
|
|
139
|
+
} catch (e: ChipReadException) {
|
|
140
|
+
Log.e("NFCSDK", "Chip read failed: ${e.message}")
|
|
141
|
+
updateProgress(-1, e.message ?: "Đọc NFC thất bại")
|
|
142
|
+
isReading = false
|
|
143
|
+
} catch (e: Exception) {
|
|
144
|
+
Log.e("NFCSDK", "NFC read failed", e)
|
|
145
|
+
updateProgress(-1, "Đọc NFC thất bại")
|
|
146
|
+
isReading = false
|
|
147
|
+
}
|
|
148
|
+
}.start()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun sendResult(result: ChipReadResult) {
|
|
152
|
+
val resultMap = Arguments.createMap().apply {
|
|
153
|
+
putString("citizenId", result.citizenId)
|
|
154
|
+
putString("fullName", result.fullName)
|
|
155
|
+
putString("dob", result.dob)
|
|
156
|
+
putString("gender", result.gender)
|
|
157
|
+
putString("nationality", result.nationality)
|
|
158
|
+
putString("permanentAddress", result.permanentAddress)
|
|
159
|
+
putString("issueDate", result.issueDate)
|
|
160
|
+
putString("issuePlace", result.issuePlace)
|
|
161
|
+
putString("expireDate", result.expireDate)
|
|
162
|
+
putString("imageFromChip", result.imageFromChipBase64)
|
|
163
|
+
putString("frontCardBase64", "")
|
|
164
|
+
putString("backCardBase64", "")
|
|
165
|
+
putString("dg1DataB64", result.dg1DataB64)
|
|
166
|
+
putString("dg2DataB64", result.dg2DataB64)
|
|
167
|
+
putString("dg13DataB64", result.dg13DataB64)
|
|
168
|
+
putString("dg14DataB64", result.dg14DataB64)
|
|
169
|
+
putString("sodData", result.sodData)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
NFCSDKModule.sendEvent("onNfcCompleted", resultMap)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── UI Setup ───────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
private fun setupWindow() {
|
|
178
|
+
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
179
|
+
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
|
180
|
+
window.setLayout(
|
|
181
|
+
android.view.WindowManager.LayoutParams.MATCH_PARENT,
|
|
182
|
+
android.view.WindowManager.LayoutParams.MATCH_PARENT,
|
|
183
|
+
)
|
|
184
|
+
window.setGravity(Gravity.BOTTOM)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fun setupTransition() {
|
|
188
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
189
|
+
overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0)
|
|
190
|
+
} else {
|
|
191
|
+
@Suppress("DEPRECATION")
|
|
192
|
+
overridePendingTransition(0, 0)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private fun setupSheetAnimation() {
|
|
197
|
+
val sheetScrim = findViewById<View>(R.id.sheetScrim)
|
|
198
|
+
val sheetContainer = findViewById<View>(R.id.sheetContainer)
|
|
199
|
+
|
|
200
|
+
sheetScrim.alpha = 0f
|
|
201
|
+
sheetContainer.post {
|
|
202
|
+
sheetContainer.translationY = sheetContainer.height.toFloat()
|
|
203
|
+
sheetScrim.animate().alpha(1f).setDuration(300).start()
|
|
204
|
+
sheetContainer.animate()
|
|
205
|
+
.translationY(0f)
|
|
206
|
+
.setDuration(300)
|
|
207
|
+
.setInterpolator(android.view.animation.DecelerateInterpolator())
|
|
208
|
+
.start()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
sheetScrim.setOnClickListener {
|
|
212
|
+
if (!isReading) finish()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private fun setupViews() {
|
|
217
|
+
progressBar = findViewById(R.id.nfcProgressBar)
|
|
218
|
+
progressPercentTextView = findViewById(R.id.tvProgressPercent)
|
|
219
|
+
retryButton = findViewById(R.id.btnRetry)
|
|
220
|
+
|
|
221
|
+
retryButton.setOnClickListener {
|
|
222
|
+
isReading = false
|
|
223
|
+
scanCompleted = false
|
|
224
|
+
progressBar.progress = 0
|
|
225
|
+
retryButton.visibility = View.GONE
|
|
226
|
+
updateProgress(0, "Đưa CCCD sát mặt lưng điện thoại")
|
|
227
|
+
vibrate(120)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Progress & Feedback ────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
private fun updateProgress(progress: Int, message: String) {
|
|
234
|
+
runOnUiThread {
|
|
235
|
+
if (progress >= 0) {
|
|
236
|
+
progressBar.progress = progress
|
|
237
|
+
progressPercentTextView.text = "$progress%"
|
|
238
|
+
} else {
|
|
239
|
+
progressPercentTextView.text = "--%"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
retryButton.visibility = if (progress < 0) View.VISIBLE else View.GONE
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
val params = Arguments.createMap().apply {
|
|
246
|
+
putInt("progress", progress)
|
|
247
|
+
putString("message", message)
|
|
248
|
+
}
|
|
249
|
+
NFCSDKModule.sendEvent("onNfcProgress", params)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private fun vibrate(durationMs: Long) {
|
|
253
|
+
try {
|
|
254
|
+
@Suppress("DEPRECATION")
|
|
255
|
+
val vibrator = getSystemService(VIBRATOR_SERVICE) as Vibrator
|
|
256
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
257
|
+
vibrator.vibrate(
|
|
258
|
+
VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE),
|
|
259
|
+
)
|
|
260
|
+
} else {
|
|
261
|
+
@Suppress("DEPRECATION")
|
|
262
|
+
vibrator.vibrate(durationMs)
|
|
263
|
+
}
|
|
264
|
+
} catch (_: Exception) {
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── Finish Animation ───────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
override fun finish() {
|
|
271
|
+
val sheetScrim = findViewById<View>(R.id.sheetScrim)
|
|
272
|
+
val sheetContainer = findViewById<View>(R.id.sheetContainer)
|
|
273
|
+
|
|
274
|
+
if (sheetScrim != null && sheetContainer != null) {
|
|
275
|
+
sheetScrim.animate().alpha(0f).setDuration(250).start()
|
|
276
|
+
sheetContainer.animate()
|
|
277
|
+
.translationY(sheetContainer.height.toFloat())
|
|
278
|
+
.setDuration(250)
|
|
279
|
+
.setInterpolator(android.view.animation.AccelerateInterpolator())
|
|
280
|
+
.withEndAction {
|
|
281
|
+
super.finish()
|
|
282
|
+
suppressTransition()
|
|
283
|
+
}
|
|
284
|
+
.start()
|
|
285
|
+
} else {
|
|
286
|
+
super.finish()
|
|
287
|
+
suppressTransition()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun suppressTransition() {
|
|
292
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
293
|
+
overrideActivityTransition(OVERRIDE_TRANSITION_CLOSE, 0, 0)
|
|
294
|
+
} else {
|
|
295
|
+
@Suppress("DEPRECATION")
|
|
296
|
+
overridePendingTransition(0, 0)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
package com.nfcsdk.utils
|
|
2
|
+
|
|
3
|
+
import java.nio.charset.Charset
|
|
4
|
+
import java.text.Normalizer
|
|
5
|
+
import java.text.ParseException
|
|
6
|
+
import java.text.SimpleDateFormat
|
|
7
|
+
import java.util.Date
|
|
8
|
+
import java.util.Locale
|
|
9
|
+
|
|
10
|
+
data class Dg13ParsedData(
|
|
11
|
+
val gender: String = "",
|
|
12
|
+
val permanentAddress: String = "",
|
|
13
|
+
val issueDate: String = "",
|
|
14
|
+
val issuePlace: String = "",
|
|
15
|
+
val expireDate: String = "",
|
|
16
|
+
val fieldMap: Map<Int, List<String>> = emptyMap(),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
private data class Asn1Node(
|
|
20
|
+
val tagClass: Int,
|
|
21
|
+
val tagNumber: Int,
|
|
22
|
+
val constructed: Boolean,
|
|
23
|
+
val valueOffset: Int,
|
|
24
|
+
val valueLength: Int,
|
|
25
|
+
val nextOffset: Int,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
object Dg13Parser {
|
|
29
|
+
private const val TAG_CLASS_UNIVERSAL = 0
|
|
30
|
+
private const val TAG_CLASS_APPLICATION = 1
|
|
31
|
+
|
|
32
|
+
fun parse(
|
|
33
|
+
dg13Bytes: ByteArray,
|
|
34
|
+
fallbackIssuePlace: String = "",
|
|
35
|
+
): Dg13ParsedData {
|
|
36
|
+
if (dg13Bytes.isEmpty()) {
|
|
37
|
+
return Dg13ParsedData(issuePlace = fallbackIssuePlace)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
val fields = parseStructuredFields(dg13Bytes)
|
|
41
|
+
?: return Dg13ParsedData(issuePlace = fallbackIssuePlace)
|
|
42
|
+
|
|
43
|
+
val candidates = fields.values.flatten().distinct()
|
|
44
|
+
|
|
45
|
+
return Dg13ParsedData(
|
|
46
|
+
gender = normalizeGender(fields[0x04]?.firstOrNull().orEmpty()),
|
|
47
|
+
permanentAddress = fields[0x09]?.firstOrNull().orEmpty(),
|
|
48
|
+
issueDate = normalizeDate(fields[0x0B]?.firstOrNull().orEmpty()),
|
|
49
|
+
issuePlace = findIssuePlace(candidates) ?: fallbackIssuePlace,
|
|
50
|
+
expireDate = normalizeDate(fields[0x0C]?.firstOrNull().orEmpty()),
|
|
51
|
+
fieldMap = fields,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fun normalizeGender(raw: String): String {
|
|
56
|
+
return when (normalizeForSearch(raw)) {
|
|
57
|
+
"M", "MALE", "NAM" -> "Nam"
|
|
58
|
+
"F", "FEMALE", "NU" -> "Nữ"
|
|
59
|
+
else -> raw.trim()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private fun parseStructuredFields(bytes: ByteArray): Map<Int, List<String>>? {
|
|
64
|
+
val root = parseNode(bytes, 0) ?: return null
|
|
65
|
+
if (root.tagClass != TAG_CLASS_APPLICATION || root.tagNumber != 13) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
val rootChildren = parseChildren(bytes, root)
|
|
70
|
+
val sequenceNode = rootChildren.firstOrNull() ?: return null
|
|
71
|
+
val sequenceChildren = parseChildren(bytes, sequenceNode)
|
|
72
|
+
val setNode = sequenceChildren.firstOrNull {
|
|
73
|
+
it.tagClass == TAG_CLASS_UNIVERSAL && it.tagNumber == 17
|
|
74
|
+
} ?: return null
|
|
75
|
+
|
|
76
|
+
val fieldNodes = parseChildren(bytes, setNode)
|
|
77
|
+
if (fieldNodes.isEmpty()) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
val result = linkedMapOf<Int, List<String>>()
|
|
82
|
+
fieldNodes.forEach { fieldNode ->
|
|
83
|
+
if (!(fieldNode.tagClass == TAG_CLASS_UNIVERSAL && fieldNode.tagNumber == 16)) {
|
|
84
|
+
return@forEach
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val parts = parseChildren(bytes, fieldNode)
|
|
88
|
+
val indexNode = parts.firstOrNull {
|
|
89
|
+
it.tagClass == TAG_CLASS_UNIVERSAL && it.tagNumber == 2
|
|
90
|
+
} ?: return@forEach
|
|
91
|
+
|
|
92
|
+
val index = parseInteger(bytes, indexNode) ?: return@forEach
|
|
93
|
+
val values = parts
|
|
94
|
+
.drop(1)
|
|
95
|
+
.flatMap { extractStrings(bytes, it) }
|
|
96
|
+
.map(::cleanValue)
|
|
97
|
+
.filter { it.isNotBlank() }
|
|
98
|
+
|
|
99
|
+
if (values.isNotEmpty()) {
|
|
100
|
+
result[index] = values
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result.takeIf { it.isNotEmpty() }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun parseChildren(bytes: ByteArray, parent: Asn1Node): List<Asn1Node> {
|
|
108
|
+
if (!parent.constructed) {
|
|
109
|
+
return emptyList()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
val children = mutableListOf<Asn1Node>()
|
|
113
|
+
var offset = parent.valueOffset
|
|
114
|
+
val end = parent.valueOffset + parent.valueLength
|
|
115
|
+
|
|
116
|
+
while (offset < end) {
|
|
117
|
+
val child = parseNode(bytes, offset) ?: break
|
|
118
|
+
if (child.nextOffset > end) {
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
children.add(child)
|
|
122
|
+
offset = child.nextOffset
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return children
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun parseNode(bytes: ByteArray, offset: Int): Asn1Node? {
|
|
129
|
+
if (offset >= bytes.size) {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
var cursor = offset
|
|
134
|
+
val firstTagByte = bytes[cursor].toInt() and 0xFF
|
|
135
|
+
cursor += 1
|
|
136
|
+
|
|
137
|
+
val tagClass = (firstTagByte ushr 6) and 0x03
|
|
138
|
+
val constructed = (firstTagByte and 0x20) != 0
|
|
139
|
+
var tagNumber = firstTagByte and 0x1F
|
|
140
|
+
|
|
141
|
+
if (tagNumber == 0x1F) {
|
|
142
|
+
tagNumber = 0
|
|
143
|
+
while (cursor < bytes.size) {
|
|
144
|
+
val next = bytes[cursor].toInt() and 0xFF
|
|
145
|
+
cursor += 1
|
|
146
|
+
tagNumber = (tagNumber shl 7) or (next and 0x7F)
|
|
147
|
+
if ((next and 0x80) == 0) {
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (cursor >= bytes.size) {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
val lengthByte = bytes[cursor].toInt() and 0xFF
|
|
158
|
+
cursor += 1
|
|
159
|
+
|
|
160
|
+
val valueLength =
|
|
161
|
+
if ((lengthByte and 0x80) == 0) {
|
|
162
|
+
lengthByte
|
|
163
|
+
} else {
|
|
164
|
+
val count = lengthByte and 0x7F
|
|
165
|
+
if (count == 0 || count > 4 || cursor + count > bytes.size) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
var length = 0
|
|
170
|
+
repeat(count) {
|
|
171
|
+
length = (length shl 8) or (bytes[cursor + it].toInt() and 0xFF)
|
|
172
|
+
}
|
|
173
|
+
cursor += count
|
|
174
|
+
length
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
val nextOffset = cursor + valueLength
|
|
178
|
+
if (nextOffset > bytes.size) {
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return Asn1Node(
|
|
183
|
+
tagClass = tagClass,
|
|
184
|
+
tagNumber = tagNumber,
|
|
185
|
+
constructed = constructed,
|
|
186
|
+
valueOffset = cursor,
|
|
187
|
+
valueLength = valueLength,
|
|
188
|
+
nextOffset = nextOffset,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private fun parseInteger(bytes: ByteArray, node: Asn1Node): Int? {
|
|
193
|
+
if (node.tagClass != TAG_CLASS_UNIVERSAL || node.tagNumber != 2) {
|
|
194
|
+
return null
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
var result = 0
|
|
198
|
+
for (index in 0 until node.valueLength) {
|
|
199
|
+
result = (result shl 8) or (bytes[node.valueOffset + index].toInt() and 0xFF)
|
|
200
|
+
}
|
|
201
|
+
return result
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private fun extractStrings(bytes: ByteArray, node: Asn1Node): List<String> {
|
|
205
|
+
if (node.constructed) {
|
|
206
|
+
return parseChildren(bytes, node).flatMap { child ->
|
|
207
|
+
extractStrings(bytes, child)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (node.tagClass != TAG_CLASS_UNIVERSAL) {
|
|
212
|
+
return emptyList()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
val value = bytes.copyOfRange(node.valueOffset, node.nextOffset)
|
|
216
|
+
val decoded = when (node.tagNumber) {
|
|
217
|
+
12, 19, 20, 22 -> decodeString(value, Charsets.UTF_8)
|
|
218
|
+
30 -> decodeString(value, Charsets.UTF_16BE)
|
|
219
|
+
else -> ""
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return decoded.takeIf { it.isNotBlank() }?.let(::listOf) ?: emptyList()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun decodeString(value: ByteArray, charset: Charset): String {
|
|
226
|
+
return value.toString(charset).trim()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private fun cleanValue(raw: String): String {
|
|
230
|
+
return raw.replace(Regex("""\s+"""), " ").trim()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private fun normalizeForSearch(raw: String): String {
|
|
234
|
+
return Normalizer.normalize(raw, Normalizer.Form.NFD)
|
|
235
|
+
.replace(Regex("""\p{M}+"""), "")
|
|
236
|
+
.uppercase(Locale.ROOT)
|
|
237
|
+
.trim()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private fun findIssuePlace(candidates: List<String>): String? {
|
|
241
|
+
return candidates.firstOrNull { candidate ->
|
|
242
|
+
val normalized = normalizeForSearch(candidate)
|
|
243
|
+
normalized.contains("CONG AN") || normalized.contains("CANH SAT")
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private fun normalizeDate(raw: String): String {
|
|
248
|
+
val parsed = tryParseDate(raw) ?: return raw.trim()
|
|
249
|
+
return OUTPUT_DATE_FORMAT.format(parsed)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private fun tryParseDate(raw: String): Date? {
|
|
253
|
+
val clean = raw.trim()
|
|
254
|
+
for (pattern in DATE_PATTERNS) {
|
|
255
|
+
try {
|
|
256
|
+
return SimpleDateFormat(pattern, Locale.ROOT).apply {
|
|
257
|
+
isLenient = false
|
|
258
|
+
}.parse(clean)
|
|
259
|
+
} catch (_: ParseException) {
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return null
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private val OUTPUT_DATE_FORMAT =
|
|
267
|
+
SimpleDateFormat("dd/MM/yyyy", Locale.ROOT).apply {
|
|
268
|
+
isLenient = false
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private val DATE_PATTERNS =
|
|
272
|
+
listOf(
|
|
273
|
+
"dd/MM/yyyy",
|
|
274
|
+
"yyyy-MM-dd",
|
|
275
|
+
"ddMMyyyy",
|
|
276
|
+
"yyyyMMdd",
|
|
277
|
+
)
|
|
278
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package com.nfcsdk.utils
|
|
2
|
+
|
|
3
|
+
import android.util.Base64
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import org.jmrtd.lds.icao.DG2File
|
|
6
|
+
import java.io.ByteArrayInputStream
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Trích xuất ảnh khuôn mặt từ DG2 (biometric data).
|
|
10
|
+
*/
|
|
11
|
+
object FaceExtractor {
|
|
12
|
+
|
|
13
|
+
private const val TAG = "NFCSDK"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Trích xuất ảnh khuôn mặt đầu tiên từ DG2 raw bytes.
|
|
17
|
+
* @return Base64-encoded image string, hoặc null nếu không tìm thấy.
|
|
18
|
+
*/
|
|
19
|
+
fun extractBase64(dg2Bytes: ByteArray): String? {
|
|
20
|
+
return try {
|
|
21
|
+
val dg2File = DG2File(ByteArrayInputStream(dg2Bytes))
|
|
22
|
+
val faceInfos = dg2File.faceInfos
|
|
23
|
+
if (faceInfos.isEmpty()) return null
|
|
24
|
+
|
|
25
|
+
val faceImageInfos = faceInfos[0].faceImageInfos
|
|
26
|
+
if (faceImageInfos.isEmpty()) return null
|
|
27
|
+
|
|
28
|
+
val imageBytes = faceImageInfos[0].imageInputStream.readBytes()
|
|
29
|
+
if (imageBytes.isEmpty()) {
|
|
30
|
+
Log.e(TAG, "extractBase64: imageBytes is empty")
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Base64.encodeToString(imageBytes, Base64.NO_WRAP)
|
|
35
|
+
} catch (exception: Exception) {
|
|
36
|
+
Log.e(TAG, "DG2 face extraction failed", exception)
|
|
37
|
+
null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
package com.nfcsdk.utils
|
|
2
|
+
|
|
3
|
+
import java.util.Calendar
|
|
4
|
+
import java.util.Locale
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Utility functions xử lý dữ liệu MRZ (Machine Readable Zone) từ DG1.
|
|
8
|
+
*/
|
|
9
|
+
object MrzUtils {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format ngày sinh từ MRZ (YYMMDD) sang DD/MM/YYYY.
|
|
13
|
+
* Sử dụng năm hiện tại làm ngưỡng xác định thế kỷ (19xx vs 20xx).
|
|
14
|
+
*/
|
|
15
|
+
fun formatBirthDate(value: String): String {
|
|
16
|
+
if (value.length != 6) return value
|
|
17
|
+
|
|
18
|
+
val yy = value.substring(0, 2)
|
|
19
|
+
val mm = value.substring(2, 4)
|
|
20
|
+
val dd = value.substring(4, 6)
|
|
21
|
+
val currentYearShort = Calendar.getInstance().get(Calendar.YEAR) % 100
|
|
22
|
+
val year = if (yy.toIntOrNull() ?: 0 > currentYearShort) "19$yy" else "20$yy"
|
|
23
|
+
|
|
24
|
+
return "$dd/$mm/$year"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format ngày hết hạn từ MRZ (YYMMDD) sang DD/MM/20YY.
|
|
29
|
+
* CCCD luôn hết hạn trong thế kỷ 21.
|
|
30
|
+
*/
|
|
31
|
+
fun formatExpireDate(value: String): String {
|
|
32
|
+
if (value.length != 6) return value
|
|
33
|
+
|
|
34
|
+
val yy = value.substring(0, 2)
|
|
35
|
+
val mm = value.substring(2, 4)
|
|
36
|
+
val dd = value.substring(4, 6)
|
|
37
|
+
|
|
38
|
+
return "$dd/$mm/20$yy"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ghép primaryIdentifier + secondaryIdentifier từ MRZ thành họ tên.
|
|
43
|
+
* Thay ký tự '<' bằng khoảng trắng và chuẩn hóa.
|
|
44
|
+
*/
|
|
45
|
+
fun normalizeName(primary: String?, secondary: String?): String {
|
|
46
|
+
return listOf(primary, secondary)
|
|
47
|
+
.filterNotNull()
|
|
48
|
+
.joinToString(" ")
|
|
49
|
+
.replace("<", " ")
|
|
50
|
+
.replace(Regex("\\s+"), " ")
|
|
51
|
+
.trim()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Chuẩn hóa giới tính từ MRZ sang tiếng Việt.
|
|
56
|
+
*/
|
|
57
|
+
fun normalizeGender(raw: String): String {
|
|
58
|
+
return when (raw.trim().uppercase(Locale.ROOT)) {
|
|
59
|
+
"M", "MALE", "NAM" -> "Nam"
|
|
60
|
+
"F", "FEMALE", "NU" -> "Nữ"
|
|
61
|
+
else -> "Khác"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Chuẩn hóa quốc tịch từ mã ISO 3166 sang tiếng Việt.
|
|
67
|
+
*/
|
|
68
|
+
fun normalizeNationality(raw: String?): String {
|
|
69
|
+
return when (raw?.uppercase(Locale.ROOT)) {
|
|
70
|
+
"VNM" -> "Việt Nam"
|
|
71
|
+
null, "" -> "Việt Nam"
|
|
72
|
+
else -> raw
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
android:shape="rectangle">
|
|
4
|
+
<solid android:color="#FFF7ED" />
|
|
5
|
+
<corners
|
|
6
|
+
android:topLeftRadius="28dp"
|
|
7
|
+
android:topRightRadius="28dp"
|
|
8
|
+
android:bottomLeftRadius="24dp"
|
|
9
|
+
android:bottomRightRadius="24dp" />
|
|
10
|
+
<stroke
|
|
11
|
+
android:width="1dp"
|
|
12
|
+
android:color="#FED7AA" />
|
|
13
|
+
</shape>
|