Created
November 27, 2019 10:06
-
-
Save standa/1dea88b6048a5b7dd56bf5d10d3eec1a to your computer and use it in GitHub Desktop.
Ingenico POS Payment Terminal Test Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php declare(strict_types=1); | |
namespace Maternia\Utils\Terminal; | |
error_reporting(E_ALL); | |
ini_set('display_errors', '1'); | |
ob_implicit_flush(); | |
const HOST = '192.168.0.176'; | |
const PORT = 1818; | |
const STX = "\x02"; | |
const ETX = "\x03"; | |
const EOT = "\x04"; | |
const ENQ = "\x05"; | |
const ACK = "\x06"; | |
const NAK = "\x15"; | |
const DLE = "\x10"; | |
const WACK = "\x13"; | |
const FS = "\x1C"; // Field separator | |
const GS = "\x1D"; // | |
const MESSAGE_SPEC = [ | |
'STX' => 'C1', // start of transmission | |
'num' => 'C1', // CAC / SAC | |
'messagetype' => 'C2', // TYPE | |
'flag' => 'C4', // reserved for future use | |
'mc' => 'C4', // Message counter is a unique message identifier. | |
// The response must have the same message number as the corresponding request. | |
// message data header | |
'datatype' => 'C1', // 0 - request from source cash desk, 5 - response from POS terminal | |
'src' => 'C2', // ID of source device | |
'dest' => 'C2', // ID of the destination device | |
'ver' => 'C1', // protocol version 1 - basic | |
'tid' => 'C2', // transaction ID | |
'data' => 'C*', // message data | |
// 'ETX' => 'C1', // End of transmission | |
// 'CRC' => 'C2', // CRC-16 of the transmission | |
]; | |
function pack_spec(): string | |
{ | |
return implode('', array_keys(MESSAGE_SPEC)); | |
} | |
function unpack_spec(): string | |
{ | |
return implode('/', array_map( | |
function ($k, $v) { return "$v$k"; }, | |
array_keys(MESSAGE_SPEC), | |
array_values(MESSAGE_SPEC) | |
)); | |
} | |
$payload = [ | |
STX, "\x00", "\x30", "\x31", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x39", "\x30", "\x30", | |
"\x31", "\x31", "\x32", "\x30", | |
ETX, // ETX | |
"\xcb", "\xfd", // CRC-16 | |
]; | |
function read_message(string $m): array | |
{ | |
// return unpack(unpack_spec(), $m); | |
// $data = unpack('C1STX/C1num/n1messagetype/N1flag/N1mc/C1datatype/n1src/n1dest/C1ver/n1tid/C*data', substr($m, 0, -3)); | |
// return array_merge($data, unpack('C1ETX/C1CRC1/C1CRC2', substr($m, -3))); | |
return unpack(unpack_spec(), $m); | |
} | |
// @see https://www.php.net/manual/en/function.crc32.php#28012 | |
function crc16(string $string): int { | |
$crc = 0xFFFF; | |
for ($x = 0, $xMax = strlen($string); $x < $xMax; $x++) { | |
$crc ^= ord($string[$x]); | |
for ($y = 0; $y < 8; $y++) { | |
if (($crc & 0x0001) == 0x0001) { | |
$crc = (($crc >> 1) ^ 0xA001); | |
} else { | |
$crc >>= 1; | |
} | |
} | |
} | |
return $crc; | |
} | |
// debug bytes as hex | |
function _dhex(string $bytes): void | |
{ | |
echo '0x[ ' . implode(' ', array_map(function (int $v): string { return sprintf('%02s', dechex($v)); }, unpack("C*", $bytes))) . ' ]' . PHP_EOL; | |
} | |
/** | |
* Basic terminal test script. Successful output should look like this: | |
* | |
* Status Information Request for status IDLE ('0') | |
* | |
starting | |
socket_create(): OK. | |
Connecting to '192.168.0.176' via port '1818'...socket_connect(): OK. | |
socket_read(ENQ): 0x[ 05 ] | |
socket_read(ENQ): ENQ OK | |
socket_write(status_info_request): sending request: 23 bytes | |
0x[ 00 00 00 01 00 00 00 00 00 00 00 00 00 09 00 00 01 01 02 00 00 00 00 ] | |
socket_write(status_info_request): 23 bytes written | |
socket_read(status_info_request): reading response:0x[ 06 00 ] | |
socket_read(status_info_request): ACK OK | |
socket_read(status_info_request): Read 24 bytes: | |
0x[ 02 00 30 32 30 30 30 30 30 30 30 30 35 30 31 39 30 31 32 30 30 03 8f 7f ] | |
socket_read(status_info_request): Response validation OK | |
socket_read(status_info_request): Full response match OK | |
socket_write(status_info_request): Send ACK | |
socket_read(status_info_request): Read 1 bytes: 0x[ 04 ] | |
socket_read(status_info_request): EOT OK | |
*/ | |
echo 'starting'.PHP_EOL; | |
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); | |
if ($socket === false) { | |
echo "socket_create() error : " . socket_strerror(socket_last_error()) . "\n"; | |
} else { | |
echo "socket_create(): OK.\n"; | |
} | |
printf("Connecting to '%s' via port '%s'...", HOST, PORT); | |
$result = socket_connect($socket, HOST, PORT); | |
if ($socket === false) { | |
echo "socket_connect() error: ($result) " . socket_strerror(socket_last_error($socket)) . "\n"; | |
} else { | |
echo "socket_connect(): OK.\n"; | |
} | |
echo 'socket_read(ENQ): '; | |
$out = socket_read($socket, 1); | |
_dhex($out); | |
if ($out !== ENQ) { | |
echo 'socket_read(ENQ): ENQ ERROR; expected ENQ 0x05' . PHP_EOL; | |
exit; | |
} else { | |
echo 'socket_read(ENQ): ENQ OK' . PHP_EOL; | |
} | |
echo "socket_write(status_info_request): sending request: " . count($payload) . ' bytes' . PHP_EOL; | |
_dhex(pack('C*', ...$payload)); | |
$cnt = socket_write($socket, implode('', $payload)); | |
echo "socket_write(status_info_request): $cnt bytes written" . PHP_EOL; | |
if ($cnt !== count($payload)) { | |
echo "socket_write(status_info_request): ERROR: $cnt bytes written, expected: " . count($payload) . PHP_EOL; | |
} | |
$out = socket_read($socket, 2); | |
echo 'socket_read(status_info_request): reading response:'; | |
_dhex($out); | |
if ($out === ACK . $payload[1]) { | |
echo 'socket_read(status_info_request): ACK OK'. PHP_EOL; | |
} else { | |
echo 'socket_read(status_info_request): ACK ERROR. Received: ' . PHP_EOL; | |
_dhex($out); | |
exit; | |
} | |
$out = socket_read($socket, 2048); | |
echo 'socket_read(status_info_request): Read ' . strlen($out) . ' bytes:' . PHP_EOL; | |
_dhex($out); | |
if (substr($out, 0, 2) === STX . $payload[1] && substr($out, -3) === ETX . "\x8f\x7f") { | |
echo 'socket_read(status_info_request): Response validation OK' . PHP_EOL; | |
} else { | |
echo 'socket_read(status_info_request): Response validation ERROR: start and end + crc were: ' . PHP_EOL; | |
_dhex(substr($out, 0, 2)); | |
_dhex(substr($out, -3)); | |
exit; | |
} | |
if ($out !== "\x02\x00\x30\x32\x30\x30\x30\x30\x30\x30\x30\x30\x35\x30\x31\x39\x30\x31\x32\x30\x30\x03\x8f\x7f") { | |
echo 'socket_read(status_info_request): Response validation ERROR: Invalid response contents' . PHP_EOL; | |
exit; | |
} else { | |
echo 'socket_read(status_info_request): Full response match OK' . PHP_EOL; | |
} | |
echo "socket_write(status_info_request): Send ACK" . PHP_EOL; | |
socket_write($socket, ACK . $payload[1]); | |
$out = socket_read($socket, 1); | |
echo 'socket_read(status_info_request): Read ' . strlen($out) . ' bytes: '; | |
_dhex($out); | |
if ($out === "\x04") { | |
echo 'socket_read(status_info_request): EOT OK' . PHP_EOL; | |
} else { | |
echo 'socket_read(status_info_request): Terminal did not send EOT but:' . PHP_EOL; | |
_dhex($out); | |
} | |
socket_close($socket); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment