Initial commit

This commit is contained in:
Wira Basalamah
2026-04-24 04:55:24 +07:00
commit 8f0b001501
128 changed files with 9366 additions and 0 deletions

View File

@ -0,0 +1,408 @@
//
// BcaFlazzApi.swift
// Emoney Info
//
// Created by Wira Irawan on 27/07/24.
//
import Foundation
import CoreNFC
public class BcaFlazzApi : UnifiedNfcApi {
var emoney : Emoney = Emoney()
var riwayatList: [RiwayatCard] = []
var start = 0
var finishV2 = 5
var finish2V202 = 256
var finishV1 = 16
var mapv1 : String = ""
var mapv2 : String = ""
public override init() {}
public func checkFlazzCard(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU01, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.emoney.setCardLabel("BCA Flazz")
self.getCardNumber()
} else {
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
}
})
}
private func getCardNumber(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU02, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
let raw = String(data: response.getData(), encoding: .isoLatin1)
let start = raw!.firstIndex(of: ";")?.utf16Offset(in: raw!)
let end = raw!.firstIndex(of: "=")?.utf16Offset(in: raw!)
self.emoney.setCardNumber(String((raw?.subString(from: (start! + 1), to: end!))!))
self.getBalance()
} else {
self.apduRunner.invalidateSession()
}
})
}
private func getBalance(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU03, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
let raw = response.getData().hexEncodedString()
let balance = raw.subString(from: 2, to: 8)
self.emoney.setBalance(balance.hex2decimal())
self.checkLog()
} else {
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
}
})
}
private func checkLog(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU04, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog("log v2")
// self.updateScreen()
self.getLogV2step01(index: self.start)
} else {
debugLog("log v1")
// self.updateScreen()
self.getLogV1step01(index: self.start)
}
})
}
private func getLogV2step01(index : Int){
debugLog("log getLogV2step01")
//00 B0 85 00 78
let st = index*60
let hex = String(st, radix: 16)
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x85, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 120)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog("log data", response.getData().hexEncodedString())
self.mapv1.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finishV2)){
self.getLogV2step01(index: self.start)
} else {
//mapping first
self.parseLogV201()
self.start = 0
self.getLogV2step02(index: self.start)
}
} else {
self.parseLogV201()
self.start = 0
self.getLogV2step02(index: self.start)
}
})
}
private func getLogV1step01(index : Int){
debugLog("log getLogV1step01")
//00 b0 84 00 3c
let st = index*15
let hex = String(st, radix: 16)
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x84, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 60)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.mapv1.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finishV1)){
self.getLogV1step01(index: self.start)
} else {
self.start = 0
self.getLogV1step02(index: self.start)
}
} else {
self.start = 0
self.getLogV1step02(index: self.start)
}
})
}
private func getLogV1step02(index : Int){
debugLog("log getLogV1step02")
//00b085003c
let st = index*15
let hex = String(st, radix: 16)
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x85, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 60)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.mapv1.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finishV1)){
self.getLogV1step02(index: self.start)
} else {
self.parseLogV101()
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
if (self.riwayatList.count > 0){
self.emoney.setRiwayatList(self.riwayatList)
self.emoney.setTampilRiwayat(true)
}
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
} else {
self.parseLogV101()
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
if (self.riwayatList.count > 0){
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 getLogV2step02(index : Int){
debugLog("log getLogV2step02")
//00b08400f0
let st = index*60
let hex = String(st, radix: 16)
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x84, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 240)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.mapv1.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finishV2)){
self.getLogV2step02(index: self.start)
} else {
self.start = 0
self.getLogV2step03()
}
} else {
self.start = 0
self.getLogV2step03()
}
})
}
private func getLogV2step03(){
debugLog("log getLogV2step03")
//00 84 00 00 08
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0x84, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : 8)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.getLogV2step04(data: response.getData())
} else {
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
})
}
private func getLogV2step04(data: Data){
debugLog("log getLogV2step04")
//90 32 03 00 0A 0801 0000000000000000 29
let send = "0801" + data.hexEncodedString()
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_: send.hex2byte()), expectedResponseLength : 41)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.getLogV2step05(index: self.start)
} else {
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
})
}
private func getLogV2step05(index : Int){
debugLog("log getLogV2step05")
//00 B0 89 00 40
let st = index
let hex = String(st, radix: 16)
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x89, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 64)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.mapv2.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finish2V202)){
self.getLogV2step05(index: self.start)
} else {
self.start = 0
self.getLogV2step06(index: self.start)
}
} else {
self.start = 0
self.getLogV2step06(index: self.start)
}
})
}
private func getLogV2step06(index : Int){
debugLog("log getLogV2step06")
//90 32 03 00 01 00 20
let st = index
let hex = String(st, radix: 16)
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_: hex.hex2byte()), expectedResponseLength : 32)
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.mapv2.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finish2V202)){
self.getLogV2step06(index: self.start)
} else {
//mapping the data
self.parseLogV202()
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
if (self.riwayatList.count > 0){
self.emoney.setRiwayatList(self.riwayatList)
self.emoney.setTampilRiwayat(true)
}
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
} else {
//mapping the data
self.parseLogV202()
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
if (self.riwayatList.count > 0){
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 parseLogV101(){
debugLog("log parseLogV101")
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
if (logs.count % 120 != 0){
return
}
let total = logs.count/120
for i in 0..<total {
let start = i*120
let end = start + 120
let data = logs.subString(from: start, to: end)
let riwayat = RiwayatCard()
let location = data.subString(from: 60, to: 76).hex2byte()
let locationId = String(data: location, encoding: .utf8)!
riwayat.setLocationId(locationId)
// riwayat.setLocationName(data.subString(from: 0, to: 16))
let amount = data.subString(from: 12, to: 18).hex2decimal()
riwayat.setAmount(amount)
let transactionTime = data.subString(from: 76, to: 84).hex2decimal()
riwayat.setTransactionTime(formatDate(seconds: transactionTime))
let type = data.subString(from: 0, to: 4).hex2decimal()
if (type == 1024){
riwayat.setProsesTipe(1)
riwayat.setTitle("payment".localizeString(string: self.langCode!))
} else {
riwayat.setProsesTipe(0)
riwayat.setTitle("topup".localizeString(string: self.langCode!))
}
if (transactionTime > 0){
riwayatList.append(riwayat)
}
}
}
private func parseLogV201(){
debugLog("log parseLogV201")
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
if (logs.count % 120 != 0){
return
}
let total = logs.count/120
for i in 0..<total {
let start = i*120
let end = start + 120
let data = logs.subString(from: start, to: end)
let riwayat = RiwayatCard()
let location = data.subString(from: 60, to: 76).hex2byte()
let locationId = String(data: location, encoding: .utf8)!
riwayat.setLocationId(locationId)
// riwayat.setLocationName(data.subString(from: 0, to: 16))
let amount = data.subString(from: 12, to: 18).hex2decimal()
riwayat.setAmount(amount)
let transactionTime = data.subString(from: 76, to: 84).hex2decimal()
riwayat.setTransactionTime(formatDate(seconds: transactionTime))
let type = data.subString(from: 0, to: 4).hex2decimal()
if (type == 1024){
riwayat.setProsesTipe(1)
riwayat.setTitle("payment".localizeString(string: self.langCode!))
} else {
riwayat.setProsesTipe(0)
riwayat.setTitle("topup".localizeString(string: self.langCode!))
}
if (transactionTime > 0){
riwayatList.append(riwayat)
}
}
}
private func parseLogV202(){
debugLog("log parseLogV202")
let logs = self.mapv2.trimmingCharacters(in: .whitespacesAndNewlines)
if (logs.count % 64 != 0){
return
}
let total = logs.count/64
for i in 0..<total {
let start = i*64
let end = start + 64
let data = logs.subString(from: start, to: end)
let riwayat = RiwayatCard()
// let location = data.subString(from: 28, to: 44).hex2byte()
// let locationId = String(data: location, encoding: .utf8)!
// riwayat.setLocationId(locationId)
let transactionTime = data.subString(from: 8, to: 16).hex2decimal()
riwayat.setTransactionTime(formatDate(seconds: transactionTime))
let type = data.subString(from: 0, to: 2).hex2decimal()
let amount = data.subString(from: 2, to: 8).hex2decimal()
if (type == 4){
riwayat.setProsesTipe(1)
riwayat.setAmount(16777216 - amount)
riwayat.setTitle("payment".localizeString(string: self.langCode!))
} else {
riwayat.setProsesTipe(0)
riwayat.setAmount(amount)
riwayat.setTitle("topup".localizeString(string: self.langCode!))
}
if (transactionTime > 0){
riwayatList.append(riwayat)
}
}
}
private func formatDate(seconds : Int) -> Date{
// Specify date components
var dateComponents = DateComponents()
dateComponents.year = 1980
dateComponents.month = 1
dateComponents.day = 1
dateComponents.timeZone = TimeZone(identifier: "Asia/Jakarta")!
dateComponents.hour = 0
dateComponents.minute = 0
dateComponents.second = seconds
// Create date from components
let userCalendar = Calendar.current // user calendar
let someDateTime = userCalendar.date(from: dateComponents)
return someDateTime!
}
private func updateScreen(){
if (self.apduRunner.callback != nil){
self.apduRunner.callback?.complete(emoney: self.emoney)
}
}
}

View File

@ -0,0 +1,247 @@
//
// 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 = ""
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()
let command = self.emoney.getCardNumber() + self.uid! + "FF"
let decrypted = BrizziSamHelper.decryptDeSeDe(random)?.hexEncodedString()
let decryptedFinal = decrypted?.subString(from: 0, to: 32)
let encrypted = BrizziSamHelper.encryptDeSeDe(command, decryptedFinal!, "0000000000000000")?.hexEncodedString()
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)
let randomHexEncrypted = BrizziSamHelper.encryptDeSeDe(brizziSamHelper.encryptedKey!, randomHexFinal!, brizziSamHelper.authKey)
brizziSamHelper.random = (randomHexEncrypted?.hexEncodedString())!.subString(from: 0, to: 32)
let samChallenge = brizziSamHelper.generateSamRandom().subString(from: 0, to: 32)
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.getLog()
} else {
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
}
})
}
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.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
})
}
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 {
self.rawLog.append(response.getData().hexEncodedString())
if (self.parseLog()){
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
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 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..<total {
let start = i*64
let end = start + 64
let data = logs.subString(from: start, to: end)
let riwayat = RiwayatCard()
// riwayat.setLocationId(data.subString(from: 16, to: 32))
// riwayat.setLocationName(data.subString(from: 0, to: 16))
riwayat.setTitle(self.getCode(trxCode: data.subString(from: 44, to: 46)))
riwayat.setProsesTipe(self.getTipe(trxCode: data.subString(from: 44, to: 46)))
riwayat.setAmount(self.getRealBalance(reverseHexa: data.subString(from: 46, to: 52)))
let time = self.getTransactionTime(formatDate: data.subString(from: 32, to: 38), formatTime: data.subString(from: 38, to: 44))
riwayat.setTransactionTime(time!)
riwayatList.append(riwayat)
}
return true
}
func getTransactionTime(formatDate : String, formatTime : String) -> Date?{
let dateFormatter2 = DateFormatter()
dateFormatter2.dateFormat = "HHmmss"
let date12 = dateFormatter2.date(from: formatTime)!
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)
}
}
}

View File

@ -0,0 +1,37 @@
//
// JackCardApi.swift
// Emoney Info
//
// Created by Wira Irawan on 27/07/24.
//
import Foundation
public class JackCardApi : UnifiedNfcApi {
var emoney : Emoney = Emoney()
var riwayatList: [RiwayatCard] = []
public override init() {}
public func getBalance(resp : Data){
self.emoney.setCardLabel("Jackcard")
self.emoney.setCardNumber(resp.hexEncodedString().subString(from: 16, to: 32))
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.DKI_APDU01, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.emoney.setBalance(response.getData().hexEncodedString().hex2decimal())
self.emoney.setTampilRiwayat(false)
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
} else {
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
}
})
}
private func updateScreen(){
if (self.apduRunner.callback != nil){
self.apduRunner.callback?.complete(emoney: self.emoney)
}
}
}

View File

@ -0,0 +1,263 @@
//
// MandiriEmoneyApi.swift
// Emoney Info
//
// Created by Wira Irawan on 27/07/24.
//
import Foundation
import CoreNFC
public class MandiriEmoneyApi : UnifiedNfcApi {
var emoney : Emoney = Emoney()
var riwayatList: [RiwayatCard] = []
var cardType : Int?
var mapv1 : String = ""
var start = 0
var finish = 256
var finish2 = 10
public override init() {}
public func getCardNumber(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU01, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.emoney.setCardLabel("Mandiri e-Money")
self.emoney.setCardNumber(response.getData().hexEncodedString().subString(from: 0, to: 16))
self.cardType = response.getData().hexEncodedString().subString(from: 36, to: 38).hex2decimal()
debugLog(self.cardType!)
self.getBalance()
} else {
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
}
})
}
private func getBalance(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU02, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog(response.getData().hexEncodedString())
let balance = response.getData().hexEncodedString().subString(from: 0, to: 8)
self.emoney.setBalance(self.getRealBalance(reverseHexa: balance))
// self.updateScreen()
if (self.cardType! == 131){
self.getLogStep01(index: self.start)
} else {
self.getLogStep02(index: self.start)
}
} else {
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
}
})
}
private func getLogStep01(index : Int){
//00 d1 00 00 00
let hex = String(index, radix: 16)
let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xD1, p1Parameter : hex.hex2byte().bytes.first!, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.mapv1.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finish)){
self.getLogStep01(index: self.start)
} else {
self.parseNewLog()
self.start = 0
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
self.emoney.setRiwayatList(self.riwayatList)
self.emoney.setTampilRiwayat(true)
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
} else {
self.parseNewLog()
self.start = 0
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
self.emoney.setRiwayatList(self.riwayatList)
self.emoney.setTampilRiwayat(true)
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
})
}
func parseNewLog(){
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
if (logs.count % 48 != 0){
return
}
let total = logs.count/48
for i in 0..<total {
let start = i*48
let end = start + 48
let data = logs.subString(from: start, to: end)
let riwayat = RiwayatCard()
let time = self.getTransactionTime(formatDate: data.subString(from: 0, to: 6), formatTime: data.subString(from: 6, to: 12))
riwayat.setTransactionTime(time!)
let processType = Int(data.subString(from: 28, to: 32))
if (processType == 100){
riwayat.setProsesTipe(0)
riwayat.setTitle("Top Up")
} else {
riwayat.setProsesTipe(1)
riwayat.setTitle("Payment")
}
riwayat.setLocationId(data.subString(from: 12, to: 20))
let amount = data.subString(from: 32, to: 40)
riwayat.setAmount(getRealBalance(reverseHexa: amount))
riwayatList.append(riwayat)
}
}
private func getLogStep02(index : Int){
//00 b2 00 00 1e
let hex = String(index, radix: 16)
let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB2, p1Parameter : hex.hex2byte().bytes.first!, p2Parameter : 0x00, data : Data(), expectedResponseLength : 30)
apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.mapv1.append(response.getData().hexEncodedString())
self.start+=1
if (self.start < (self.finish2)){
self.getLogStep02(index: self.start)
} else {
self.parseOldLog()
self.start = 0
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
self.emoney.setRiwayatList(self.riwayatList)
self.emoney.setTampilRiwayat(true)
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
} else {
self.parseOldLog()
self.start = 0
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
self.emoney.setRiwayatList(self.riwayatList)
self.emoney.setTampilRiwayat(true)
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
})
}
func parseOldLog(){
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
if (logs.count % 60 != 0){
return
}
let total = logs.count/60
for i in 0..<total {
let start = i*60
let end = start + 60
let data = logs.subString(from: start, to: end)
let riwayat = riwayatCard(data.hex2byte().bytes)
riwayatList.append(riwayat!)
}
}
func getTransactionTime(formatDate : String, formatTime : String) -> Date?{
let dateFormatter2 = DateFormatter()
dateFormatter2.dateFormat = "HHmmss"
let date12 = dateFormatter2.date(from: formatTime)!
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))
}
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)
}
}
func riwayatCard(_ bArr: [UInt8]) -> RiwayatCard? {
var str: String
let riwayatCard = RiwayatCard()
var wrap = Data(bArr)
var bArr2 = [UInt8](repeating: 0, count: 6)
var bArr3 = [UInt8](repeating: 0, count: 16)
wrap.copyBytes(to: &bArr2, count: 6)
wrap.removeFirst(10)
let len = wrap.withUnsafeBytes { $0.load(as: Int32.self).bigEndian }
wrap.removeFirst(4)
let amount = wrap.withUnsafeBytes { $0.load(as: Int32.self).littleEndian }
wrap.removeFirst(4)
let desk = wrap.withUnsafeBytes { $0.load(as: Int32.self).littleEndian }
wrap.removeFirst(4)
do {
wrap.copyBytes(to: &bArr3, count: 16)
debugLog("bArr3: \(bArr3.map { String(format: "%02X", $0) }.joined())")
} catch {
debugLog("Error: \(error.localizedDescription)")
}
var type = 0
if len == 288 {
str = "payment".localizeString(string: self.langCode!)
type = 1
} else if len == 256 || len == 336 {
type = 3
str = "topup".localizeString(string: self.langCode!)
} else {
type = -1
str = "unknown".localizeString(string: self.langCode!)
}
var str2 = ""
for y in 0..<6 {
str2 += String(format: "%02X", bArr2[y])
}
let transactionTime = self.getTransactionTime(formatDate: str.subString(from: 0, to: 6), formatTime: str.subString(from: 6, to: 12))
riwayatCard.setTransactionTime(transactionTime!)
riwayatCard.setAmount(Int(amount))
// if str.caseInsensitiveCompare("payment") == .orderedSame {
// type = 1
// } else if str.caseInsensitiveCompare("topup") != .orderedSame {
// type = 3
// }
riwayatCard.setProsesTipe(type)
riwayatCard.setTitle(str)
if type == -1 {
return nil
}
return riwayatCard
}
}

