.
1352 |
1353 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | +---------------------------------------------------------------+
3 | | EPT emulator |
4 | | Concert Protocole, aka "Protocole Caisse" in french. |
5 | | © 2016 Libre Informatique [http://www.libre-informatique.fr/] |
6 | +---------------------------------------------------------------+
7 |
8 | Emulated EPT: iCT250, iWL220
9 | ```
10 |
11 | Introduction
12 | =============
13 |
14 | The EPTs (Electronic Payment Terminals) provided for some trade marks like Ingenico must be tested in some kind of real conditions, and no development environments are available (like fake bank cards, fake bank accounts, etc.). So if you want to interconnect your software with their hardware, you'll need a real account, real bank cards, etc.
15 |
16 | This project aims to replace a real EPT, real bank cards, etc. substituting the physical USB or Serial connection with a Websocket one. Things may be a bit different but this emulator will save you dozens of real transactions with your own credit card. We just hope that you'll be able to work with WebSockets easily in your own software.
17 |
18 | Note that this project is provided "AS-IS" and if your software works out with it, this is not a proof that it will work with real EPTs...
19 |
20 | First of all
21 | =============
22 |
23 | Using NPM:
24 |
25 | ```
26 | $ npm install ept-emulator
27 | ```
28 |
29 | Using Git, if you did that...
30 |
31 | ```
32 | $ git clone https://github.com/libre-informatique/EPTEmulator .
33 | $ npm update
34 | ```
35 |
36 | The server side tests
37 | ======================
38 |
39 | Low-layer
40 | ----------
41 |
42 | Launch the physical-server.js script in the terminal:
43 |
44 | ```
45 | $ node physical-server.js
46 | ```
47 |
48 | Middle-layer
49 | -------------
50 |
51 | Launch the logical-server.js script in the terminal
52 |
53 | ```
54 | $ node logical-server.js
55 | ```
56 |
57 | Within the logical server, you will be able to answer "0" (success) or "1" (error) at the ```stat``` prompt, to emulate a failed or a successfull payment.
58 |
59 | The client side tests
60 | ======================
61 |
62 | Web tests
63 | ----------
64 |
65 | You can do all your tests and Proof of Concepts using the URL http://localhost:8001/.
66 |
67 | You also can do lots of low level experiments usign the URL http://localhost:8001/physical-tests.html, but this is not advised.
68 |
69 | Low-layer
70 | ----------
71 |
72 | Launch the physical-client.js script in the terminal:
73 |
74 | ```
75 | $ node physical-client.js
76 | ```
77 |
78 | Middle-layer
79 | -------------
80 |
81 | Launch the logical-client.js script in the terminal
82 |
83 | ```
84 | $ node logical-client.js
85 | ```
86 |
87 | Application-layer
88 | ------------------
89 |
90 | Launch the application-client.console.js script in the terminal if you want to be "interactive" with the server.
91 |
92 | ```
93 | $ node application-client.console.js
94 | ```
95 |
96 | Launch the application-client.event.js script in the terminal if you want to see asynchronous automated exchanges with the server.
97 |
98 | ```
99 | $ node application-client.console.js
100 | ```
101 |
102 | Do not hesitate to edit and modify the ```application-client.*.js``` files to fit your own environments & tests
103 |
104 | Integrating those libraries into your own web-app
105 | ==================================================
106 |
107 | Refer to this the [index.html](index.html) webpage for your own integration.
108 | Note that our libraries are compatible with nodejs & browsers for the client side, and with nodejs only for the server side.
109 |
--------------------------------------------------------------------------------
/application-client.console.js:
--------------------------------------------------------------------------------
1 | var application = require('./application-layer.class.js')
2 | .createApplicationLayer(true, 'B8', 978, 1, 0, 0);
3 | application.createClient('ws://localhost:8001/');
4 |
5 | console.log('Application client created');
6 |
--------------------------------------------------------------------------------
/application-client.event.js:
--------------------------------------------------------------------------------
1 | var application = require('./application-layer.class.js')
2 | .createApplicationLayer(false, 'B8', 978, 1, 0, 0);
3 | application.createClient('ws://localhost:8001/');
4 |
5 | console.log('Application client created');
6 |
7 | setTimeout(function(){
8 | application.prepareTransaction(2690, 789789);
9 | },1500);
10 |
--------------------------------------------------------------------------------
/application-layer.class.js:
--------------------------------------------------------------------------------
1 | var nodejs = false;
2 | try {
3 | if ( window !== this )
4 | nodejs = true;
5 | } catch (e) {
6 | nodejs = true;
7 | }
8 |
9 | var ApplicationLayer = function(interactive, pos, currency, mod, type, ind) {
10 | var application = this;
11 | this.interactive = interactive;
12 | this.pos = pos !== undefined ? pos+'' : pos;
13 | this.currency = currency !== undefined ? currency+'' : currency;
14 | this.mode = mod !== undefined ? mod+'' : mod;
15 | this.type = type !== undefined ? type+'' : type;
16 | this.ind = ind !== undefined ? ind+'' : ind;
17 |
18 | this.logical = nodejs ? require('./logical-layer.class.js').createLogicalLayer() : new LogicalLayer();
19 |
20 | this.createServer = function(){
21 | return this.logical.createServer();
22 | }
23 | this.createClient = function(url){
24 | return this.logical.createClient(url);
25 | }
26 |
27 | this.prepareTransaction = function(amount, private){
28 | preanswers = {
29 | pos: this.pos,
30 | currency: this.currency,
31 | mode: this.mode,
32 | type: this.type,
33 | ind: this.ind,
34 | amount: amount !== undefined ? amount+'' : amount,
35 | private: private !== undefined ? private+'' : private,
36 | }
37 |
38 | questions = [];
39 | for ( var question in this.logical.messageStructure[this.logical.ept] )
40 | if ( preanswers[question] === undefined )
41 | questions.push(question);
42 |
43 | this.logical.physical.prepareTransmission(questions, this.logical.messageStructure[this.logical.ept], preanswers);
44 | }
45 |
46 | application.logical.physical
47 | .clear([
48 | 'clientConnection'
49 | ])
50 |
51 | .on('clientConnection', function(conn){
52 | console.log("Application connected");
53 | if ( application.interactive )
54 | application.prepareTransaction();
55 | })
56 |
57 | .on('stxReceived', function(message){
58 | var result = application.logical.processResponse(message);
59 |
60 | application.logical.physical._call(result.stat == '0' ? 'applicationSuccess' : 'applicationFailure', result);
61 |
62 | if ( application.interactive )
63 | application.prepareTransaction();
64 | })
65 |
66 | .on('applicationSuccess', function(result){
67 | console.info('---- application -----------');
68 | console.info(' Success :)');
69 | console.info(' Result:', result);
70 | console.info('----------------------------');
71 |
72 | })
73 | .on('applicationFailure', function(result){
74 | console.info('---- application -----------');
75 | console.info(' !! Failed !!');
76 | console.info(' Result:', result);
77 | console.info('----------------------------');
78 |
79 | })
80 | ;
81 | }
82 |
83 | if ( nodejs )
84 | exports.createApplicationLayer = function(interactive, pos, currency, mod, type, ind){
85 | return new ApplicationLayer(interactive, pos, currency, mod, type, ind);
86 | }
87 |
--------------------------------------------------------------------------------
/concert.class.js:
--------------------------------------------------------------------------------
1 | // A trick to be able
2 | var nodejs = false;
3 | try {
4 | if ( window !== this )
5 | nodejs = true;
6 | } catch (e) {
7 | nodejs = true;
8 | }
9 |
10 | if ( nodejs )
11 | exports.getLogicalLayer = function(){ return require('./logical-layer.class.js').getLogicalLayer(); }
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Libre Informatique - Ingenico EPT Emulator - Tester
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
56 |
60 |
61 |
62 | Ingenico EPT Emulator
63 |
64 | +---------------------------------------------------------------+
65 | | EPT emulator |
66 | | Concert Protocole, aka "Protocole Caisse" in french. |
67 | | © 2016 Libre Informatique [http://www.libre-informatique.fr/] |
68 | +---------------------------------------------------------------+
69 |
70 | Emulated EPT: iCT250, iWL220
71 |
72 | Launch `node logical-server.js` to get this webpage all its potential
73 |
74 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/logical-client.js:
--------------------------------------------------------------------------------
1 | var physical = require('./logical-layer.class.js')
2 | .createLogicalLayer()
3 | .createClient('ws://localhost:8001/');
4 |
5 | console.log('Logical client created');
6 |
--------------------------------------------------------------------------------
/logical-layer.class.js:
--------------------------------------------------------------------------------
1 | var nodejs = false;
2 | try {
3 | if ( window !== this )
4 | nodejs = true;
5 | } catch (e) {
6 | nodejs = true;
7 | }
8 |
9 | var LogicalLayer = function() {
10 | var logical = this;
11 | this.ept = null;
12 | this.expectingRep = false;
13 | this.version = 'E';
14 | this.priv = '';
15 | this.rep = true;
16 |
17 | var messageStructure = this.messageStructure = {
18 | true: {
19 | pos: 2,
20 | stat: 1,
21 | amount: 8,
22 | mode: 1,
23 | rep: { pad: ' ', length: 55 },
24 | currency: 3,
25 | private: { pad: ' ', length: 10 }
26 | },
27 | false: {
28 | pos: 2,
29 | amount: 8,
30 | ind: 1,
31 | mode: 1,
32 | type: 1,
33 | currency: 3,
34 | private: { pad: ' ', length: 10 },
35 | }
36 | };
37 |
38 | this.createServer = function(handler){
39 | this.ept = true; // true is for the EPT
40 |
41 | return physical.createServer(handler);
42 | }
43 | this.createClient = function(url){
44 | this.ept = false; // false is for the POS
45 |
46 | if ( this.version == 'E+' )
47 | {
48 | messageStructure.pos.delai = 4; // 0xa01-
49 | messageStructure.pos.auto = 4; // 0xb01-
50 | }
51 |
52 | return physical.createClient(url);
53 | }
54 |
55 | this.processResponse = function(message){
56 | var result = fromRawToStructuredData(message, messageStructure[!logical.ept]);
57 | // play with the options of the answer
58 | logical.rep = result.ind == 1;
59 |
60 | return result;
61 | }
62 |
63 | var physical = this.physical = nodejs ? require('./physical-layer.class.js').getPhysicalLayer() : PhysicalLayer;
64 | physical
65 | .clear([
66 | 'dataReceived',
67 | 'changingMode',
68 | 'beforeAction',
69 | 'changingSenderState',
70 | 'changingReceiverState',
71 | 'receivingRawData',
72 | 'sendingRawData',
73 | 'sendingData',
74 | 'sendingNothing',
75 | 'stxReceiving',
76 | 'stxBeforeSending',
77 | 'serverConnection',
78 | 'clientConnection',
79 | 'reset'
80 | ])
81 |
82 | .on('serverConnection', function(conn){
83 | console.log("New connection");
84 | })
85 |
86 | .on('clientConnection', function(conn){
87 | console.log("Connected");
88 |
89 | var questions = [];
90 | for ( var question in messageStructure[logical.ept] )
91 | questions.push(question);
92 | physical.prepareTransmission(questions, messageStructure[logical.ept]);
93 | })
94 |
95 | .on('stxReceived', function(message){
96 | var result = logical.processResponse(message);
97 |
98 | console.info('---- logical ---------------');
99 | for ( prop in result )
100 | console.info(' '.substring(prop.length)+prop.substring(0,10), result[prop]);
101 | console.info('----------------------------');
102 |
103 | var answers = {};
104 | if ( logical.ept && typeof(result) == 'object' )
105 | {
106 | var questions = ['pos', 'amount', 'mode', 'currency', 'private'];
107 | for ( var i = 0 ; i < questions.length ; i++ )
108 | if ( result[questions[i]] !== undefined )
109 | answers[questions[i]] = result[questions[i]];
110 | }
111 |
112 | // for the answer
113 | var questions = [];
114 | for ( var question in messageStructure[logical.ept] )
115 | if (!( question == 'priv' && result.priv === undefined ) && !( question == 'rep' && !this.rep ))
116 | if ( answers[question] === undefined )
117 | questions.push(question);
118 |
119 | physical._call('stxProcessed', { questions: questions, structure: messageStructure[logical.ept], answers: answers });
120 | })
121 |
122 | // Message control function
123 | .on('stxProcessed', function(data){
124 | physical.prepareTransmission(data.questions, data.structure, data.answers);
125 | })
126 |
127 | // Message control function
128 | .on('stxReceivedLengthControl', function(message){
129 | switch ( logical.ept ){
130 | case false: // POS
131 | if ( message.length != 25 // E or E+
132 | && message.length != 80 // E or E+ with REP
133 | )
134 | return false;
135 |
136 | logical.rep = message.length == 80;
137 | break;
138 | case true: // EPT
139 | if ( message.length != 26 // E
140 | && message.length != 34 ) // E+
141 | return false;
142 | break;
143 | }
144 |
145 | // if we finish here, we are ok
146 | return true;
147 | })
148 |
149 | .on('stxReceivingError', function(err){
150 | console.error('stxReceivingError /!\\', err);
151 | })
152 |
153 | .on('reset', function(mode){
154 | console.log('Resetting things...');
155 | if ( mode == 'sender' )
156 | return;
157 |
158 | // for the answer
159 | var questions = [];
160 | for ( var question in messageStructure[logical.ept] )
161 | if (!( question == 'priv' && result.priv === undefined ))
162 | questions.push(question);
163 | physical.prepareTransmission(questions, messageStructure[logical.ept]);
164 | })
165 |
166 | .on('stxSent', function(message){
167 | var result = fromRawToStructuredData(message, messageStructure[logical.ept]);
168 |
169 | if ( logical.ept == 'pos' && result.ind == '1' ) // needs a REP field at the end in the response
170 | logical.expectingRep = true;
171 |
172 | console.info('---- logical ---------------');
173 | for ( prop in result )
174 | console.info(' '.substring(prop.length)+prop.substring(0,10), result[prop]);
175 | console.info('----------------------------');
176 | })
177 |
178 | .on('stxSendingError', function(message){
179 | console.error('stxSendingError /!\\', message);
180 | })
181 | ;
182 |
183 | var fromRawToStructuredData = function(raw, structure){
184 | var i = 0;
185 | result = {};
186 | for ( var prop in structure )
187 | if (!( prop == 'rep' && !this.rep ))
188 | {
189 | var length = typeof(structure[prop]) == 'object'
190 | ? structure[prop].length
191 | : structure[prop];
192 |
193 | result[prop] = raw.slice(i,i+length);
194 | i += length;
195 | }
196 | return result;
197 | }
198 |
199 | }
200 |
201 | if ( nodejs )
202 | exports.createLogicalLayer = function(){ return new LogicalLayer(); }
203 |
--------------------------------------------------------------------------------
/logical-server.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | var physical = require('./logical-layer.class.js')
4 | .createLogicalLayer()
5 | .createServer(function(req, res){
6 | console.error(req, res);
7 | fs.readFile(__dirname + '/index.html',
8 | function (err, data) {
9 | if (err) {
10 | res.writeHead(500);
11 | return res.end('Error loading index.html');
12 | }
13 |
14 | res.writeHead(200);
15 | res.end(data);
16 | });
17 | })
18 | .listen('8001');
19 |
20 | console.log('');
21 | console.log('+---------------------------------------------------------------+');
22 | console.log('| EPT emulator |');
23 | console.log('| Concert Protocole, aka "Protocole Caisse" in french. |');
24 | console.log('| © 2016 Libre Informatique [http://www.libre-informatique.fr/] |');
25 | console.log('+---------------------------------------------------------------+');
26 | console.log('');
27 | console.log('Emulated EPT: iCT250, iWL220');
28 | console.log('');
29 | console.log('Visit http://localhost:8001/ to test the EPT emulator using a web browser');
30 | console.log('Launch `node application-client.console.js` to test the EPT emulator using the command line');
31 | console.log('');
32 | console.log('');
33 | console.log('Logical server created');
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ept-emulator",
3 | "version": "1.0.2",
4 | "description": "Electronic Payment Terminal emulator (using websockets and the 'Concert Protocol' aka 'Protocole Caisse', from Ingenico)",
5 | "main": "logical-layer.class.js",
6 | "dependencies": {
7 | "express": "^4.13.4",
8 | "socket.io": "^1.4.5",
9 | "socket.io-client": "^1.4.5",
10 | "prompt": "^1.0.0"
11 | },
12 | "devDependencies": {},
13 | "scripts": {
14 | "test": "echo \"Error: no test specified\" && exit 1"
15 | },
16 | "keywords": [
17 | "EPT",
18 | "Emulator"
19 | ],
20 | "author": {
21 | "name": "Libre Informatique",
22 | "email": "contact@libre-informatique.fr"
23 | },
24 | "license": "GPL-3.0",
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/libre-informatique/EPTEmulator.git"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/libre-informatique/EPTEmulator/issues"
31 | },
32 | "homepage": "https://github.com/libre-informatique/EPTEmulator#readme",
33 | "gitHead": "19e354547abde1d24d6f78a657396b99ed3ce383",
34 | "_id": "ept-emulator@1.0.2",
35 | "_shasum": "adf6b237289d8052963a26cd6fbe7dc7e5db307c",
36 | "_from": "ept-emulator@latest",
37 | "_npmVersion": "2.14.12",
38 | "_nodeVersion": "4.3.1",
39 | "_npmUser": {
40 | "name": "betaglop",
41 | "email": "beta@e-glop.net"
42 | },
43 | "dist": {
44 | "shasum": "adf6b237289d8052963a26cd6fbe7dc7e5db307c",
45 | "tarball": "http://registry.npmjs.org/ept-emulator/-/ept-emulator-1.0.2.tgz"
46 | },
47 | "maintainers": [
48 | {
49 | "name": "betaglop",
50 | "email": "beta@e-glop.net"
51 | },
52 | {
53 | "name": "marcoslibre",
54 | "email": "marcos.bezerra@libre-informatique.fr"
55 | }
56 | ],
57 | "_npmOperationalInternal": {
58 | "host": "packages-13-west.internal.npmjs.com",
59 | "tmp": "tmp/ept-emulator-1.0.2.tgz_1456872283488_0.5876089916564524"
60 | },
61 | "directories": {},
62 | "_resolved": "https://registry.npmjs.org/ept-emulator/-/ept-emulator-1.0.2.tgz",
63 | "readme": "ERROR: No README data found!"
64 | }
65 |
--------------------------------------------------------------------------------
/physical-client.js:
--------------------------------------------------------------------------------
1 | var physical = require('./physical-layer.class.js').getPhysicalLayer();
2 |
3 | physical.createClient('ws://localhost:8001/', 'physical');
4 |
5 | console.log('Physical client created');
6 |
--------------------------------------------------------------------------------
/physical-layer.class.js:
--------------------------------------------------------------------------------
1 | var nodejs = false;
2 | try {
3 | if ( window !== this )
4 | nodejs = true;
5 | } catch (e) {
6 | nodejs = true;
7 | }
8 |
9 | if ( nodejs )
10 | {
11 | var http = require("http");
12 | var ProtocolHelpers = require('./protocol-helpers.class.js').getProtocolHelpers();
13 | var Workflows = require('./workflows.class.js').getWorkflows();
14 | }
15 |
16 | /**
17 | * This PhysicalLayer object can be extended for "stx"
18 | * processing using PhysicalLayer.on([event], [callback]);
19 | *
20 | * The argument given to the callback function is "mixed" data...
21 | *
22 | **/
23 |
24 | var PhysicalLayer = {
25 | prompt: nodejs ? require('prompt').start() : Prompt,
26 | on: function(event, callback){
27 | if ( PhysicalLayer._events[event] === undefined )
28 | PhysicalLayer._events[event] = [];
29 | if ( typeof(callback) == 'function' )
30 | PhysicalLayer._events[event].push(callback);
31 | return PhysicalLayer;
32 | },
33 | _events: {
34 | creation: [function(){
35 | }],
36 | serverConnection: [function(socket){
37 | console.log("New connection");
38 | PhysicalLayer.prepareTransmission();
39 | }],
40 | connectError: [function(error){
41 | console.error('CONNECTION ERROR to the ept-emulator', error);
42 | }],
43 | clientConnection: [function(socket){
44 | console.log("Connected");
45 | PhysicalLayer.prepareTransmission();
46 | }],
47 | dataReceived: [function(data){
48 | console.log('Got:', ProtocolHelpers.Physical.decode(data[0]), data);
49 | }],
50 | changingMode: [function(mode){
51 | console.log('Going into ', mode, 'mode');
52 | }],
53 | beforeAction: [function(infos){
54 | console.log('The current state before anything is', infos.state, infos.mode);
55 | }],
56 | close: [function(infos){
57 | // infos.code
58 | console.log("Connection closed:", infos.reason);
59 | }],
60 | changingSenderState: [function(infos){
61 | console.log('Sending state after', infos[0], 'is', infos[1]);
62 | }],
63 | changingReceiverState: [function(infos){
64 | console.log('Receiving state after', infos[0], 'is', infos[1]);
65 | }],
66 | changingState: [function(infos){
67 | }],
68 | receivingRawData: [function(data){
69 | console.log('Received:', data, '(raw)');
70 | }],
71 | sendingRawData: [function(data){
72 | console.log('Sent:', data, '(raw)');
73 | }],
74 | sendingData: [function(physical){
75 | console.log('Sent:', physical);
76 | }],
77 | sendingNothing: [function(what){
78 | console.log('Internal routing:', what);
79 | }],
80 | stxReceiving: [function(message){
81 | console.error('Receiving a message...', message);
82 | }],
83 | stxReceived: [function(message){
84 | console.info('');
85 | console.info('---- physical --------------');
86 | console.info(' ',message);
87 | console.info('----------------------------');
88 | }],
89 | stxReceivingError: [function(){
90 | }],
91 | stxSent: [function(message){
92 | console.info('');
93 | console.info('---- physical --------------');
94 | console.info(' "'+message+'"');
95 | console.info('----------------------------');
96 | }],
97 | stxBeforeSending: [function(infos){
98 | console.log('Sending:', infos.state, infos.message, infos.frame);
99 | }],
100 | stxSendingError: [function(message){
101 | }],
102 | reset: [function(){
103 | PhysicalLayer.prepareTransmission();
104 | }],
105 | stxReceivedLengthControl: [function(){
106 | return true;
107 | }],
108 | send: [function(data){
109 | PhysicalLayer.socket.emit('serial', data);
110 | }]
111 | },
112 |
113 | createServer: function(handler){
114 | var Express = require('express');
115 | var express = Express();
116 | var server = http.createServer(express);
117 | express.use('/', Express.static(__dirname + '/'));
118 | var io = require('socket.io')(server);
119 | io.on('connection', function(socket){
120 | PhysicalLayer.socket = socket;
121 | socket.on('disconnect', PhysicalLayer.events.close);
122 | socket.on('serial', PhysicalLayer.events.binary);
123 | PhysicalLayer._call('serverConnection', socket);
124 | });
125 | return server;
126 | },
127 |
128 | createClient: function(url){
129 | PhysicalLayer.socket = nodejs ? require('socket.io-client')(url) : io(url);
130 | PhysicalLayer.socket.on('connect', function(){
131 | PhysicalLayer._call('clientConnection', PhysicalLayer.socket);
132 | });
133 | PhysicalLayer.socket.on('connect_error', function(error){
134 | PhysicalLayer._call('connectError', error);
135 | });
136 | PhysicalLayer.socket.on('disconnect', PhysicalLayer.events.close);
137 | PhysicalLayer.socket.on('serial', PhysicalLayer.events.binary);
138 | return PhysicalLayer.socket;
139 | },
140 |
141 | clear: function(events){
142 | if ( events === undefined )
143 | events = [];
144 | if ( typeof(events) != 'object' )
145 | return PhysicalLayer;
146 | if ( events.length === undefined )
147 | events = [events];
148 |
149 | // clear everything
150 | if ( events.length == 0 || events.length == 1 && events[0] === null )
151 | {
152 | PhysicalLayer._events = {};
153 | return PhysicalLayer;
154 | }
155 |
156 | // clear some events
157 | for ( var i = 0 ; i < events.length ; i++ )
158 | if ( PhysicalLayer._events[events[i]] !== undefined )
159 | PhysicalLayer._events[events[i]] = [];
160 | return PhysicalLayer;
161 | },
162 | _call: function(event, mixed){
163 | var r = true;
164 | if ( PhysicalLayer._events[event] !== undefined )
165 | for ( var i = 0 ; i < PhysicalLayer._events[event].length ; i++ )
166 | r = PhysicalLayer._events[event][i](mixed) && r;
167 | return r;
168 | }
169 | }
170 |
171 | PhysicalLayer.events = {
172 | binary: function(data) {
173 | PhysicalLayer._call('receivingRawData', data);
174 | readStream(data, function(bin, msg){
175 | PhysicalLayer._call('dataReceived', [bin, msg]);
176 |
177 | // a strange exception
178 | if ( ProtocolHelpers.Physical.decode(bin) === undefined
179 | && ProtocolHelpers.Physical.decode(bin[0]) !== undefined )
180 | bin = bin[0];
181 |
182 | // errors
183 | if ( ProtocolHelpers.Physical.decode(bin) == undefined
184 | || mode && Workflows[mode][state[mode]][ProtocolHelpers.Physical.decode(bin)] == undefined )
185 | {
186 | sendBackError(bin);
187 | return;
188 | }
189 |
190 | // choosing the current mode if needed
191 | if ( !mode )
192 | {
193 | if ( Workflows.receiver[state.receiver][ProtocolHelpers.Physical.decode(bin)] != undefined )
194 | mode = 'receiver';
195 | if ( Workflows.sender[state.sender][ProtocolHelpers.Physical.decode(bin)] != undefined )
196 | mode = 'sender';
197 | PhysicalLayer._call('changingMode', mode);
198 | if ( !mode )
199 | {
200 | sendBackError(bin);
201 | return;
202 | }
203 | }
204 |
205 | PhysicalLayer._call('beforeAction', [state[mode], mode]);
206 |
207 | switch ( mode ){
208 | case 'sender':
209 | receiveInSenderMode(bin, msg);
210 | break;
211 | default:
212 | receiveInReceiverMode(bin, msg);
213 | break;
214 | }
215 | });
216 | },
217 |
218 | close: function (code, reason) {
219 | PhysicalLayer._call('close', [code, reason]);
220 | }
221 | }
222 |
223 | // internal stuff
224 |
225 | var state = { receiver: 'waiting-transfert', sender: 'waiting-start' };
226 | var mode = null;
227 |
228 |
229 | // receiver
230 | var receiveInReceiverMode = function(bin, msg){
231 | if ( ProtocolHelpers.Physical.decode(bin) === undefined )
232 | {
233 | // unknown input
234 | send('nak');
235 | return;
236 | }
237 |
238 | send(Workflows.receiver[state.receiver][ProtocolHelpers.Physical.decode(bin)]);
239 |
240 | // everything but a message
241 | if ( ProtocolHelpers.Physical.decode(bin) != 'stx' )
242 | return;
243 |
244 | PhysicalLayer._call('stxReceiving', msg);
245 |
246 | // receiving a message...
247 | if ( msg === undefined || msg === null ) // ERROR
248 | {
249 | PhysicalLayer._call('stxReceivingError', 'no message'); // hook
250 | send(Workflows.receiver[state.receiver]['nak']);
251 | return;
252 | }
253 |
254 | if ( !PhysicalLayer._call('stxReceivedLengthControl', msg) )
255 | {
256 | PhysicalLayer._call('stxReceivingError', 'message length is incorrect ('+msg.length+')'); // hook
257 | send(Workflows.receiver[state.receiver]['nak']);
258 | return;
259 | }
260 |
261 | PhysicalLayer._call('stxReceived', msg); // hook
262 | send(Workflows.receiver[state.receiver]['ack']);
263 | }
264 |
265 | // sender
266 | var message = null;
267 | PhysicalLayer.prepareTransmission = function(questions, lengthCriterias, preanswers){
268 | if ( questions === undefined )
269 | questions = 'message';
270 |
271 | // answers
272 | PhysicalLayer.prompt.get(questions, function(err, result){
273 | if ( err )
274 | {
275 | console.error('Error reading stdin', err);
276 | return;
277 | }
278 |
279 | for ( question in preanswers )
280 | if ( preanswers[question] !== undefined )
281 | result[question] = preanswers[question];
282 |
283 | if ( result.message )
284 | message = result.message;
285 | else
286 | {
287 | message = '';
288 | if ( lengthCriterias !== undefined )
289 | {
290 | for ( var question in lengthCriterias )
291 | if ( result[question] !== undefined )
292 | {
293 | var pad = '';
294 | var length = typeof(lengthCriterias[question]) == 'object' ? lengthCriterias[question].length : lengthCriterias[question];
295 | var padc = typeof(lengthCriterias[question]) == 'object' ? lengthCriterias[question].pad : '0';
296 | for ( var i = 0 ; i < length ; i++ )
297 | pad += padc;
298 | message += pad.substring(result[question].length);
299 | message += result[question].substring(0,length);
300 | }
301 | }
302 | else
303 | for ( var question in questions )
304 | if ( result[question] !== undefined )
305 | message += result[question];
306 | }
307 |
308 | var loops = 0;
309 | mode = 'sender';
310 | send(Workflows.sender[state.sender][null]); // opens a new transfert
311 | });
312 | }
313 |
314 | var receiveInSenderMode = function(bin, msg){
315 | // sender
316 | if ( Workflows.sender[state.sender][ProtocolHelpers.Physical.decode(bin)] === undefined )
317 | {
318 | resetMode();
319 | send('nak');
320 | return;
321 | }
322 |
323 | if ( Workflows.sender[state.sender][ProtocolHelpers.Physical.decode(bin)] == 'nak' )
324 | {
325 | if ( Workflows.sender[state.sender][ProtocolHelpers.Physical.decode(bin)] === undefined )
326 | {
327 | resetMode();
328 | return;
329 | }
330 |
331 | if ( state.sender == 'waiting-reception' )
332 | PhysicalLayer._call('stxSendingError', message); // hook
333 | changeState.sender(Workflows.sender[state.sender][ProtocolHelpers.Physical.decode(bin)][2]);
334 | return;
335 | }
336 |
337 | send(Workflows.sender[state.sender][ProtocolHelpers.Physical.decode(bin)]);
338 | }
339 |
340 | // generic
341 | var sendBackError = function(bin){ send('nak'); }
342 |
343 | var changeState = {
344 | sender: function(newState){
345 | PhysicalLayer._call('changingSenderState', [state.sender, newState]);
346 | state.sender = newState;
347 | if ( Workflows.sender[state.sender][1] )
348 | send(ProtocolHelpers.Physical.encode(Workflows.sender[state.sender][1]));
349 | },
350 | receiver: function(newState){
351 | PhysicalLayer._call('changingReceiverState', [state.sender, newState]);
352 | state.receiver = newState;
353 | if ( Workflows.receiver[state.receiver][null] !== undefined )
354 | {
355 | send(Workflows.receiver[state.receiver][null]);
356 | if ( state.receiver != 'waiting-start' )
357 | readResponse();
358 | }
359 | }
360 | }
361 |
362 | var send = PhysicalLayer.send = function(action, rawState){
363 | // the data, raw
364 | if ( typeof(action) != 'object' )
365 | {
366 | if ( action !== null )
367 | {
368 | PhysicalLayer._call('send', ProtocolHelpers.Physical.encode(action));
369 | PhysicalLayer._call('sendingRawData', action);
370 | }
371 | if ( rawState !== undefined )
372 | changeState[mode](rawState);
373 | if ( action == 'eot' )
374 | resetMode();
375 | return;
376 | }
377 |
378 | var nextMode = mode;
379 | if ( action[1] )
380 | switch ( action[1] ) {
381 | case 'stx':
382 | var tmp = ProtocolHelpers.Physical.getFrame(message);
383 | PhysicalLayer._call('stxBeforeSending', { state: action[0], message: message, frame: tmp }); // hook
384 | PhysicalLayer._call('send', tmp); // hook
385 | PhysicalLayer._call('stxSent', message); // hook
386 | break;
387 | case 'eot':
388 | nextMode = null;
389 | default:
390 | // the data, forged
391 | PhysicalLayer._call('send', ProtocolHelpers.Physical.encode(action[1])); // hook
392 | PhysicalLayer._call('sendingData', action[0]);
393 | break;
394 | }
395 | else
396 | PhysicalLayer._call('sendingNothing', action[0]);
397 |
398 | // the state
399 | changeState[mode](action[2]);
400 | nextMode == null ? resetMode() : mode = nextMode;
401 | }
402 |
403 | var resetMode = function(){
404 | PhysicalLayer._call('reset', mode);
405 | mode = null;
406 | }
407 |
408 | // low level
409 | var readStream = function(data, callback){
410 | // for the single char communication
411 | if ( data.length == 1 )
412 | {
413 | callback(data);
414 | return;
415 | }
416 |
417 | var msg = null;
418 | try {
419 | msg = ProtocolHelpers.Physical.getMessage(data);
420 | } catch (e) {
421 | console.error('Error when uncrypting the STX frame:', e);
422 | }
423 | callback(data[0], msg);
424 | }
425 |
426 | if ( nodejs )
427 | exports.getPhysicalLayer = function(){ return PhysicalLayer; }
428 |
--------------------------------------------------------------------------------
/physical-server.js:
--------------------------------------------------------------------------------
1 | var physical = require('./physical-layer.class.js').getPhysicalLayer();
2 |
3 | physical
4 | .createServer()
5 | .listen(8001);
6 |
7 | console.log('Physical server created');
8 |
--------------------------------------------------------------------------------
/physical-tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Testing the physical layer of EPT from Ingenico
6 |
7 |
57 |
60 |
61 |
62 |
63 |
64 |
65 |
New session
66 |
74 |
75 |
76 |
Ack
77 |
85 |
86 |
87 |
Nak
88 |
96 |
97 |
98 |
End session
99 |
107 |
108 |
109 |
New message
110 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/protocol-helpers.class.js:
--------------------------------------------------------------------------------
1 | var nodejs = false;
2 | try {
3 | if ( window !== this )
4 | nodejs = true;
5 | } catch (e) {
6 | nodejs = true;
7 | }
8 |
9 | var ProtocolHelpers = {}
10 |
11 | ProtocolHelpers.Physical = {
12 | messages: {
13 | 0x05: 'enq', // session request
14 | 0x06: 'ack', // session agreement
15 | 0x15: 'nak', // session refusal
16 | 0x02: 'stx', // message start
17 | 0x03: 'etx', // message end
18 | 0x04: 'eot', // session end
19 |
20 | enq: 0x05, // session request
21 | ack: 0x06, // acknoledgement
22 | nak: 0x15, // non-acknoledgement
23 | stx: 0x02, // message start
24 | etx: 0x03, // message end
25 | eot: 0x04, // session end
26 | },
27 |
28 | getSessionReq: function(){ return ProtocolHelpers.Physical.encode('enq'); },
29 | getSessionEnd: function(){ return ProtocolHelpers.Physical.encode('eot'); },
30 | getSessionAck: function(){ return ProtocolHelpers.Physical.encode('ack'); },
31 | getNack: function(){ return ProtocolHelpers.Physical.encode('nak'); },
32 | decode: function(hexx){ return ProtocolHelpers.Physical.messages[hexx.charCodeAt(0)]; },
33 | encode: function(id) { return String.fromCharCode(ProtocolHelpers.Physical.messages[id]); },
34 |
35 | getFrame: function(msg){
36 | // init
37 | var packet = ProtocolHelpers.Physical.encode('stx');
38 | var check = [];
39 | var data = [];
40 |
41 | // the message itself
42 | check = msg;
43 | check += ProtocolHelpers.Physical.encode('etx');
44 | packet += check;
45 |
46 | // the checksum
47 | packet += ProtocolHelpers.Physical.getLrc(check);
48 |
49 | return packet;
50 | },
51 |
52 | getMessage: function(frame){
53 | if ( frame[0] != ProtocolHelpers.Physical.encode('stx') )
54 | {
55 | console.error('The frame is not preceded by the expected code: ',frame[0], frame);
56 | throw ProtocolHelpers.Physical.encode('nak');
57 | }
58 |
59 | if ( frame[frame.length-1] != ProtocolHelpers.Physical.getLrc(frame.slice(1,frame.length-1)) )
60 | {
61 | console.error('The frame has a bad checksum');
62 | throw ProtocolHelpers.Physical.encode('nak');
63 | }
64 |
65 | if ( frame[frame.length-2] != ProtocolHelpers.Physical.encode('etx') )
66 | {
67 | console.error('The message is not followed with the expected code');
68 | throw ProtocolHelpers.Physical.encode('nak');
69 | }
70 |
71 | return frame.slice(1, frame.length-2);
72 | },
73 |
74 | getLrc: function(str){
75 | var sum = 0x0;
76 | for ( var i = 0 ; i < str.length ; i++ )
77 | sum = sum ^ str.charCodeAt(i);
78 | return String.fromCharCode(sum);
79 | },
80 |
81 | hextoa: function(hexx){
82 | var hex = hexx.toString();//force conversion
83 | var str = '';
84 | for (var i = 0; i < hex.length; i += 2)
85 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
86 | return str;
87 | }
88 | }
89 |
90 | ProtocolHelpers.Logical = {};
91 |
92 | if ( nodejs )
93 | exports.getProtocolHelpers = function(){ return ProtocolHelpers; }
94 |
--------------------------------------------------------------------------------
/web-prompt.class.js:
--------------------------------------------------------------------------------
1 | var Prompt = {
2 | get: function(questions, handler){
3 | handler(null, {});
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/workflows.class.js:
--------------------------------------------------------------------------------
1 | var nodejs = false;
2 | try {
3 | if ( window !== this )
4 | nodejs = true;
5 | } catch (e) {
6 | nodejs = true;
7 | }
8 |
9 | var Workflows = {}
10 | Workflows.receiver = {
11 | 'waiting-transfert': {
12 | enq: ['Transfert accepted', 'ack', 'waiting-message'],
13 | nak: ['Transfert refused', 'nak', 'waiting-transfert']
14 | },
15 | 'waiting-message': {
16 | stx: ['Message recieved', null, 'validating-message'],
17 | eot: ['Transfert closed', null, 'waiting-transfert']
18 | },
19 | 'validating-message': {
20 | ack: ['Message accepted', 'ack', 'stopping-transfert'],
21 | nak: ['Message refused', 'nak', 'waiting-message']
22 | },
23 | 'stopping-transfert': {
24 | eot: ['Transfert terminated', null, 'waiting-transfert']
25 | }
26 | }
27 | Workflows.sender = {
28 | 'waiting-start': {
29 | null: ['Opening Transfert', 'enq', 'waiting-transfert']
30 | },
31 | 'waiting-transfert': {
32 | ack: ['Sending message', 'stx', 'waiting-reception'],
33 | nak: ['Transfert refused', 'eot', 'waiting-start']
34 | },
35 | 'waiting-reception': {
36 | ack: ['Message accepted, terminating', 'eot', 'waiting-start'],
37 | nak: ['Message refused, terminating', 'eot', 'waiting-start']
38 | }
39 | }
40 |
41 | if ( nodejs )
42 | {
43 | exports.getWorkflows = function(){ return Workflows; }
44 | }
45 |
--------------------------------------------------------------------------------