Non so quanti di voi si sono mai trovati di fronte a delle stampanti fiscali come la Fasy F1 e hanno avuto il problema di doverle interfacciare da un altro software.
Per un cliente che sto seguendo, ho dovuto riscrivere un vecchio programma sviluppato in Visual Basic 6, in qualcosa di maggiormente modulare e portabile.
Dato che il linguaggio nel quale mi sento maggiormente a mio agio è Java, ho deciso di utilizzarlo per riscrivere il codice.
Funzionamento
Al momento la soluzione tecnica non è delle migliori: il programma non attende un evento, ma continua ad interrogare un sito web per sapere se ci sono nuovi scontrini da stampare.
Questo tipo di approccio è dovuto ad una precedente implementazione che, per retrocompatibilità, ho scelto di non variare nella prima release, in modo che il cambio di software non avesse impatti lato server, ma solo lato client.
Ogni volta che è presente una vendita, viene preparato un nuovo scontrino ed inviato alla stampante fiscale.
In questo caso la stampa è fatta tramite una Fasy:
Ammetto che fino a poche settimane fa non avevo mai utilizzato questo prodotto, ma chiaramente non è stato un motivo per demotivarmi dal provare a programmarlo.
Il primo ostacolo che ho trovato è stata la totale assenza di interfacce ufficiali in Java: esempi e supporto, volendo usare il protocollo "custom", era fatti su Visual Basic 6, VB.NET, VC#, VC++ 6.0 e VC++.NET.
Fortunatamente tutti utilizzavano la libreria CeFdll.dll, le cui API erano indicate chiaramente in un manuale di poche ed essenziali pagine.
Viva la JNA
Quando da Java si vuole uscire dalla Virtual Machine ed utilizzare qualcosa di Nativo è necessario affidarsi a delle librerie realizzate per questo scopo. Quella maggiormente utilizzata al momento, dato che non necessita programmazione in altri linguaggi, se non in Java è JNA.
JNA è una libreria sviluppata per superare l'approccio alle estensioni Java dato da JNI, che obbliga la scrittura di codice in un linguaggio diverso da Java (tipicamente in C) e permette, continuando a scrivere codice Java, di richiamare qualsiasi DLL a 32 o 64 bit.
Dopo qualche ora di lavoro avevo realizzato e testato un'interfaccia JNA fra Java e CeFdll.dll.
Quasi tutto a posto
Tutto funzionava come da manuale, fino a quando non mi sono imbattuto nella funzione che permette la connessione alla stampante tramite porta Ethernet in TCP/IP.
L'unica diversità rispetto al flusso standard era data dal metodo di apertura della stampante. Un nuovo metodo che non avevo mai usato.
Il problema era che, quel metodo, continuava a darmi un errore di comunicazione:
Errore 65543: Errore di comunicazione ritornato dalla stampante in fase di scrittura
Ho controllato e ricontrollato il codice più volte, ma probabilmente c'era qualcosa che mi sfuggiva.
Per questa ragione, ho rispolverato il mio cassetto degli attrezzi per iniziare ad entrare nel dettaglio di quello che, per me, era un errore incomprensibile.
Utilizziamo JNI
Non capendo a che livello era il problema, ho iniziato a levare degli strati software, andando a programmare ad un livello più basso ed utilizzando JNI al posto di JNA.
Sicuramente un modo più rozzo di utilizzare una DLL, ma col vantaggio di poter fare un debug a livello di chiamata diretta fra C e DLL.
Scrivo quindi la classe Java, aggiungo il singolo metodo nativo che dovevo controllare, genero l'interfaccia, creo il programma in C, lo compilo in MinGW, provo il tutto e .. incredibilmente: funziona.
Provo a portare la classi JNI nel programma principale, per usarla al posto di quella JNA e, anche con JNI ho lo stesso errore di comunicazione.
Iniziavo a non capire il problema: come è possibile che, con un programma di poche righe tutto funzionasse e con un programma più complesso ci fossero errori di comunicazione?
Era venuto il momento di mettere le mani a CeFdll, di cui non avevo però i sorgenti.
Poco male: lancio OllyDBG, che in questo caso ero sicuro mi potesse dare soddisfazioni, e inizio a capire cosa fa CeFdll e il motivo per cui avesse un comportamento diverso sui miei due programmi.
Hidden debugger
La prima cosa che scopro è che, CeFdll, è predisposta internamente per fare il log di tutte le operazioni effettuate, sul file C:\CeFdll.log.
Non avendo trovato un modo standard per sbloccare il log, decido di modificare CefDLL per capirci qualcosa di più e mi permetto una piccola patch, cambiando un paio di byte e trasformando due je in jne.
000015AD: 75 74
00001627: 75 74
Come per magia, la dll inizia a generare un po' di log, indispensabile per capire che problema ci potesse essere.
Nell'ambiente di test funzionava tutto, il log era perfetto.
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Open ethernet core - Start
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Verify Printer Status
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Write Start
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Write End
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Read Start
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Custom LITE Protocol
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString VC Read End
[ Sat Oct 10 16:38:52 2015 ] CeComOutputDebugString Open ethernet - End
Riprovando la cosa nel programma principale, dove avevo l'errore di comunicazione, ho come risultato un log di una sola riga:
[ Sat Oct 10 16:39:29 2015 ] CeComOutputDebugString Open ethernet core - Start
sembrava quasi che non ci fosse neppure il tentativo di apertura della comunicazione TCP/IP verso la stampante.
Già che avevo fatto del reverse engineerig sulla DLL mi sono detto: perché non farlo anche a livello di protocollo TCP/IP, per capire quali dati transitano e perché non funziona la comunicazione fra stampante e PC?
In questo caso ho usato qualcosa di più semplice.
SockRedirector
Uno dei miei primi lavori per capire il funzionamento delle socket è stato quello di costruire un programma in grado di ricevere una chiamata TCP/IP verso un IP:PORTA e rigirarla verso un altro IP:PORTA, facendo un log dei byte in ingresso e dei byte in uscita. Una specie di NAT software con log.
Ammetto che il programma è abbastanza primitivo, ma fa il suo sporco lavoro. Per questo motivo lo uso spesso per analizzare i dati scambiati fra applicazioni diverse via TCP/IP.
In questo caso ho attivato SockRedirector su 127.0.0.1 porta 9100 e ho rediretto il traffico verso la stampante in ascolto all'indirizzo 192.168.10.121 porta 9100.
Grazie a questo ho capito alcune cose:
* La comunicazione è tutta in chiaro e non cifrata: questo è stato un bene, dato che mi ha permesso di analizzare i dati in transito.
* Il protocollo Custom è molto semplice e segue la prassi di avere un header di pochi byte fissi, una stringa di comunicazione, 2 byte di CRC e un terminatore finale.
* Era semplice ricostruire il protocollo di comunicazione in Java, senza utilizzare la DLL esterna, tramite l'uso di semplici socket.
Stavo però partendo per la tangente, mi ero allontanato un po' troppo dal problema.
Sono tornato quindi a leggere meglio il decompilato, in prossimità della funzione di apertura della porta TCP/IP, e mi sono accorto della chiamata a un'altra DLL CeNComLayer.dll.
Provo a metterla nella stessa cartella del programma che non funzionava e .. tutto inizia ad andare.
Ero stato fuorviato da un messaggio d'errore che avevo interpretato come di configurazione TCP/IP e non di mancanza di requisiti software.
Le ragioni
Per pulizia di programma avevo scelto di fare il deploy dell'applicazione tramite un unico JAR, costruito con Maven, al cui interno erano presenti anche le dll utilizzate da JNA (in prarica la CeFdll.dll ed altro).
Se non fosse che, da quella posizione, CeFdll non riuscire a fare la LoadLibrady della seconda DLL che veniva utilizzata per la comunicazione TCP/IP.
Finalmente avevo capito il problema e avevo una soluzione.
Conclusioni
Devo ammettere che quello che poteva sembrare un problema, alla fine si è rivelato un banale errore di configurazione.
La voglia di arrivare ad una soluzione è stata però uno stimolo per poter capire più in dettaglio come funzionava la comunicazione verso le stampanti Fasy e per entrare nel dettaglio del protocollo software utilizzato.
Cosa porto a casa?
- Come funzionano le dipendenze fra DLL all'interno di un programma JNA realizzato con un unico JAR
- Che in certi programmi la parte documentata è solo parte di quello che in realtà il programma è in grado di fare.
- Come funziona il protocollo di comunicazione Custom
- Una classe socket di comunicazione fra il mio programma e la stampante fiscale, che mi permetterà di gestire un numero infinito di stampanti all'interno dello stesso programma, anche in modalità multithread.
- Capisco, nuovamente, che un po' di sano reverse engineering può arrivare dove i messaggi d'errore e i manuali, a volte, non arrivano
Quando il reverse engineering è l'unica soluzione
In questi giorni mi sono trovato a lavorare sulle sulle stampanti Fasy tramite Java. L'unico problema è che l'SDK fornito a corredo della stampante funziona tramite DLL. Vediamo come sono riuscito comunque ad aggirare il problema
8 commenti Aggiungi il tuo
Buongiorno Sig. Baccan,
Intanto Grazie per l'articolo che oltre ad essere interessante a livello didattico si pone anche utile a livello pratico per il chi sta approcciando la CeFdll.dll con un linguaggio non previsto...
Nel mio caso sto cercando di ineragire via NodeJS + Node-ffi, rispetto al caso descritto nell'articolo la comunicazione si avvia ma si interrompe anche immediatamente e non viene restituito nessun errore (se non un generico ECONNRESET nell'inspect di node confermato dal controllo del traffico TCP che ho fatto con wireshark).
Ho provato a dare un'occhiata in CeNComLayer.dll e sono sicuro che anche qui ci sia un un log da attivare (di default c:\CeNComLayer.log) ma non riesco a trovare un'analogo je da modificare.
Non mi dispiacerebbe creare un'interfaccia node da mettere su github magari con un'interscambio dati in xml.
Mi rendo conto che l'articolo è di qualche tempo fa e che forse sono un pò off-topic!
La ringrazio anticipatamente per un'eventuale risposta Cordiali Saluti,
Roberto
Intanto Grazie per l'articolo che oltre ad essere interessante a livello didattico si pone anche utile a livello pratico per il chi sta approcciando la CeFdll.dll con un linguaggio non previsto...
Nel mio caso sto cercando di ineragire via NodeJS + Node-ffi, rispetto al caso descritto nell'articolo la comunicazione si avvia ma si interrompe anche immediatamente e non viene restituito nessun errore (se non un generico ECONNRESET nell'inspect di node confermato dal controllo del traffico TCP che ho fatto con wireshark).
Ho provato a dare un'occhiata in CeNComLayer.dll e sono sicuro che anche qui ci sia un un log da attivare (di default c:\CeNComLayer.log) ma non riesco a trovare un'analogo je da modificare.
Non mi dispiacerebbe creare un'interfaccia node da mettere su github magari con un'interscambio dati in xml.
Mi rendo conto che l'articolo è di qualche tempo fa e che forse sono un pò off-topic!
La ringrazio anticipatamente per un'eventuale risposta Cordiali Saluti,
Roberto
Ciao Rob
io farei questi controlli
1) Il demo della dll CeFDllDemo.exe funziona?
2) Se si ci sono alcune cose da controllare:
2.1) dll a 32 o 64 bit conpatibilmente con l'interfaccia usata
2.2) verifica dimensione dei parametri passati (strutture errate potrebbero causare un tentativo di connessione a indirizzo sbagliato e sconnessione immediata
2.3) Correttezza indirizzo di connessione stampante: il telnet su ip/porta funziona? Ricordo che avevo chiesto varie patch al kernel della stampante per sconnessioni e freeze dell'interfaccia tcp/ip
2.4) Usa un echo server per testare la connessione in ingresso in una finta stampante
a valle di queste verifiche la situazione dovrebbe essere più chiara
A livello protocollare la stampante gestisce una sequenza di byte abbastanza semplice, data da header di 4 byte
0x02
0x30
0x30
0x30
sequenza da inviare
checksum da 2 byte se non ricord male
terminatore
0x03
Spero che queste info, al netto del fatto che non ho accesso al codice, possano aiutarti
ciao
matteo
io farei questi controlli
1) Il demo della dll CeFDllDemo.exe funziona?
2) Se si ci sono alcune cose da controllare:
2.1) dll a 32 o 64 bit conpatibilmente con l'interfaccia usata
2.2) verifica dimensione dei parametri passati (strutture errate potrebbero causare un tentativo di connessione a indirizzo sbagliato e sconnessione immediata
2.3) Correttezza indirizzo di connessione stampante: il telnet su ip/porta funziona? Ricordo che avevo chiesto varie patch al kernel della stampante per sconnessioni e freeze dell'interfaccia tcp/ip
2.4) Usa un echo server per testare la connessione in ingresso in una finta stampante
a valle di queste verifiche la situazione dovrebbe essere più chiara
A livello protocollare la stampante gestisce una sequenza di byte abbastanza semplice, data da header di 4 byte
0x02
0x30
0x30
0x30
sequenza da inviare
checksum da 2 byte se non ricord male
terminatore
0x03
Spero che queste info, al netto del fatto che non ho accesso al codice, possano aiutarti
ciao
matteo
Buongiorno Matteo,
Grazie per la risposta di seguito rispetto ai punti elencati:
1) si, in effetti ho anche loggato con wireshark il traffico ed emulato in modo rudimentale i comandi con node e la libreria sua net per il collegamento tcp
2.1) node a 64bit, le librerie .dll a 32bit giustamente non vengono caricate da node-ffi
2.2) qualche dubbio ce l'ho ma dato che la connessione tcp viene avviata verso i corretti indirizzi e porte l'unico ulteriore parametro che potrebbe essere allocato in modo errato è il codice di errore. In pratica con node-ffi ho:
var ffi = require('ffi')
, ref = require('ref');
var cfd = ffi.Library('CeFdll.dll', {
//DWORD CEFOpenEth (LPTSTR strIp, DWORD dwPort, LPDWORD lpdwSysError)
"CEFOpenEth" : [ref.types.long, ['string', ref.types.long, ref.types.long]],
});
var errBuf = 0x00000000;
errBuf.type = ref.types.long
var ret = cfd.CEFOpenEth( '192.168.1.121', 9100, errBuf);
Qui l'esecuzione si interrompe senza restituire errori, solo node inspect restituisce ECONNRESET che però non dice nulla. Il log CeFdll.log contiene.
[ Wed Feb 27 16:17:38 2019 ] CeComOutputDebugString Open ethernet core - Start
[ Wed Feb 27 16:17:38 2019 ] CeComOutputDebugString Verify Printer Status
Loggando il traffico con wireshark ho notato che dove dovrebbe esserci la chiamata corrispondente a "CEFOpenEth" ovvero gli hex "0x02, 0x30, 0x30, 0x30, 0x31, 0x31, 0x30, 0x39, 0x34, 0x37, 0x03" non c'è nulla e quindi la stampante interrompe la comunicazione.
Sono abbastanza sicuro che il secondo log CeNComLayer.log potrebbe aiutare
2.3) Si il traffico arriva correttamente
2.4) Le connessioni in ingresso dovrebbero funzionare correttamente (vedi immagine traffico tcp)
In effetti come dicevo ho provato a interfacciare direttamente la comunicazione con un semplice client tcp e copiando i comandi di controllo stampante ed in questo modo funziona correttamente, non sarebbe la soluzione migliore per via di possibili mancanze (questi comandi non sono descritti).
Grazie ancora,
Roberto
Grazie per la risposta di seguito rispetto ai punti elencati:
1) si, in effetti ho anche loggato con wireshark il traffico ed emulato in modo rudimentale i comandi con node e la libreria sua net per il collegamento tcp
2.1) node a 64bit, le librerie .dll a 32bit giustamente non vengono caricate da node-ffi
2.2) qualche dubbio ce l'ho ma dato che la connessione tcp viene avviata verso i corretti indirizzi e porte l'unico ulteriore parametro che potrebbe essere allocato in modo errato è il codice di errore. In pratica con node-ffi ho:
var ffi = require('ffi')
, ref = require('ref');
var cfd = ffi.Library('CeFdll.dll', {
//DWORD CEFOpenEth (LPTSTR strIp, DWORD dwPort, LPDWORD lpdwSysError)
"CEFOpenEth" : [ref.types.long, ['string', ref.types.long, ref.types.long]],
});
var errBuf = 0x00000000;
errBuf.type = ref.types.long
var ret = cfd.CEFOpenEth( '192.168.1.121', 9100, errBuf);
Qui l'esecuzione si interrompe senza restituire errori, solo node inspect restituisce ECONNRESET che però non dice nulla. Il log CeFdll.log contiene.
[ Wed Feb 27 16:17:38 2019 ] CeComOutputDebugString Open ethernet core - Start
[ Wed Feb 27 16:17:38 2019 ] CeComOutputDebugString Verify Printer Status
Loggando il traffico con wireshark ho notato che dove dovrebbe esserci la chiamata corrispondente a "CEFOpenEth" ovvero gli hex "0x02, 0x30, 0x30, 0x30, 0x31, 0x31, 0x30, 0x39, 0x34, 0x37, 0x03" non c'è nulla e quindi la stampante interrompe la comunicazione.
Sono abbastanza sicuro che il secondo log CeNComLayer.log potrebbe aiutare
2.3) Si il traffico arriva correttamente
2.4) Le connessioni in ingresso dovrebbero funzionare correttamente (vedi immagine traffico tcp)
In effetti come dicevo ho provato a interfacciare direttamente la comunicazione con un semplice client tcp e copiando i comandi di controllo stampante ed in questo modo funziona correttamente, non sarebbe la soluzione migliore per via di possibili mancanze (questi comandi non sono descritti).
Grazie ancora,
Roberto
Ciao Rob
ti lascio la riga (semplificata) che uso per la connessione eth
// Setup porta
IntByReference lpdwSysError = new IntByReference();
lpdwSysError.setValue(0);
WinDef.DWORD CEFOpen = new WinDef.DWORD(0);
// Creo il buffer per il ritorno
Memory mOut = new Memory(255);
mOut.setString(0, "192.168.1.121");
// Apro ethernet
CEFOpen = cefdll.CEFOpenEth(mOut, new WinDef.DWORD(9100), lpdwSysError);
in questo modo mi funziona.
L'errore di sconnessione, a fronte di non traffico, mi fa pensare solo a un problema di interpretazione dei parametri di connessione che, pur giusti, sono utilizzati in modo errato per dimensionamento dei parametri di ingresso in DLL
Il dubbio mi viene sul terzo parametro che è un riferimento ad una variabile e non un valore, nella dichiarazione che uso lo esternalizzo in questo modo
WinDef.DWORD CEFOpenEth(Pointer strip, WinDef.DWORD dwPort, IntByReference lpdwSysError);
Puntatore a stringa, DWORD come valore, Int by riferimento
ciao
matteo
ti lascio la riga (semplificata) che uso per la connessione eth
// Setup porta
IntByReference lpdwSysError = new IntByReference();
lpdwSysError.setValue(0);
WinDef.DWORD CEFOpen = new WinDef.DWORD(0);
// Creo il buffer per il ritorno
Memory mOut = new Memory(255);
mOut.setString(0, "192.168.1.121");
// Apro ethernet
CEFOpen = cefdll.CEFOpenEth(mOut, new WinDef.DWORD(9100), lpdwSysError);
in questo modo mi funziona.
L'errore di sconnessione, a fronte di non traffico, mi fa pensare solo a un problema di interpretazione dei parametri di connessione che, pur giusti, sono utilizzati in modo errato per dimensionamento dei parametri di ingresso in DLL
Il dubbio mi viene sul terzo parametro che è un riferimento ad una variabile e non un valore, nella dichiarazione che uso lo esternalizzo in questo modo
WinDef.DWORD CEFOpenEth(Pointer strip, WinDef.DWORD dwPort, IntByReference lpdwSysError);
Puntatore a stringa, DWORD come valore, Int by riferimento
ciao
matteo
Buongiorno Matteo,
Grazie ancora per la disponibilità!
Sembra un problema di definizione dei parametri, non so se dipenda da node-ffi le definizioni le avevo prese da un esempio in c dove il terzo parametro era era un "DWORD dwSysError = 0x00000000;".
Modificando il terzo parametro (o non passandolo...) la comunicazione si avvia.
Appena ho risolto posto qui la definizione x node magari potrebbe essere utile a qualcun'altro.
Grazie Ancora Buona Giornata,
Roberto
Grazie ancora per la disponibilità!
Sembra un problema di definizione dei parametri, non so se dipenda da node-ffi le definizioni le avevo prese da un esempio in c dove il terzo parametro era era un "DWORD dwSysError = 0x00000000;".
Modificando il terzo parametro (o non passandolo...) la comunicazione si avvia.
Appena ho risolto posto qui la definizione x node magari potrebbe essere utile a qualcun'altro.
Grazie Ancora Buona Giornata,
Roberto
salve a tutti
scusate ma non riesco a risolvere...
Collegato tramite TCP, invio comandi ma non riesco a capire come mandare quantità dell'articolo.
Qualche dritta sulla stringa comandi?
Ciao Giovanni
Stai usando la libreria custom o stai facendo manualmente il codice senza supporto custom ?
Ciao
Matteo
Stai usando la libreria custom o stai facendo manualmente il codice senza supporto custom ?
Ciao
Matteo
Per commentare occorre essere un utente iscritto