View File

@ -0,0 +1,58 @@
//
// MegaCashApi.swift
// Emoney Info
//
// Created by Wira Irawan on 27/07/24.
//
import Foundation
public class MegaCashApi : UnifiedNfcApi {
var emoney : Emoney = Emoney()
var riwayatList: [RiwayatCard] = []
public override init() {}
public func getBalance(resp : Data){
self.emoney.setCardLabel("MegaCash")
let value = resp.hexEncodedString()
self.emoney.setCardNumber(value.subString(from: 4, to: value.count))
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MEGA_APDU02, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
let balance = response.getData().hexEncodedString()
self.emoney.setBalance(self.getRealBalance(reverseHexa: balance))
self.emoney.setTampilRiwayat(false)
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
} else {
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
}
})
}
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)
}
}
}

View File

@ -0,0 +1,178 @@
//
// TapCashApi.swift
// Emoney Info
//
// Created by Wira Irawan on 26/07/24.
//
import Foundation
import CoreNFC
public class TapCashApi : UnifiedNfcApi {
var emoney : Emoney = Emoney()
var tapCashData : TapCashData = TapCashData()
var riwayatList: [RiwayatCard] = []
var start = 0
var totalLog = 0
public override init() {}
public func checkBalance(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.TAPCASH_APDU01, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.emoney.setCardLabel("BNI TapCash")
self.tapCashData.setPurseData(response.getData().bytes)
let balance = self.tapCashData.getPurseBalance()?.hexString().hex2decimal()
self.emoney.setBalance(balance!)
self.emoney.setCardNumber(self.tapCashData.getCAN()!.hexString())
self.totalLog = (self.tapCashData.getTotalRecords()?.hexString().hex2decimal())!
if (self.totalLog > 10){
self.totalLog = 10
}
debugLog("total log " + String(self.totalLog))
self.getHistory(index: 0)
} else {
self.emoney.setTampilRiwayat(false)
self.updateScreen()
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
})
}
private func getHistory(index : Int){
let st = String(index).leftPad(with: "0", length: 2)
//90 32 03 00 01 00 10
let TAPCASH_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_ : st.stringToBytes()!), expectedResponseLength : 16)
debugLog(TAPCASH_LOG.toHexString())
apduRunner.exchangeApdu(apduCommand: TAPCASH_LOG, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
self.addRiwayatTransaksi(data: response.getData().bytes)
}
self.start+=1
if (self.start < (self.totalLog)){
self.getHistory(index: self.start)
} else {
self.updateScreen()
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
if (self.riwayatList.count > 0){
self.emoney.setRiwayatList(self.riwayatList)
self.emoney.setTampilRiwayat(true)
}
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
}
})
}
private func updateScreen(){
if (self.apduRunner.callback != nil){
self.apduRunner.callback?.complete(emoney: self.emoney)
}
}
private func addRiwayatTransaksi(data: [UInt8]) {
let trxType = Array(data[0..<1])
let trxAmount = Array(data[1..<4])
let trxDateTimes = Array(data[4..<8])
//let trxUserData = Array(data[8..<16])
let title = trxType.hexString()
var amount: Int
if title.lowercased() == "01" || title.lowercased() == "05" || title.lowercased() == "07" || title.lowercased() == "10" || title.lowercased() == "20" {
amount = trxAmount.hexString().secondComplementsAmount()
} else {
amount = trxType.hexString().hex2decimal()
}
let riwayatCard = RiwayatCard()
riwayatCard.setTitle(getStatementTitle(header: trxType.hexString()))
riwayatCard.setProsesTipe(getTranscationType(header: trxType.hexString()))
riwayatCard.setAmount(amount)
let transactionTime = self.getTransactionTime(julian: trxDateTimes.hexString())
riwayatCard.setTransactionTime(transactionTime)
riwayatList.append(riwayatCard)
}
private func getStatementTitle(header: String) -> String {
var title = ""
switch header.uppercased() {
case "01":
title = "payment".localizeString(string: self.langCode!)
case "02":
title = "Black List Card"
case "03":
title = "topup".localizeString(string: self.langCode!)
case "04":
title = "topup".localizeString(string: self.langCode!)
case "05":
title = "statementFee".localizeString(string: self.langCode!)
case "06":
title = "updateBalance".localizeString(string: self.langCode!)
case "07":
title = "gracePeriod".localizeString(string: self.langCode!)
case "10":
title = "refund".localizeString(string: self.langCode!)
case "20":
title = "refund".localizeString(string: self.langCode!)
case "22":
title = "close".localizeString(string: self.langCode!)
case "F0":
title = "atu".localizeString(string: self.langCode!)
default:
break
}
return title
}
private func getTranscationType(header: String) -> Int {
switch header.uppercased() {
case "01":
return 1
case "02":
return 2
case "03":
return 0
case "04":
return 0
case "05":
return 1
case "06":
return 6
case "07":
return 7
case "10":
return 10
case "20":
return 20
case "22":
return 22
default:
break
}
return 0
}
func getTransactionTime(julian: String) -> Date {
let dec = julian.hex2decimal()
let cal = Calendar.current
// set to 1st January 1995
var dateComponents = DateComponents()
dateComponents.year = 1995
dateComponents.month = 1
dateComponents.day = 1
dateComponents.hour = 0
dateComponents.minute = 0
dateComponents.second = 0
if let date = cal.date(from: dateComponents) {
let newDate = date.addingTimeInterval(Double(dec))
return newDate
}
return Date()
}
}

