I am trying to create a driver to work with an Ingenico POS. I've to send packet to this device via UDP. I used for a scale (works too with UDP protocol) this library CocoaAsyncSocket and it works perfectly, but now with this device I'm getting some issue. To work with this device I've to do the follow:
Sometimes I've a strange behavior: the packet will sent twice and generate issues for the device. To understand that issue I used WireShark and I got this log:
as you can see in this picture the packet 1832 and the packet 1833 are the same indeed the device answer me twice (see packet 1834 and packet 1835). For the ping packet it's not a big problem, but for the payment auth packet it's a big problem, indeed if I sent twice the payment auth packet the device answer me with an error. I'm not understanding why sometimes I've this wired behavior and sometimes it works perfectly, I'm sure that my code sent the packet only once. Anyone has an idea how to fix this? Thank you
EDIT WITH ASKED CODE
Code to create the socket:
private func setupSocket() {
outSocket = GCDAsyncUdpSocket.init(delegate: self, delegateQueue: DispatchQueue.main)
outSocket.setIPv4Enabled(true)
outSocket.setIPv6Enabled(false)
do {
try outSocket.connect(toHost:ip ?? "", onPort: port ?? 0)
} catch {
outSocket.close()
}
do {
try outSocket.beginReceiving()
} catch {
outSocket.close()
}
}
When the socket is connected in the delegate I do the follow:
public func udpSocket(_ sock: GCDAsyncUdpSocket, didConnectToAddress address: Data) {
outSocket.send(self.generatePacket(), withTimeout: 10, tag: 0)
}
The function to generate packet is the follow:
private func generatePacket() -> Data {
var data = Data()
switch actualOperation {
case .FIRST_PACKET:
data.append(self.generateHeader())
break
case .SECOND_PACKET:
data.append(self.generateHeader())
data.append(self.generateBody())
break
default:
break
}
print("REQUEST PKT: \(data.hexEncodedString(options: .upperCase))")
return data
}
private func generateHeader() -> Data {
var data = Data()
switch actualOperation {
case .FIRST_PACKET:
data.append(TransportProtocol.pingFromPosToClient, count: TransportProtocol.pingFromPosToClient.count)
break
case .SECOND_PACKET:
data.append(TransportProtocol.messageFromPosToClient, count: TransportProtocol.messageFromPosToClient.count)
break
default:
break
}
data.append(Constant.nodeId, count: Constant.nodeId.count)
data.append(Constant.portId, count: Constant.portId.count)
data.append(Constant.groupId, count: Constant.groupId.count)
data.append(self.generateTransmissionId())
return data
}
private func generateTransmissionId() -> Data {
var data = Data()
if let transId = String(format: "%05ld", transmissionID).data(using: .utf8) {
data.append(transId)
transmissionID += 1
if (transmissionID > 99999) {
transmissionID = 1
}
UserDefaults.standard.set(transmissionID, forKey: "TransmissionID")
}
return data
}
private func generateBodyForPayment() -> Data {
var data = Data()
data.append(self.getPacketLength())
data.append(CommandsPosToClient.authRequest, count: 1)
data.append(OperationTypePosToClient.opPayment, count: 1)
switch currency {
case .DANISH_KRONE:
data.append(CurrencyCode.danishKrone, count: 1)
break
case .EURO:
data.append(CurrencyCode.euro, count: 1)
break
case .SWISS_FRANC:
data.append(CurrencyCode.swissFranc, count: 1)
break
case .CZECH_KORUNA:
data.append(CurrencyCode.czechKoruna, count: 1)
break
case .UK_POUND:
data.append(CurrencyCode.ukPound, count: 1)
break
case .US_DOLLAR:
data.append(CurrencyCode.usDollar, count: 1)
break
case .KUWAITI_DINAR:
data.append(CurrencyCode.kuwaitiDinar, count: 1)
break
case .POLISH_ZLOTY:
data.append(CurrencyCode.polishZloty, count: 1)
break
case .HUNGARIAN_FLORINT:
data.append(CurrencyCode.hungarianFlorint, count: 1)
break
default:
data.append(CurrencyCode.euro, count: 1)
break
}
let amount = Int(toPay * 100)
if let amountToPay = String(format: "%09ld", amount).data(using: .utf8) {
data.append(amountToPay)
}
data.append(Constant.manTrans, count: Constant.manTrans.count)
//ID cassiere 8 byte
if let idEmp = "\(idEmployee ?? 0)".paddingToLeft(upTo: 8).data(using: .utf8) {
data.append(idEmp)
}
//Numero scontrino 8 byte
if let billNmb = "\(billNumber ?? 0)".paddingToLeft(upTo: 8).data(using: .utf8) {
data.append(billNmb)
}
data.append(Constant.plugIn, count: Constant.plugIn.count)
data.append(Constant.voidField, count: Constant.voidField.count)
if let amountToPay = String(format: "%09ld", amount).data(using: .utf8) {
data.append(amountToPay)
}
data.append(Constant.voidField, count: Constant.voidField.count)
data.append(Constant.voidField, count: Constant.voidField.count)
data.append(Constant.voidField, count: Constant.voidField.count)
//TODO: Extra data
data.append(Constant.voidField, count: Constant.voidField.count)
data.append(Constant.voidField, count: Constant.voidField.count)
if let crcData = self.crc16(data.bytes) {
data.append(crcData)
}
return data
}
private func getPacketLength() -> Data {
var data = Data()
var packetLength = 2
packetLength += 1 //Lunghezza del comando --> sempre 1 byte
packetLength += 1 //Lunghezza dell'operazione --> sempre 1 byte
switch actualOperation {
case .SECOND_PACKET:
packetLength += 1 //Lunghezza della valuta
packetLength += 9 //Lunghezza dell'importo
packetLength += 1 //Lunghezza della tipologia della transazione
packetLength += 8 //Lunghezza del campo dedicato all'ID del cassiere
packetLength += 8 //Lunghezza del campo dedicato al numero dello scontrino
packetLength += 1 //Lunghezza di plug-in
packetLength += 1 //Lunghezza di RFU
packetLength += 9 //Lunghezza del totale
packetLength += 1 //Lunghezza di Track 1
packetLength += 1 //Lunghezza di Track 2
packetLength += 1 //Lunghezza di Track 3
packetLength += 1 //Lunghezza di Extra data length
packetLength += 1 //Lunghezza di Extra data
break
default:
break
}
var value = UInt16(littleEndian: UInt16(packetLength))
var array = withUnsafeBytes(of: &value) { Array($0) }
array.reverse()
for var byte in array {
data.append(Data(bytes: &byte, count: 1))
}
return data
}
private func crc16(_ data: [UInt8]) -> Data? {
guard !data.isEmpty else {
print("data is empty")
return nil
}
let polynomial: UInt16 = 0xA001
var accumulator: UInt16 = 0
for byte in data {
var tempByte = UInt16(byte)
for _ in 0 ..< 8 {
let temp1 = accumulator & 0x0001
accumulator = accumulator >> 1
let temp2 = tempByte & 0x0001
tempByte = tempByte >> 1
if (temp1 ^ temp2) == 1 {
accumulator = accumulator ^ polynomial
}
}
}
var data = Data()
var value = UInt16(bigEndian: accumulator)
var array = withUnsafeBytes(of: &value) { Array($0) }
array.reverse()
for var byte in array {
data.append(Data(bytes: &byte, count: 1))
}
return data
}
And this is the code where the answer is analyzed:
public func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
print("OPERATION: \(actualOperation) ANSWER: \(data.hexEncodedString(options: .upperCase))")
switch actualOperation {
case .SEND_PING:
actualOperation = .SEND_PAYMENT_AUTH
sock.send(self.generatePacket(), withTimeout: 10, tag: 0)
break
case .ABORT_PAYMENT:
sock.close()
break
default:
if (data.count > 21) {
//Elimino la prima parte del pacchetto che non mi interessa ed ottengo il pacchetto contenente i dati
let packetData = data.subdata(in: 23..<data.count)
switch packetData.subdata(in: 0..<1).bytes {
case ClientToPos.statusNotification:
self.delegate?.posDeviceInPayment(actualStatus: String(decoding: packetData.subdata(in: 2..<packetData.count - 2), as: UTF8.self), model: .DEVICE)
break
case ClientToPos.receiptDataPayment:
let billDataLength = UInt16(bigEndian: packetData.subdata(in: 87..<89).withUnsafeBytes { $0.load(as: UInt16.self) })
billData = String(decoding: packetData.subdata(in: 90..<90 + Int(billDataLength)), as: UTF8.self)
break
case ClientToPos.transactionDone:
if (errorCode != "" || errorDescription != "") {
self.delegate?.posDeviceOperationFailed(errorCode: errorCode, errorDescription: errorDescription, model: .DEVICE)
} else {
self.delegate?.posDeviceOperationCompleted(extraInfo: billData, model: .DEVICE)
}
sock.close()
break
case ClientToPos.panChecking:
self.delegate?.posDevicePanCheckingRequired(model: .DEVICE)
break
case ClientToPos.extendedError:
errorCode = String(decoding: packetData.subdata(in: 1..<5), as: UTF8.self)
errorDescription = String(decoding: packetData.subdata(in: 5..<packetData.count - 4), as: UTF8.self)
break
case ClientToPos.error:
errorCode = String(decoding: packetData.subdata(in: 1..<5), as: UTF8.self)
errorDescription = String(decoding: packetData.subdata(in: 5..<packetData.count - 4), as: UTF8.self)
break
default:
break
}
}
break
}
}
I hope it helps
For everyone is interested, this issue is caused by the network. At home I've a Vodafone network, with their router (Vodafone Station Revolution). The Vodafone Station Revolution generate Wi-Fi @ 2,4GHz and Wi-Fi @ 5GHz. When I try to send an UDP packet on this network this packet will posted twice one for the 2,4GHz and one for the 5GHz network. So to solve my issue I disabled the 5GHz network and all work fine. It is not a code problem, indeed it's a network problem. I hope my experience will be useful for other person who need the send UDP packet on Vodafone network. By the way thank you for help.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.