// // BrizziApi.swift // Emoney Info // // Created by Wira Irawan on 27/07/24. // import Foundation import CoreNFC public class BrizziApi : UnifiedNfcApi { var emoney : Emoney = Emoney() var riwayatList: [RiwayatCard] = [] var uid : String? var rawLog : String = "" private var historyRetryCount = 0 public override init() {} public func getUid(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_UID01, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0xAF){ self.getUid02() } else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) } }) } private func getUid02(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_UID02, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0xAF){ self.getUid02() } else { self.uid = response.getData().hexEncodedString().subString(from: 0, to: 14) self.getCardNumber() } }) } private func getCardNumber(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU01, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0x00){ self.emoney.setCardLabel("Brizzi") self.emoney.setCardNumber(response.getData().hexEncodedString().subString(from: 6, to: 22)) self.process01() } else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) } }) } private func process01(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU02, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0x00){ self.process02() } else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) } }) } private func process02(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU03, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){ self.process03(data: response.getData()) } else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) } }) } private func process03(data : Data){ let brizziSamHelper = BrizziSamHelper() brizziSamHelper.keyCard = data.hexEncodedString() let random = "8DC0DC40FE1DC582CF7099E2AACFBC10".hex2byte() guard let uid = self.uid else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) return } let command = self.emoney.getCardNumber() + uid + "FF" guard let decrypted = BrizziSamHelper.decryptDeSeDe(random)?.hexEncodedString() else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) return } let decryptedFinal = decrypted.subString(from: 0, to: 32) guard decryptedFinal.count == 32, let encrypted = BrizziSamHelper.encryptDeSeDe(command, decryptedFinal, "0000000000000000")?.hexEncodedString() else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) return } brizziSamHelper.encryptedKey = encrypted.subString(from: 0, to: 32) let randomHex = "3C37029CA595FE4E7E62FCB2F7909B2C".hex2byte() let randomHexDecrypted = BrizziSamHelper.decryptDeSeDe(randomHex) let randomHexFinal = randomHexDecrypted?.hexEncodedString().subString(from: 0, to: 32) ?? "" guard randomHexFinal.count == 32, let encryptedKey = brizziSamHelper.encryptedKey, let randomHexEncrypted = BrizziSamHelper.encryptDeSeDe(encryptedKey, randomHexFinal, brizziSamHelper.authKey)?.hexEncodedString() else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) return } brizziSamHelper.random = randomHexEncrypted.subString(from: 0, to: 32) let samChallenge = brizziSamHelper.generateSamRandom().subString(from: 0, to: 32) guard samChallenge.count == 32 else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) return } let BRI_APDU04 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xAF, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: samChallenge.hex2byte()), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA) apduRunner.exchangeApdu(apduCommand: BRI_APDU04, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){ self.process04() } else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) } }) } private func process04(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU05, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){ self.emoney.setBalance(self.getRealBalance(reverseHexa: response.getData().hexEncodedString().subString(from: 0, to: 8))) self.historyRetryCount = 0 self.startHistoryRead() } else { self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!)) } }) } private func startHistoryRead() { rawLog = "" riwayatList.removeAll() getLog() } private func retryHistoryRead(reason: String) { if historyRetryCount < 1 { historyRetryCount += 1 debugLog("Retrying Brizzi history read: \(reason)") startHistoryRead() } else { finalizeHistoryRead() } } private func finalizeHistoryRead() { if (self.parseLog()){ self.riwayatList = self.riwayatList.sorted { ($0.getTransationTime() ?? Date.distantPast) > ($1.getTransationTime() ?? Date.distantPast) } self.emoney.setRiwayatList(self.riwayatList) self.emoney.setTampilRiwayat(true) } self.updateScreen() self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!) self.apduRunner.invalidateSession() } private func getLog(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_LOG01, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){ self.rawLog.append(response.getData().hexEncodedString()) self.getMoreLog() } else { self.retryHistoryRead(reason: "initial log status \(response.sw1)-\(response.sw2)") } }) } private func getMoreLog(){ apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_LOG02, completionHandler: {response in if (response.sw1 == 0x91 && response.sw2 == 0xAF){ self.rawLog.append(response.getData().hexEncodedString()) self.getMoreLog() } else { if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x90 && response.sw2 == 0x00) { self.rawLog.append(response.getData().hexEncodedString()) self.finalizeHistoryRead() } else { self.retryHistoryRead(reason: "continuation log status \(response.sw1)-\(response.sw2)") } } }) } private func parseLog() -> Bool { let logs = self.rawLog.trimmingCharacters(in: .whitespacesAndNewlines) if (logs.count % 64 != 0){ return false } let total = logs.count/64 for i in 0.. Date?{ let dateFormatter2 = DateFormatter() dateFormatter2.locale = Locale(identifier: "en_US_POSIX") dateFormatter2.dateFormat = "HHmmss" guard let date12 = dateFormatter2.date(from: formatTime) else { return nil } dateFormatter2.dateFormat = "hh:mm a" let date22 = dateFormatter2.string(from: date12) let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "en_US_POSIX") // set locale to reliable US_POSIX dateFormatter.dateFormat = "ddMMyy hh:mm a" return dateFormatter.date(from:(formatDate + " " + date22)) } private func getCode(trxCode: String) -> String { let trxUp = trxCode.uppercased() if trxUp.contains("5F") { return "reactivation".localizeString(string: self.langCode!) } else if trxUp.contains("EB") { return "payment".localizeString(string: self.langCode!) } else if trxUp.contains("EC") { return "topup".localizeString(string: self.langCode!) } else if trxUp.contains("ED") { return "void".localizeString(string: self.langCode!) } else if trxUp.contains("EF") { return "updateBalance".localizeString(string: self.langCode!) } else { return "-" } } private func getTipe(trxCode: String) -> Int { let trxUp = trxCode.uppercased() if trxUp.contains("5F") { return 2 } else if trxUp.contains("EB") { return 1 } else if trxUp.contains("EC") { return 0 } else if trxUp.contains("ED") { return 0 } else if trxUp.contains("EF") { return 1 } else { return 0 } } func getRealBalance(reverseHexa: String?) -> Int { guard let reverseHexa = reverseHexa?.trimmingCharacters(in: .whitespacesAndNewlines), !reverseHexa.isEmpty else { return 0 } if reverseHexa.count % 2 != 0 { return 0 } var sb = "" for l in stride(from: reverseHexa.count / 2, through: 1, by: -1) { let index1 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 2) let index2 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 1) sb.append(reverseHexa[index1]) sb.append(reverseHexa[index2]) } return sb.hex2decimal() } private func updateScreen(){ if (self.apduRunner.callback != nil){ self.apduRunner.callback?.complete(emoney: self.emoney) } } }