View File

@ -0,0 +1,463 @@
import Foundation
import CoreNFC
extension CFArray {
func toSwiftArray<T>() -> [T] {
let array = Array<AnyObject>(_immutableCocoaArray: self)
return array.compactMap { $0 as? T }
}
}
extension Dictionary where Key == String, Value == Any {
var account: String? {
guard let account = self[kSecAttrAccount as String] as? String else {
return nil
}
return account
}
}
@available(iOS 13.0, *)
public class UnifiedNfcApi {
var stationMap: [Int: Station] = [:]
func parseData() {
stationMap = [
0: Station(id: 0, name: "PARKIR RESKA", subName: "PARKIR RESKA", latitude: "0", longitude: "0"),
1: Station(id: 1, name: "Tanah Abang", subName: "Tanah Abang", latitude: "-6.18574476", longitude: "106.8108382"),
67: Station(id: 67, name: "C-Access", subName: "C-Access", latitude: "0", longitude: "0"),
257: Station(id: 257, name: "Bogor", subName: "Bogor", latitude: "-6.59561005", longitude: "106.7904379"),
258: Station(id: 258, name: "Cilebut", subName: "Cilebut", latitude: "-6.53050343", longitude: "106.8005885"),
259: Station(id: 259, name: "Bojonggede", subName: "Bojonggede", latitude: "-6.49326562", longitude: "106.7949173"),
260: Station(id: 260, name: "Citayam", subName: "Citayam", latitude: "-6.44879141", longitude: "106.8024588"),
261: Station(id: 261, name: "Depok", subName: "Depok", latitude: "-6.40493394", longitude: "106.8172447"),
262: Station(id: 262, name: "Depok Baru", subName: "Depok Baru", latitude: "-6.39113047", longitude: "106.821707"),
263: Station(id: 263, name: "Pondok Cina", subName: "Pondok Cina", latitude: "-6.36905168", longitude: "106.8322114"),
264: Station(id: 264, name: "Univ. Indonesia", subName: "Univ. Indonesia", latitude: "-6.36075528", longitude: "106.8317544"),
265: Station(id: 265, name: "Univ. Pancasila", subName: "Univ. Pancasila", latitude: "-6.33894476", longitude: "106.8344241"),
272: Station(id: 272, name: "Lenteng Agung", subName: "Lenteng Agung", latitude: "-6.33065157", longitude: "106.8349938"),
273: Station(id: 273, name: "Tanjung Barat", subName: "Tanjung Barat", latitude: "-6.30780817", longitude: "106.8388513"),
274: Station(id: 274, name: "Pasar Minggu", subName: "Pasar Minggu", latitude: "-6.28440597", longitude: "106.8445384"),
275: Station(id: 275, name: "Pasar Minggu Baru", subName: "Pasar Minggu Baru", latitude: "-6.26278132", longitude: "106.8518598"),
276: Station(id: 276, name: "Duren Kalibata", subName: "Duren Kalibata", latitude: "-6.25534623", longitude: "106.8550195"),
277: Station(id: 277, name: "Cawang", subName: "Cawang", latitude: "-6.24266069", longitude: "106.8588196"),
278: Station(id: 278, name: "Tebet", subName: "Tebet", latitude: "-6.22606896", longitude: "106.8583004"),
279: Station(id: 279, name: "Manggarai", subName: "Manggarai", latitude: "-6.20992352", longitude: "106.8502129"),
280: Station(id: 280, name: "Cikini", subName: "Cikini", latitude: "-6.19856352", longitude: "106.8412599"),
281: Station(id: 281, name: "Gondangdia", subName: "Gondangdia", latitude: "-6.18594019", longitude: "106.8325942"),
288: Station(id: 288, name: "Juanda", subName: "Juanda", latitude: "-6.16672229", longitude: "106.8304674"),
289: Station(id: 289, name: "Sawah Besar", subName: "Sawah Besar", latitude: "-6.16063965", longitude: "106.8276397"),
290: Station(id: 290, name: "Mangga Besar", subName: "Mangga Besar", latitude: "-6.14979667", longitude: "106.8269796"),
291: Station(id: 291, name: "Jayakarta", subName: "Jayakarta", latitude: "-6.14134112", longitude: "106.8230834"),
292: Station(id: 292, name: "Jakarta Kota", subName: "Jakarta Kota", latitude: "-6.13761335", longitude: "106.8146308"),
293: Station(id: 293, name: "Bekasi", subName: "Bekasi", latitude: "-6.23614485", longitude: "106.9994173"),
294: Station(id: 294, name: "Kranji", subName: "Kranji", latitude: "-6.22433352", longitude: "106.9793992"),
295: Station(id: 295, name: "Cakung", subName: "Cakung", latitude: "-6.21929974", longitude: "106.9521357"),
296: Station(id: 296, name: "Klender Baru", subName: "Klender Baru", latitude: "-6.21743543", longitude: "106.9396893"),
297: Station(id: 297, name: "Buaran", subName: "Buaran", latitude: "-6.21615092", longitude: "106.9283069"),
304: Station(id: 304, name: "Klender", subName: "Klender", latitude: "-6.21335877", longitude: "106.8998889"),
305: Station(id: 305, name: "Jatinegara", subName: "Jatinegara", latitude: "-6.21513342", longitude: "106.8703259"),
313: Station(id: 313, name: "Tangerang", subName: "Tangerang", latitude: "-6.17679787", longitude: "106.63272688"),
327: Station(id: 327, name: "Karet", subName: "Karet", latitude: "-6.2008165", longitude: "106.8159002"),
328: Station(id: 328, name: "Sudirman", subName: "Sudirman", latitude: "-6.202438", longitude: "106.8234505"),
329: Station(id: 329, name: "Tanah Abang", subName: "Tanah Abang", latitude: "-6.18574476", longitude: "106.8108382"),
336: Station(id: 336, name: "Palmerah", subName: "Palmerah", latitude: "-6.20740425", longitude: "106.7974463"),
337: Station(id: 337, name: "Kebayoran", subName: "Kebayoran", latitude: "-6.23718958", longitude: "106.782542"),
338: Station(id: 338, name: "Pondok Ranji", subName: "Pondok Ranji", latitude: "-6.27633762", longitude: "106.7449376"),
339: Station(id: 339, name: "Jurang Mangu", subName: "Jurang Mangu", latitude: "-6.28876225", longitude: "106.7291141"),
340: Station(id: 340, name: "Sudimara", subName: "Sudimara", latitude: "-6.29694285", longitude: "106.7127952"),
341: Station(id: 341, name: "Rawabuntu", subName: "Rawabuntu", latitude: "-6.31500105", longitude: "106.6761968"),
342: Station(id: 342, name: "Serpong", subName: "Serpong", latitude: "-6.32004857", longitude: "106.6655717"),
343: Station(id: 343, name: "Cisauk", subName: "Cisauk", latitude: "-6.3249995", longitude: "106.6407467"),
344: Station(id: 344, name: "Cicayur", subName: "Cicayur", latitude: "-6.32951436", longitude: "106.6189624"),
345: Station(id: 345, name: "Parung Panjang", subName: "Parung Panjang", latitude: "-6.34420808", longitude: "106.5698061"),
352: Station(id: 352, name: "Cilejit", subName: "Cilejit", latitude: "-6.35434367", longitude: "106.5097328"),
353: Station(id: 353, name: "Daru", subName: "Daru", latitude: "-6.33800742", longitude: "106.4923913"),
354: Station(id: 354, name: "Tenjo", subName: "Tenjo", latitude: "-6.32725713", longitude: "106.4613542"),
355: Station(id: 355, name: "Tigaraksa", subName: "Tigaraksa", latitude: "-6.32846118", longitude: "106.4347451"),
356: Station(id: 356, name: "Maja", subName: "Maja", latitude: "-6.33230387", longitude: "106.3965692"),
357: Station(id: 357, name: "Citeras", subName: "Citeras", latitude: "-6.33492764", longitude: "106.3327125"),
358: Station(id: 358, name: "Rangkasbitung", subName: "Rangkasbitung", latitude: "-6.3526711", longitude: "106.251502"),
374: Station(id: 374, name: "Bekasi Timur", subName: "Bekasitimur", latitude: "-6.246845", longitude: "107.0181248"),
376: Station(id: 376, name: "Cikarang", subName: "Cikarang", latitude: "-6.2553926", longitude: "107.1451293")
]
}
var langCode : String?
var apduRunner = ApduRunner()
public init() {
langCode = Locale.current.languageCode
parseData()
}
func setCallback(apduCallback : ApduCallback){
apduRunner.setApduCallback(callback: apduCallback)
apduRunner.setUnifiedNfcApi(nfcApi: self)
}
func setApduRunner(apRunner : ApduRunner){
self.apduRunner = apRunner
}
public func checkIfNfcSupported() -> Bool {
return NFCTagReaderSession.readingAvailable
}
public func searchCard(){
apduRunner.startScan()
}
public func checkCard(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRIZZI_INIT_APDU, completionHandler: {response in
if (response.sw1 == 0x91 && response.sw2 == 0x00){
debugLog("brizzi card")
let brizzi = BrizziApi()
brizzi.setApduRunner(apRunner: self.apduRunner)
brizzi.getUid()
} else {
self.checkNext01()
}
})
}
public func checkFelicaCard(tag: NFCFeliCaTag){
readFelicaCard(tag: tag)
}
func readFelicaCard(tag: NFCFeliCaTag){
let kmt = Emoney()
let serviceCode = Data([0x0B, 0x30])
let blockList = Data([0x80, 0x00])
tag.readWithoutEncryption(
serviceCodeList: [serviceCode],
blockList: [blockList]
) { (status1, status2, blockData, error) in
if let error = error {
self.apduRunner.nfcApi?.stopCheckCard(message: "Gagal membaca: \(error.localizedDescription)")
return
}
// Cek status keberhasilan dari kartu (0x00 0x00 berarti sukses)
guard status1 == 0x00 && status2 == 0x00 else {
self.apduRunner.nfcApi?.stopCheckCard(message: "Error Status: \(status1) \(status2)")
return
}
for (index, data) in blockData.enumerated() {
debugLog("Data Blok \(index): \(data.map { String(format: "%02X", $0) }.joined())")
if let cardNumberString = String(data: data, encoding: .utf8) {
kmt.setCardLabel("KMT")
kmt.setCardNumber(cardNumberString)
self.readFelicaBalance(tag: tag, kmt: kmt)
}
}
}
}
func readFelicaBalance(tag: NFCFeliCaTag, kmt: Emoney){
let serviceCode = Data([0x17, 0x10])
let blockList = Data([0x80, 0x00])
tag.readWithoutEncryption(
serviceCodeList: [serviceCode],
blockList: [blockList]
) { (status1, status2, blockData, error) in
if let error = error {
self.apduRunner.nfcApi?.stopCheckCard(message: "Gagal membaca: \(error.localizedDescription)")
return
}
if status1 == 0x00 && status2 == 0x00 {
let cardBalance = [UInt8](blockData[0])
var y: Int = 0
for x in 0..<4 {
y += Int(cardBalance[x]) << (x * 8)
}
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "id_ID")
formatter.numberStyle = .decimal
debugLog("balance")
kmt.setBalance(y)
if let balance = formatter.string(from: NSNumber(value: y)) {
debugLog("Saldo: \(balance)") // Hasil contoh: "67.305.985"
}
self.readFelicaCardHistory(tag: tag, kmt: kmt)
// kmt.setTampilRiwayat(false)
// self.apduRunner.callback?.complete(emoney: kmt)
// self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
// self.apduRunner.invalidateSession()
}
}
}
func readFelicaCardHistory(tag: NFCFeliCaTag, kmt: Emoney){
let serviceCode = Data([0x0F, 0x20])
// 2. Buat daftar 15 blok (Blok 0 sampai 14) secara otomatis
var blockList = [Data]()
for i in 0..<15 {
blockList.append(Data([0x80, UInt8(i)]))
}
// 3. Panggil fungsi pembacaan
tag.readWithoutEncryption(
serviceCodeList: [serviceCode],
blockList: blockList
) { (status1, status2, blockData, error) in
var riwayatList: [RiwayatCard] = []
if let error = error {
debugLog("Error: \(error.localizedDescription)")
return
}
if status1 == 0x00 && status2 == 0x00 {
debugLog("Berhasil membaca 15 blok!")
// blockData akan berisi array of Data, masing-masing 16 byte
for (index, data) in blockData.enumerated() {
let riwayat = RiwayatCard()
var normal = true
let subId = data.subdata(in: 8..<10)
let uid = self.convert(bytes: [UInt8](subId))
debugLog("station: \(uid)")
debugLog("Blok \(index): \(data.map { String(format: "%02X", $0) }.joined())")
if (uid == 0){
normal = false
}
if (data.count > 10){
let type = data[10]
debugLog(type)
switch type {
case 0x01:
riwayat.setProsesTipe(1)
riwayat.setTitle("payment".localizeString(string: self.langCode!))
debugLog("Pembayaran")
case 0x00, 0x03:
riwayat.setProsesTipe(0)
riwayat.setTitle("topup".localizeString(string: self.langCode!))
debugLog("Topup")
default:
riwayat.setProsesTipe(1)
riwayat.setTitle("payment".localizeString(string: self.langCode!))
debugLog("Other")
}
}
if let station = self.stationMap[uid]{
debugLog("station", station.name)
riwayat.setLocationName(station.name.uppercased(with: .autoupdatingCurrent))
}
// let station = self.stationMap[uid]
// print("station", station!)
// riwayat.setPlace(self.stationMap[uid]!.name)
if (normal){
let subData = data.subdata(in: 0..<4)
let date = self.getDate(data: [UInt8](subData))
riwayat.setTransactionTime(date!)
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "id_ID") // Format Indonesia
formatter.dateFormat = "dd MMMM yyyy, HH:mm"
let dateString = formatter.string(from: date!)
debugLog("Hasil Konversi: \(dateString)")
let amn = data.subdata(in: 4..<8)
let amount = amn.withUnsafeBytes { $0.load(as: Int32.self).bigEndian }
//print("Amount: \(amount)")
// if (data.count > 10){
// let type = data[10]
// print(type)
// switch type {
// case 0x01:
// print("Pembayaran")
// case 0x00:
// print("Topup")
// default:
// print("Other")
// }
//
// let subId = data.subdata(in: 8..<10)
//
// let uid = self.convert(bytes: [UInt8](subId))
// print("station: \(uid)")
// }
riwayat.setAmount(Int(amount))
let nformatter = NumberFormatter()
nformatter.locale = Locale(identifier: "id_ID")
nformatter.numberStyle = .decimal
if let balance = nformatter.string(from: NSNumber(value: amount)) {
debugLog("amount: \(balance)") // Hasil contoh: "67.305.985"
}
debugLog("")
} else {
debugLog("RESKA PARKIR")
let stringData = data.map { String(format: "%02X", $0) }.joined()
let inputFormatter = DateFormatter()
inputFormatter.dateFormat = "ddMMyyyyHHmmssSS"
let finalData = stringData.prefix(16)
// 2. Konversi String ke objek Date
if let date = inputFormatter.date(from: String(finalData)) {
// 3. Inisialisasi Formatter untuk mengubah ke format tujuan
let outputFormatter = DateFormatter()
outputFormatter.dateFormat = "dd MMM yyyy HH:mm"
let result = outputFormatter.string(from: date)
riwayat.setTransactionTime(date)
debugLog(result) // Hasil: 29-01-2026 16:00:44
} else {
debugLog("Format string tidak cocok")
}
let amn = data.subdata(in: 8..<12)
let amount = amn.withUnsafeBytes { $0.load(as: Int32.self).bigEndian }
riwayat.setAmount(Int(amount))
//print("Amount: \(amount)")
let nformatter = NumberFormatter()
nformatter.locale = Locale(identifier: "id_ID")
nformatter.numberStyle = .decimal
if let balance = nformatter.string(from: NSNumber(value: amount)) {
debugLog("amount: \(balance)") // Hasil contoh: "67.305.985"
}
}
debugLog("")
riwayatList.append(riwayat)
}
kmt.setRiwayatList(riwayatList)
kmt.setTampilRiwayat(true)
self.apduRunner.callback?.complete(emoney: kmt)
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
self.apduRunner.invalidateSession()
} else {
debugLog("Gagal. Status: \(status1), \(status2)")
}
}
}
func getDate(data: [UInt8]) -> Date? {
// 1. Tentukan TimeZone Jakarta
let timeZone = TimeZone(identifier: "Asia/Jakarta")!
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = timeZone
// 2. Set tanggal dasar (1 Januari 2000, 07:00:00)
var components = DateComponents()
components.year = 2000
components.month = 1
components.day = 1
components.hour = 7
components.minute = 0
components.second = 0
guard let baseDate = calendar.date(from: components) else { return nil }
// 3. Ambil selisih detik dari data byte
let secondsToAdd = convert(bytes: data)
// 4. Tambahkan detik ke baseDate
let finalDate = calendar.date(byAdding: .second, value: secondsToAdd, to: baseDate)
return finalDate
}
func convert(bytes: [UInt8]) -> Int {
switch bytes.count {
case 0:
fatalError("Data kosong")
case 1:
return Int(bytes[0])
case 2:
// Big-endian: (byte[0] << 8) | byte[1]
return (Int(bytes[0]) << 8) | Int(bytes[1])
case 3:
// (byte[0] << 16) | (byte[1] << 8) | byte[2]
return (Int(bytes[0]) << 16) | (Int(bytes[1]) << 8) | Int(bytes[2])
default:
// Padanan ByteBuffer.wrap(bArr).getInt() (Big-endian)
return Int(bytes.withUnsafeBytes { $0.load(as: Int32.self).bigEndian })
}
}
public func stopCheckCard(message : String){
apduRunner.sessionEx?.invalidate(errorMessage: message)
}
private func checkNext01(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.FLAZZ_INIT_APDU, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog("flazz card")
let flazz = BcaFlazzApi()
flazz.setApduRunner(apRunner: self.apduRunner)
flazz.checkFlazzCard()
} else {
self.checkNext02()
}
})
}
private func checkNext02(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.TAPCASH_INIT_APDU, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog("tapcash card")
let tapCash = TapCashApi()
tapCash.setApduRunner(apRunner: self.apduRunner)
tapCash.checkBalance()
} else {
self.checkNext03()
}
})
}
private func checkNext03(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.EMONEY_INIT_APDU, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog("emoney card")
let emoney = MandiriEmoneyApi()
emoney.setApduRunner(apRunner: self.apduRunner)
emoney.getCardNumber()
} else {
self.checkNext04()
}
})
}
private func checkNext04(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.JACKCARD_INIT_APDU, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog("jack card")
let jack = JackCardApi()
jack.setApduRunner(apRunner: self.apduRunner)
jack.getBalance(resp: response.getData())
} else {
self.checkNext05()
}
})
}
private func checkNext05(){
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MEGA_APDU01, completionHandler: {response in
if (response.sw1 == 0x90 && response.sw2 == 0x00){
debugLog("megacash")
let mega = MegaCashApi()
mega.setApduRunner(apRunner: self.apduRunner)
mega.getBalance(resp: response.getData())
} else {
self.apduRunner.invalidateSession(msg: "Card not supported")
}
})
}
}

View File

@ -0,0 +1,16 @@
//
// ApduCallback.swift
// Emoney Info
//
// Created by Wira Irawan on 24/07/24.
//
import Foundation
import CoreNFC
protocol ApduCallback {
func connected(unifiedNfcApi : UnifiedNfcApi)
func felicaConnected(unifiedNfcApi : UnifiedNfcApi, tag : NFCFeliCaTag)
func complete(emoney: Emoney)
func failed(error: NSError)
}

View File

@ -0,0 +1,38 @@
//
// ApduResponse.swift
// Emoney Info
//
// Created by Wira Irawan on 24/07/24.
//
import Foundation
class ApduResponse {
var data : Data?
var sw1 : UInt8?
var sw2 : UInt8?
func setData(_data : Data){
self.data = _data
}
func setSw1(_sw1 : UInt8){
self.sw1 = _sw1
}
func setSw2(_sw2 : UInt8){
self.sw2 = _sw2
}
func getSw1() -> UInt8{
return sw1!
}
func getSw2() -> UInt8{
return sw2!
}
func getData() -> Data{
return data!
}
}

View File

@ -0,0 +1,126 @@
import Foundation
import CoreNFC
@available(iOS 13.0, *)
extension NFCISO7816APDU {
func toHexString() -> String {
let dataFieldInHex = (self.data ?? Data(_ : [])).hexEncodedString()
return String(format:"%02X %02X %02X %02X", self.instructionClass, self.instructionCode, self.p1Parameter, self.p2Parameter) + " " + dataFieldInHex + String(format:"%02X", self.expectedResponseLength) + " "
}
}
typealias CompletionHandler = (_ response:ApduResponse) -> Void
@available(iOS 13.0, *)
public class ApduRunner: NSObject, NFCTagReaderSessionDelegate {
public static let NFC_TAG_CONNECTED_EVENT:String = "nfcTagConnected"
var sessionEx: NFCTagReaderSession?
var callback: ApduCallback?
var nfcApi : UnifiedNfcApi?
func setApduCallback(callback : ApduCallback) {
self.callback = callback
}
func setUnifiedNfcApi(nfcApi : UnifiedNfcApi) {
self.nfcApi = nfcApi
}
func startScan() {
let langCode = Locale.current.languageCode
self.sessionEx = NFCTagReaderSession(pollingOption: [.iso14443, .iso18092], delegate: self)
self.sessionEx?.alertMessage = "scanMessage".localizeString(string: langCode!)
self.sessionEx?.begin()
}
public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
guard self.callback != nil else {
debugLog("NfcCallback is empty.")
return
}
guard self.sessionEx != nil else {
return
}
guard tags.count > 0 else {
debugLog("Nfc Tag???.")
return
}
if case NFCTag.iso7816(_) = tags.first! {
sessionEx?.connect(to: tags.first!) { [self] (error: Error?) in
if let err = error {
debugLog("Error connecting to Nfc Tag" + err.localizedDescription)
return
}
debugLog("Nfc Tag is connected.")
if (self.callback != nil){
self.callback!.connected(unifiedNfcApi: self.nfcApi!)
}
}
} else if case .feliCa(let feliCaTag) = tags.first! {
sessionEx?.connect(to: tags.first!) { [self] (error: Error?) in
if let err = error {
debugLog("Error connecting to Nfc Tag" + err.localizedDescription)
return
}
// felicaTag = feliCaTag
debugLog("Felica is connected.")
if (self.callback != nil){
debugLog("Felica is connected 2.")
self.callback!.felicaConnected(unifiedNfcApi: self.nfcApi!, tag: feliCaTag)
}
// self.sendFelicaCommand(tag: feliCaTag, session: sessionEx!)
// let idm = feliCaTag.currentIDm.map { String(format: "%.2hhx", $0) }.joined()
// let hexString = "0F06" + idm + "010B30018000"
// print("hex: \(hexString)")
// if let apduData = hexString.hexToData() {
// feliCaTag.sendFeliCaCommand(commandPacket: apduData)
// }
// if (self.callback != nil){
// self.callback!.connected(felicaNfcApi: self.felicaNfcApi!)
// }
}
}
}
func exchangeApdu(apduCommand: NFCISO7816APDU, completionHandler: @escaping CompletionHandler) {
if case let NFCTag.iso7816(nfcTag) = self.sessionEx!.connectedTag! {
nfcTag.sendCommand(apdu: apduCommand) { (response: Data, sw1: UInt8, sw2: UInt8, error: Error?)
in
let resp = ApduResponse()
debugLog("SW1-SW2: " + String(format: "%02X, %02X", sw1, sw2))
resp.setSw1(_sw1: sw1)
resp.setSw2(_sw2: sw2)
resp.setData(_data: response)
completionHandler(resp)
}
}
}
func invalidateSession() {
sessionEx?.invalidate()
}
func invalidateSession(msg : String) {
sessionEx?.invalidate(errorMessage: msg)
}
public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
debugLog("Nfc session is active")
}
public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
debugLog("Error happend: " + error.localizedDescription)
NotificationCenter.default.post(name: Notification.Name("stopTimer"), object: nil)
if ((sessionEx?.isReady) != nil){
self.invalidateSession(msg: error.localizedDescription)
}
}
}

View File

@ -0,0 +1,66 @@
//
// Emoney.swift
// Emoney Info
//
// Created by Wira Irawan on 26/07/24.
//
import Foundation
class Emoney {
private var balance: Int = 0
private var cardNumber: String?
private var cardType: String?
private var riwayatList: [RiwayatCard]?
private var tampilRiwayat: Bool = false
private var cardLabel: String?
func getBalance() -> Int {
return self.balance
}
func getCardNumber() -> String {
return self.cardNumber!
}
func getCardType() -> String {
return self.cardType!
}
func getRiwayatList() -> [RiwayatCard] {
return self.riwayatList!
}
func isTampilRiwayat() -> Bool {
return self.tampilRiwayat
}
func setBalance(_ j: Int) {
self.balance = j
}
func setCardNumber(_ str: String) {
self.cardNumber = str
}
func setCardType(_ str: String) {
self.cardType = str
}
func setRiwayatList(_ list: [RiwayatCard]) {
self.riwayatList = list
}
func setTampilRiwayat(_ z: Bool) {
self.tampilRiwayat = z
}
func setCardLabel(_ str: String) {
self.cardLabel = str
}
func getCardLabel() -> String {
return self.cardLabel!
}
}

View File

@ -0,0 +1,124 @@
//
// RiwayatCard.swift
// Emoney Info
//
// Created by Wira Irawan on 25/07/24.
//
import Foundation
class RiwayatCard
//: Comparable
{
// static func < (lhs: RiwayatCard, rhs: RiwayatCard) -> Bool {
// return lhs.valueToCompare > rhs.valueToCompare
// }
//
// static func == (lhs: RiwayatCard, rhs: RiwayatCard) -> Bool {
// return false
// }
private var amount: Int = 0
private var desk: String?
private var jam: String?
private var locationId: String?
private var locationName: String?
private var prosesTipe: Int = 0
private var tanggal: String?
private var title: String?
private var transactionTime : Date?
private var ads : Bool = false
// private var valueToCompare: Date
// init(valueToCompare: Date) {
// self.valueToCompare = valueToCompare
// }
func getAmount() -> Int {
return self.amount
}
func setAmount(_ amount: Int) {
self.amount = amount
}
func getDesk() -> String? {
return self.desk
}
func getJam() -> String? {
return self.jam
}
func setJam(_ str: String) {
self.jam = str
}
func getLocationId() -> String? {
return self.locationId
}
func setLocationId(_ str: String) {
self.locationId = str
}
func getLocationName() -> String? {
return self.locationName
}
func setLocationName(_ str: String) {
self.locationName = str
}
func getProsesTipe() -> Int {
return self.prosesTipe
}
func setProsesTipe(_ proc: Int) {
self.prosesTipe = proc
}
func getTanggal() -> String? {
return self.tanggal
}
func setTanggal(_ str: String) {
self.tanggal = str
}
func getTitle() -> String? {
return self.title
}
func setTitle(_ str: String) {
self.title = str
}
func setTransactionTime(_ date : Date){
self.transactionTime = date
}
func getTransationTime() -> Date?{
return self.transactionTime
}
func setAds(_ ads : Bool){
self.ads = ads
}
func isAds() -> Bool{
return self.ads
}
// func setValueToCompare(_ valueToCompare: Date) {
// self.valueToCompare = valueToCompare
// }
//
// func getValueToCompare() -> Date {
// return self.valueToCompare
// }
}

View File

@ -0,0 +1,109 @@
//
// BrizziSamHelper.swift
// Emoney Info
//
// Created by Wira Irawan on 27/07/24.
//
import Foundation
import CommonCrypto
class BrizziSamHelper {
public var encryptedKey: String?
public var authKey = "0000030080000000"
public var keyCard: String?
public var random = ""
static func encryptDeSeDe(_ str: String, _ str2: String, _ str3: String) -> Data? {
var key = str2
if key.count != 48 {
if key.count == 32 {
key += key.prefix(16)
} else if key.count == 16 {
key += key + key
} else {
key = "00000000000000000000000000000000"
}
}
let keyData = key.hex2byte()
let ivData = str3.hex2byte()
return crypt(input: str.hex2byte(), keyData: keyData, ivData: ivData, operation: CCOperation(kCCEncrypt))
}
static func decryptDeSeDe(_ datas: Data) -> Data? {
let keyData = ("C152153D5807784C721A433B5B59636D" + "C152153D5807784C").hex2byte()
let ivData = ("0000000000000000").hex2byte()
return crypt(input: datas, keyData: keyData, ivData: ivData, operation: CCOperation(kCCDecrypt))
}
static func mix(_ bArr: [UInt8], _ bArr2: [UInt8]) -> [UInt8] {
guard !bArr2.isEmpty else {
fatalError("empty security key")
}
var bArr3 = [UInt8](repeating: 0, count: bArr.count)
var i = 0
for y in 0..<bArr.count {
bArr3[y] = bArr[y] ^ bArr2[i]
i += 1
if i >= bArr2.count {
i = 0
}
}
return bArr3
}
static func decrypt(_ data: String, _ key: String) -> Data? {
let keyData = key.hex2byte()
return crypt(input: data.hex2byte(), keyData: keyData, ivData: nil, operation: CCOperation(kCCDecrypt))
}
static func encrypt(_ str: String, _ key: String) -> Data? {
let substring = String(key.prefix(16))
guard let decryptedData = decrypt(str, substring) else {
return ("").hex2byte()
}
let a9 = decryptedData.hexEncodedString()
let keyData = String(key.dropFirst(16).prefix(16)).hex2byte()
guard let encryptedData = crypt(input: a9.hex2byte(), keyData: keyData, ivData: nil, operation: CCOperation(kCCEncrypt)) else {
return ("").hex2byte()
}
guard let finalDecryptedData = decrypt(encryptedData.hexEncodedString(), substring) else {
return ("").hex2byte()
}
return finalDecryptedData
}
func generateSamRandom() -> String {
let sam = BrizziSamHelper.mix(((BrizziSamHelper.encrypt(self.keyCard!, self.random)!).hexEncodedString()).hex2byte().bytes, ("0000000000000000").hex2byte().bytes).hexString().subString(from: 0, to: 16)
let sams = sam[sam.index(sam.startIndex, offsetBy: 2)..<sam.index(sam.startIndex, offsetBy: 16)] + sam[sam.startIndex..<sam.index(sam.startIndex, offsetBy: 2)]
let result = (BrizziSamHelper.encrypt((BrizziSamHelper.mix(("1122334455667788").hex2byte().bytes, (self.keyCard!).hex2byte().bytes).hexString()), self.random)!).hexEncodedString().subString(from: 0, to: 16)
return result + (BrizziSamHelper.encrypt((BrizziSamHelper.mix(String(sams).hex2byte().bytes, result.hex2byte().bytes)).hexString(), self.random)!).hexEncodedString()
}
private static func crypt(input: Data, keyData: Data, ivData: Data?, operation: CCOperation) -> Data? {
var outLength = Int(0)
var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSize3DES)
var status: CCCryptorStatus
if let ivData = ivData {
status = CCCrypt(operation, CCAlgorithm(kCCAlgorithm3DES), CCOptions(kCCOptionPKCS7Padding), keyData.bytes, kCCKeySize3DES, ivData.bytes, input.bytes, input.count, &outBytes, outBytes.count, &outLength)
} else {
status = CCCrypt(operation, CCAlgorithm(kCCAlgorithmDES), CCOptions(kCCOptionPKCS7Padding), keyData.bytes, kCCKeySizeDES, nil, input.bytes, input.count, &outBytes, outBytes.count, &outLength)
}
guard status == kCCSuccess else {
return nil
}
return Data(bytes: outBytes, count: outLength)
}
}

View File

@ -0,0 +1,41 @@
import Foundation
public class ByteArrayAndHexHelper {
public static func digitalStrIntoAsciiUInt8Array(digitalStr : String) -> [UInt8]{
var bytes = [UInt8]()
for s in digitalStr {
if let byte = UInt8(String(s)) {
bytes.append(0x30 + byte)
}
}
return bytes
}
public static func hexStrToUInt8Array(hexStr: String) -> [UInt8] {
var startIndex = hexStr.startIndex
return (0..<hexStr.count/2).compactMap { _ in
let endIndex = hexStr.index(after: startIndex)
defer { startIndex = hexStr.index(after: endIndex) }
return UInt8(hexStr[startIndex...endIndex], radix: 16)
}
}
public static func hex(from string: String) -> Data {
.init(stride(from: 0, to: string.count, by: 2).map {
string[string.index(string.startIndex, offsetBy: $0) ... string.index(string.startIndex, offsetBy: $0 + 1)]
}.map {
UInt8($0, radix: 16)!
})
}
public static func makeShort(src: [UInt8], srcOff : Int) -> Int {
// if (srcOff < 0 || src.length < (srcOff + 2))
// throw new IllegalArgumentException("Bad args!");
let b0 = Int(src[srcOff] & 0xFF);
let b1 = Int(src[srcOff + 1] & 0xFF);
return (b0 << 8) + b1
}
}