
CHAPITRE 1. Des invités inattendus
Tout a commencé ce matin malheureux lorsque le chef de projet a annoncé que les délais de mise en œuvre du projet devraient être rapidement et résolument réduits d'un mois. Plus précisément, le projet devrait être prêt en 4 jours. Non, notre PO n'est pas une bête, et ne ressemble pas du tout à un
hibou (peut-être juste un peu comme un corbeau), c'est juste arrivé. Eh bien, si c'est nécessaire, c'est nécessaire, d'autant plus que l'équipe (et je suis le principal développeur de l'équipe "C") s'est vu promettre quelque chose de savoureux. L'horloge et le calendrier étaient jeudi, 11h00, lundi, le projet devrait être prêt.
Pour commencer, que faisons-nous du tout. Nous nous consacrons à l'automatisation des salles de cinéma - contrôle automatique et à distance des équipements, automatisation de la projection de films, surveillance, panneaux vidéo, et maintenant aussi terminaux pour la vente de billets et de bars. Plus précisément, le dernier paragraphe est consacré à cet article.
Le projet lui-même, qui devait être achevé avant lundi, est une sorte de couche entre le serveur principal de Scala et le terminal de paiement en fer VeriFone VX 820 (en fait, il y a plus de terminaux, mais prenez-le comme exemple). Il est clair que personne ne nous donnera de transactions comme ça, donc les utilitaires et les bibliothèques de Sberbank / Arcus et UCS sont utilisés. Par conséquent, le plan de travail devrait être le suivant:

Extérieurement, cela ressemble à ceci:

En outre, ce sous-système devrait être utilisé sur les caisses enregistreuses standard que tout le monde a vues dans n'importe quelle salle de cinéma des caissières.
Selon la tradition interne, nous appelons chaque projet de notre équipe un nom de la mythologie nordique ancienne, pour ce sous-système le nom Gefjon a été choisi - le nom de la déesse de la fertilité et de la fertilité (un bon nom pour un serveur de paiement, n'est-ce pas? Eh bien, la légende des taureaux coupant l'île correspond parfaitement à l'architecture actuelle, couper le travail avec des équipements d'une langue de haut niveau).
Le format des messages entrants et sortants est un serveur HTTP avec une charge JSON. Il s'agit du compromis optimal entre Scala, difficile à isoler les données binaires des flux de socket et C, difficile à transférer pour transférer des objets via le réseau. Il n'y a pas beaucoup d'opérations possibles qui doivent être opérées: paiement, annulation, retour, différents types de rapports, ouverture du menu de service et ping. Cela n'a rien de compliqué. Puisqu'il existe trois systèmes bancaires (et qu'une reconstitution ultérieure de la famille est prévue), il a été décidé de diviser le projet en composantes:

Les blocs verts sont ceux que nous devions faire, les bleus sont ceux qui ne peuvent pas être modifiés et que la banque fournit.
Étant donné que les principaux problèmes ne sont apparus qu'avec les logiciels de Sberbank, l'article dans son ensemble sera consacré aux pièges que nous avons comptés avec notre bateau.
CHAPITRE 2. Agneau rôti

(photo: heaclub.ru)
... ressemble à quelque chose comme ça. Le code de ce prototype, qui a été écrit il y a plusieurs mois afin de faire comprendre à toutes les personnes de niveau supérieur que nous pouvons travailler avec des applications bancaires, était à peu près le même.
char buf[BUF_KB * 2]; char * null; char * grep; #ifdef _WIN32_WINNT char * ptr; null = "nul"; grep = "findstr"; #else null = "/dev/null"; grep = "grep"; #endif sprintf(buf, "%s %"PRIi32"= %sops.ini >%s 2>%s || " "echo %"PRIi32"=9,6,PINPAD_TEST >> %sops.ini", grep, TERM_ARCUS_TEST_PINPAD, TERM_PATH, null, null, TERM_ARCUS_TEST_PINPAD, TERM_PATH); #ifdef _WIN32_WINNT ptr = buf; while (*ptr) { if (*ptr == '/') *ptr = '\\'; ptr++; } #endif
Il est clair que cela ne convenait pas à la version Production, il était donc essentiellement nécessaire de tout réécrire.
Chaque banque qui fournit des bibliothèques pour travailler avec le terminal propose généralement deux options de connexion: via les fonctions de bibliothèque (.so / .dll) ou via un utilitaire prêt à l'emploi qui n'a besoin que de passer deux valeurs - le type d'opération et le montant (si nécessaire). En théorie, rien de compliqué, juste quelque chose
char buffer[100]; sprintf(buffer, "%d %d", atoi(argv[1]), atoi(argv[2])); system(buffer);
Le résultat de l'opération sera placé dans le fichier «e», et le slip-check dans le fichier «p». Envoyez simplement ces fichiers à stdout avec conversion en JSON, de sorte que le serveur HTTP les envoie simplement en tant que charge utile sans penser à ce qui s'y trouve.
Mais cet article n'aurait pas été publié si tout avait été si simple.
CHAPITRE 4. À travers la montagne et sous la montagne
La version initiale de l'implémentation était un simple appel d'application - le serveur HTTP a appelé l'encapsuleur nécessaire avec des paramètres unifiés (par exemple, le rapport X est 4), et l'utilitaire, par exemple gfj_pilot, a lancé sb_pilot avec le paramètre requis pour cette opération (par exemple, le rapport X est 9) . L'utilitaire wrapper a ensuite lu le résultat de l'opération à partir du fichier électronique (par exemple, 2000 - «paiement refusé, répétez l'opération») et l'a converti en une erreur universelle (par exemple 3 - «Erreur de lecture ou de traitement de la carte / du compte, répétez l'opération»). Après cela, le fichier «p» a été converti en base64 pour éviter la rupture du formatage et envoyé avec le résultat à stdout en tant que JSON.
Tout cela a parfaitement fonctionné jusqu'à ce qu'à un moment précis, on nous informe que ...
... cela ne fonctionne pas sous Windows.

Eh bien, plus précisément, Windows lui-même n'a aucun problème (sauf que le glissement est généré dans l'encodage Cp-1251, et la console fonctionne dans CP866). Le fichier «e» n'a tout simplement pas été généré. Lancement direct d'un utilitaire bancaire:
C:\banks\sber\sb_pilot>dir C . : B401-6B9D C:\banks\sber\sb_pilot 04.02.2019 12:28 <DIR> . 04.02.2019 12:28 <DIR> .. 31.01.2019 17:12 10 832 F12X24.BIN 31.01.2019 17:12 128 000 gate.dll 31.01.2019 17:12 72 192 loadparm.exe 31.01.2019 17:12 36 204 OPT0.R 31.01.2019 17:12 20 716 OPT1.R 31.01.2019 17:12 1 806 OPT3.R 31.01.2019 17:12 388 608 pilot_nt.dll 31.01.2019 23:06 463 pinpad.ini 31.01.2019 17:12 91 136 posScheduler.exe 31.01.2019 17:12 418 printers.ini 01.02.2019 16:51 91 646 sbkernel1902.log 31.01.2019 17:12 653 312 sbrf.dll 31.01.2019 17:12 840 192 SBRFCOM.dll 31.01.2019 17:12 3 142 656 sb_kernel.dll 01.02.2019 16:51 9 SESS.D 01.02.2019 16:51 715 SPLC.D 31.01.2019 17:12 72 192 upwin.exe 20 5 659 718 2 37 567 004 672 # (1) 10 (1000 ) C:\banks\sber\sb_pilot>loadparm.exe 1 1000 C:\banks\sber\sb_pilot>dir C . : B401-6B9D C:\banks\sber\sb_pilot 04.02.2019 12:28 <DIR> . 04.02.2019 12:28 <DIR> .. 04.02.2019 12:28 216 commerr.log 31.01.2019 17:12 10 832 F12X24.BIN 31.01.2019 17:12 128 000 gate.dll 31.01.2019 17:12 72 192 loadparm.exe 31.01.2019 17:12 36 204 OPT0.R 31.01.2019 17:12 20 716 OPT1.R 31.01.2019 17:12 1 806 OPT3.R 01.02.2019 18:51 1 349 p 31.01.2019 17:12 388 608 pilot_nt.dll 31.01.2019 23:06 463 pinpad.ini 31.01.2019 17:12 91 136 posScheduler.exe 31.01.2019 17:12 418 printers.ini 04.02.2019 12:28 92 218 sbkernel1902.log 31.01.2019 17:12 653 312 sbrf.dll 31.01.2019 17:12 840 192 SBRFCOM.dll 31.01.2019 17:12 3 142 656 sb_kernel.dll 01.02.2019 16:51 9 SESS.D 01.02.2019 16:51 715 SPLC.D 31.01.2019 17:12 72 192 upwin.exe 19 5 659 029 2 37 567 008 768 C:\banks\sber\sb_pilot>
En effet, il n'y a pas de fichier "e". Pierre vers Sberbank # 1. Nous écrivons une lettre à Sberbank (nous avons par la suite reçu une réponse selon laquelle cela devrait être le cas), et comme il n'y a pas de temps pour la correspondance et qu'il est nécessaire de commencer dès maintenant, nous recherchons des solutions de contournement pour obtenir le résultat.
04.02 12:28:55 SBKRNL: Failed to open device \\.\COM1, err 2 04.02 12:28:56 SBKRNL: Failed to open device \\.\COM1, err 2 04.02 12:28:56 SBKRNL: Result = 0 04.02 12:28:56 GATE: unlock:'00000054' 04.02 12:28:56 GATE: lock:'00000054' 'UPOSWINMUTEX2' 04.02 12:28:56 GATE: unlock:'00000054' 04.02 12:28:56 LOADPARM: Unloading GATE.DLL... 04.02 12:28:56 GATE: SB_KERNEL.DLL is unloaded 04.02 12:28:56 LOADPARM: GATE.DLL unloaded
Oui, le résultat peut être obtenu à partir du journal sbkernel.log. Inconvénient, en plus il n'y a pas de hachage de carte pour visser par la suite «Merci» de Sberbank. Pas bon.
Vous devrez vous connecter à la bibliothèque pilot_nt.dll et importer des fonctions à partir de celle-ci. Tout irait bien, mais ... Une pierre vers Sberbank # 2: il n'y a pas une telle bibliothèque sous Linux, vous devrez créer deux applications différentes pour différentes plates-formes - pour linux, appelez l'utilitaire sb_pilot (similaire à loadparm.exe, d'ailleurs la pierre # 3 pour un nom d'utilitaire différent sous différentes plates-formes ), sous Windows, connectez-vous à la bibliothèque pilot_nt.dll.
CHAPITRE 5. Énigmes dans le noir
À 19h00.
Sberbank est une grande entreprise, la plupart des solutions logicielles sont produites conformément aux GOST et aux documents officiels. Nous entrons dans le catalogue que Sberbank fournit avec les bibliothèques:
Sberbank$ ls -l Docs 30160 drwx------ 2 alex alex 4096 17 19:31 FAQ -rw-rw-r-- 1 alex alex 3398465 9 2018 UPOS ().docx -rw-rw-r-- 1 alex alex 1182078 9 2018 UPOS .docx -rw-rw-r-- 1 alex alex 853504 9 2018 .doc drwx------ 3 alex alex 4096 31 17:11 -rw-rw-r-- 1 alex alex 5280787 9 2018 POS-.docx -rw-rw-r-- 1 alex alex 1149640 9 2018 .docx drwx------ 2 alex alex 4096 28 2018 UPOS drwx------ 2 alex alex 4096 28 2018 -rw-rw-r-- 1 alex alex 3451601 9 2018 ().docx -rw-rw-r-- 1 alex alex 1956196 9 2018 .docx -rw-rw-r-- 1 alex alex 1043161 9 2018 ()_().docx -rw-rw-r-- 1 alex alex 4348157 9 2018 POS-.docx -rw-rw-r-- 1 alex alex 3970267 9 2018 .docx drwx------ 3 alex alex 4096 28 2018 -rw-rw-r-- 1 alex alex 2644702 9 2018 POS-.docx drwx------ 2 alex alex 4096 28 2018 -rw-rw-r-- 1 alex alex 1558211 9 2018 .png
Beaucoup de bien, mais nous ne sommes intéressés que par le répertoire pour les développeurs:
Sberbank$ ls -l Docs/\ \ \ / 8704 -rw-rw-r-- 1 alex alex 47105 9 2018 1C.docx -rw-rw-r-- 1 alex alex 1824 9 2018 cardtype.h -rw-rw-r-- 1 alex alex 2590378 9 2018 cr_ttk_protocol_ru.rtf -rw-rw-r-- 1 alex alex 208 9 2018 deprtmnt.h -rw-rw-r-- 1 alex alex 16681 9 2018 errors.h drwx------ 6 alex alex 4096 28 2018 examples -rw-rw-r-- 1 alex alex 58575 9 2018 gate.h -rw-rw-r-- 1 alex alex 4218 9 2018 paramsln.h -rw-rw-r-- 1 alex alex 61693 9 2018 pilot_nt.h -rw-rw-r-- 1 alex alex 28160 9 2018 ReadTrack2.doc -rw-rw-r-- 1 alex alex 7417 9 2018 sbkernel.h -rw-rw-r-- 1 alex alex 144896 9 2018 sb_pilot.doc -rw-rw-r-- 1 alex alex 3525323 9 2018 ole- sbrf.dll.rtf -rw-rw-r-- 1 alex alex 46683 9 2018 gate.dll.chi -rw-rw-r-- 1 alex alex 255414 9 2018 gate.dll.chm -rw-rw-r-- 1 alex alex 814653 9 2018 gate.dll.pdf -rw-rw-r-- 1 alex alex 41618 9 2018 pilot_nt.chi -rw-rw-r-- 1 alex alex 241716 9 2018 pilot_nt.chm -rw-rw-r-- 1 alex alex 968753 9 2018 pilot_nt.pdf -rw-rw-r-- 1 alex alex 81 9 2018 .txt
Beaucoup de vieux papiers, juste au cas où, relisez pilot_nt, dont nous apprenons ce qui suit:
Tableau 1. Système d'exploitation sb_pilot pris en charge.
Il s'avère que l'utilitaire pour Windows devrait toujours s'appeler sb_pilot. Eh bien, une pierre vers Sberbank # 4 pour l'incohérence de sa propre documentation.
Transfert des résultats du programme.
À la fin du programme, deux fichiers texte sont formés - le fichier d'échange et le fichier de contrôle.
Le premier est nommé e et est destiné à transmettre les paramètres de l'opération parfaite au programme appelant. La première ligne de ce fichier contient le code du résultat de l'opération et un texte séparé par des virgules expliquant le message texte. Le code 0 signifie un paiement réussi, toute autre valeur - refus ou incapacité d'effectuer le paiement.

Paresseusement, nous jetons une pierre de plus et commençons à étudier la documentation pour connecter directement la bibliothèque.
La procédure d'appel des fonctions de bibliothèque
Lors du paiement (retour) d'achats par carte de crédit, le programme de caisse doit appeler la fonction card_authorize () de la bibliothèque Sberbank en remplissant les champs TType et Amount et en spécifiant zéro dans les champs restants. À la fin de la fonction, vous devez analyser le champ RCode. S'il contient la valeur «0» ou «00», l'autorisation est considérée comme terminée avec succès, sinon rejetée. De plus, vous devez vérifier la valeur du champ Vérifier.
S'il n'est pas NULL, il doit être envoyé à l'impression (en mode non fiscal) puis
supprimer en appelant la fonction GlobalFree (). Lors de la fermeture d'un quart de travail, le programme de trésorerie doit appeler la fonction close_day () à partir de la bibliothèque Sberbank en remplissant le champ TType = 7 et en spécifiant des valeurs nulles dans les champs restants. À la fin de la fonction, vérifiez la valeur du champ Vérifier.
Si le champ Vérifier n'est pas NULL, il doit être envoyé à l'impression (en mode non fiscal) puis supprimé en appelant la fonction GlobaFree ().
Cela semble facile, même un fichier d'en-tête est fourni. Eh bien, branchez-le, compilez et ...
$ cat main.c && i686-w64-mingw32-gcc main.c -o main.a #include "pilot_nt.h" int main(void) { return 0; } In file included from main.c:1:0: pilot_nt.h:525:3: error: unknown type name 'auth_answer' auth_answer ans; ^ pilot_nt.h:544:3: error: unknown type name 'auth_answer' auth_answer ans; ^ pilot_nt.h:567:3: error: unknown type name 'auth_answer' auth_answer ans; ^ pilot_nt.h:590:3: error: unknown type name 'auth_answer' auth_answer ans; ^ pilot_nt.h:627:3: error: unknown type name 'auth_answer' auth_answer ans; ^ pilot_nt.h:668:3: error: unknown type name 'auth_answer' auth_answer ans;
Ummm ... quoi? Ouvrez pilot_nt.h:
#ifdef __cplusplus extern "C"{ #endif <...> /** * * , . */ struct auth_answer{ int TType; /**< [in] . ::OpetationTypes */ unsigned long Amount; /**< [in] */ char RCode[3]; /**< [out] */ char AMessage[16]; /**< [out] */ int CType; /**< [in,out] */ char* Check; /**< [out] , GlobalFree */ }; <...> struct auth_answer7{ auth_answer auth_answ; /**< [in, out] . . ::auth_answer */ <---- THIS char AuthCode[MAX_AUTHCODE]; /**< [out] . 7 . */ char CardID [CARD_ID_LEN]; /**< [out] . 25 . */ int SberOwnCard; /**< [out] */ };
Immédiatement, sans regarder la pierre pour des commentaires en russe dans le codage CP1251.
Eh bien, la pierre la plus sérieuse: chers développeurs C ++. Si vous écrivez «C» externe, cela signifie que le code à l'intérieur du bloc doit être compilé par le compilateur C. Si vous n'avez PAS fait de `typedef` d'une structure, chaque fois que vous le mentionnez comme référence de type, vous devez écrire le mot-clé` struct`.
Corrigez le fichier pour les développeurs, en remplaçant le mot `struct` là où vous en avez besoin. Lien vers la bibliothèque `pilot_nt.dll`. Victoire, non? Nous lançons notre application.
CHAPITRE 6. Du feu aux flammes
Eh bien, vous comprenez, non? L'application se bloque simplement. Jusqu'au principal. Méditez, ajoutez l'analogue NIH de la fonction errno pour Windows: GetLastError (pierre # 3 vers Microsoft, les deux premiers sont pour les encodages).
C:\banks\sber\WIN>sb_pilot.exe 1 1000 E: !g_sblibrary (0xc0000096)
0xc0000096? GetLastError ne devrait-elle pas renvoyer un code d'erreur adéquat?
Pour une liste complète des codes d'erreur fournis par le système d'exploitation, voir Codes d'erreur système.
Ouais, ouvrez l'article
ici :
Les rubriques suivantes fournissent des listes de codes d'erreur système. Ces valeurs sont définies dans le fichier d'en-tête WinError.h.
- Codes d'erreur système (0-499) (0x0-0x1f3)
- Codes d'erreur système (500-999) (0x1f4-0x3e7)
- Codes d'erreur système (1000-1299) (0x3e8-0x513)
- Codes d'erreur système (1300-1699) (0x514-0x6a3)
- Codes d'erreur système (1700-3999) (0x6a4-0xf9f)
- Codes d'erreur système (4000-5999) (0xfa0-0x176f)
- Codes d'erreur système (6000-8199) (0x1770-0x2007)
- Codes d'erreur système (8200-8999) (0x2008-0x2327)
- Codes d'erreur système (9000-11999) (0x2328-0x2edf)
- Codes d'erreur système (12000-15999) (0x2ee0-0x3e7f)
Ok, nous avons eu une erreur non documentée, nous jetons une pierre et ouvrons le google omniscient:
L'essence de l'erreur est qu'un sous-programme utilise l'une des instructions
- _inp ()
- _inpw ()
- _inpd ()
- _outp ()
- _outpw ()
- _outpd ()
Leur utilisation est interdite sous les cœurs NT, car ils essaient de travailler directement avec le port parallèle. Apparemment, ce code est appelé dans l'initialiseur de bibliothèque, c'est-à-dire la bibliothèque au démarrage veut interroger les ports pour les périphériques, mais le noyau NT nécessite un travail via le pilote.
Situation désespérée?
CHAPITRE 8. Araignées et mouches
22 h 00 Juste au cas où, l'idée surgit de vérifier que ce n'est pas dû au fait que nous utilisons la compilation croisée avec Linux en utilisant mingw. Dans le même temps, nous comprenons que Sberbank ne fournit qu'une application 32 bits, donc cela ne fonctionnera pas avec une application 64 bits, d'accord, mais nous allons tout de même lancer une pierre vers Sberbank pour la version 32 uniquement en 2019.
Éléments fournis: installé dans virtualbox windows 7;
Obligatoire : installez Visual Studio et copiez MVP.
Nous allons sur le site Web de Microsoft, téléchargeons Visual Studio 2017. Nous prenons la licence communautaire, car nous la prenons pour vérification, pour les entreprises, la licence sera achetée si elle décolle.
Téléchargez quelques centaines de mégaoctets et ...
Nous constatons que notre version du système d'exploitation (Windows 7) n'est pas prise en charge.
Ok, on va sur toutes sortes de sites obscènes, on cherche Visual Studio 2008, on télécharge à nouveau quelques centaines de mégaoctets et ...
Nous obtenons le fichier iso.
D'accord, essayons d'installer Daemon Tools 10 (puisque c'est la version que le site propose) pour insérer ce disque virtuel.
Exécutez le binaire téléchargé. Raté, nécessite .NET Framework 4.5, téléchargez, installez.
Nous commençons le binaire téléchargé, l'installation a commencé, le chargeur de démarrage dit qu'il a besoin de 4.5.2, téléchargez, installez.
Nous commençons le binaire téléchargé, l'installation a commencé, le chargeur de démarrage dit qu'il n'ira nulle part avant d'installer la mise à jour de sécurité KB3033929, de télécharger, d'installer.
Et nous recevons une claque au visage de Microsoft comme message:

Nous lançons violemment une pierre très pointue vers Microsoft, téléchargeons les anciens Daemon Tools de torrents, décompressons avec succès Visual Studio, installons, enfin (00:00) compilons MVP, nous obtenons la même erreur. Eh bien, il y avait une bonne version, mais elle n'a pas grandi ensemble.
CHAPITRE 11. Au seuil
Nous écrivons au deuxième programmeur, qui à ce moment achève de toute urgence le serveur et la procédure d'enregistrement. Il rappelle qu'il existe un
référentiel git qui se connecte à cette bibliothèque sur NT et fonctionne avec.
En regardant de manière suspecte le référentiel, téléchargez-le, compilez-le et exécutez-le. Ça marche.

Nous regardons le code avec encore plus de méfiance. Le code est identique, sauf qu'il est écrit en C ++ et non en C.
Nous comprenons que la langue n'y est pour rien. Nous regardons les bibliothèques de la Sberbank, que le code tire.
Nous voyons le dernier commit.
Et ici, nous attendons une autre surprise.
Il s'avère que les versions de la bibliothèque Sberbank peuvent être différentes. Le dernier commit augmente la version du 23 au 27. Copiez la version de la gita sur votre ordinateur de test - FONCTIONNE!
Nous vérifions toutes les archives envoyées par Sberbank, comparons les versions et construisons une tablette:
Très bien, guérissons maintenant. Sur les systèmes où cela coûte 26, passez à 29 ou 27 et tout décollera.
Nous jetons la pierre n ° 9 vers Sberbank pour avoir rompu le comportement sur les systèmes NT.
CHAPITRE 12. Ce qui les attendait à l'intérieur
Pas assez de fichier e? Peu importe, nous prenons des en-têtes corrigés, nous lions dynamiquement à la bibliothèque pour retourner correctement une erreur, écrivons un code qui écrit simplement le code de retour de la fonction dans le fichier "e", appelons le biner sb_pilot.exe et ...
Pour travailler, ça marche.
C'est juste la version pour le système "Cryptor" qui ne crée pas de fichier "p".
Nous regardons tristement le sang qui coule sur les jointures et la dent dans le mur.Pour commencer, quel est le système Cryptor.
Cryptera est une entreprise danoise qui produit des équipements de cryptage / équipements de sécurité / clés, etc. Je pense que vous avez tous vu une des copies de leurs produits:

Ainsi, Sberbank utilise son module de chiffrement pour les pinpads et publie une bibliothèque spéciale «corrigée», dans laquelle, comme nous l'avons déjà compris, le fichier «p» n'est pas créé. Nous écrivons à Sberbank à ce sujet et dans quelques jours, nous obtiendrons la réponse que «sous le système d'origine, le fichier« p »sera créé, mais sous le correctif de Cryptor, il ne le sera pas.» Nous leur donnerons la pierre n ° 10 dans quelques jours, car vous devez travailler maintenant.
Heureusement ou malheureusement, les fonctions que nous utilisons pour mener des opérations renvoient la structure déjà mentionnée:
struct auth_answer{ int TType; unsigned long Amount; char RCode[3]; char AMessage[16]; int CType; char* Check; };
Oh, très bien, le chèque est déjà là, nous pouvons l'enregistrer nous-mêmes dans un fichier ou l'imprimer immédiatement en JSON ...
printf("%s\n", answer.Check);
Et nous obtenons le blocage de l'application en raison de l'accès à un pointeur non valide.
CHAPITRE 14. Feu et eau
4 h Nous effectuons Seth Bandha Sarvangasana pour se calmer et lisons attentivement le manuel:
[out] image du chèque, doit être libérée par GlobalFree dans le programme appelant
Qu'est-ce que cela nous donne? Beaucoup. Tout d'abord, puisque le pointeur doit être nettoyé à l'aide de GlobalFree, il a été placé à l'aide de
GlobalAlloc . Par conséquent, il n'émet pas de pointeur sur la mémoire, comme c'était le cas dans la version 16 bits, mais un numéro d'objet avec le type sémantiquement déclaré HGLOBAL, qui peut être alimenté dans la fonction GlobalSize pour obtenir la taille du bloc alloué et GlobalLock pour bloquer un morceau de mémoire, mais obtenir le pointeur d'origine. Par ailleurs, pierre # 6 vers Microsoft pour NIH malloc et gratuit, qui sont dans la bibliothèque standard.
printf("%s\n", GlobalLock(answer.Check));
Et obtenez toujours une goutte. D'accord, que montre GlobalSize? Zéro? D'une certaine manière étrange.
Nous vérifions d'autres fonctions qui devraient également donner un glissement - nous voyons la même image.
Je pense que je peux générer un bordereau par moi-même en fonction des données que la fonction de paiement la plus cool peut donner (oui, Sberbank a des fonctions appelées card_authorize2..14, je ne jetterai pas de pierre pour cela):
struct auth_answer14 { auth_answer ans; char AuthCode[MAX_AUTHCODE]; char CardID[CARD_ID_LEN]; int ErrorCode; char TransDate[TRANSDATE_LEN]; int TransNumber; int SberOwnCard; char Hash[CARD_HASH_LEN]; char Track3[CARD_TRACK3_LEN]; DWORD RequestID; DWORD Department; char RRN[MAX_REFNUM]; DWORD CurrencyCode; char CardEntryMode; char CardName[MAX_CARD_NAME_LEN]; char AID[MAX_AID_ASCII_LEN]; char FullErrorText[MAX_FULL_ERROR_TEXT]; DWORD GoodsPrice; DWORD GoodsVolume; char GoodsCode[MAX_GOODS_CODE+1]; char GoodsName[MAX_GOODS_NAME]; }; PILOT_NT_API int card_authorize14( char *track2, struct auth_answer14 *auth_answer, struct payment_info_item *payinfo );
Nous essayons de sélectionner les champs ... Nous découvrons qu'une seule chose nous a séparés du bonheur - Nom et prénom du titulaire de la carte. Sans eux, un bordereau n'est pas considéré comme légal :Détails: identifiant d'un GAB, d'un terminal électronique ou d'autres moyens techniques destinés aux transactions par carte de paiement; type d'opération; date de transaction; montant de la transaction; monnaie de transaction; code d'autorisation du montant de la commission; détails de la carte de paiement.
C'est dommage, mais former un bordereau juridique avec les données que nous avons ne fonctionnera pas.Examinons à nouveau la documentation.On retrouve l'exemple que Sberbank livre dans le répertoire «examples» std::cout << "Authorization completion finished with code '" << result << "'" << std::endl; std::ofstream file(CHEQUE_FILENAME); file << argument.auth_answ.Check; file.close(); if (argument.auth_answ.Check) { std::cout << "Cheque saved to file " << CHEQUE_FILENAME << std::endl;
Il affiche simplement le texte situé sur le pointeur. Mais nous avons déjà vu que cela ne fonctionne pas comme ça ... Juste au cas où, nous allons compiler leur exemple et l'exécuter. Départ sur la ligne `file << argument.auth_answ.Check;`, eh bien, Sberbank, tenez la pierre n ° 11 pour les exemples qui ne fonctionnent pas.7 h Vous pouvez déjà écrire aux développeurs d'un autre wrapper qui a été écrit en Delphi il y a plusieurs années. Nous obtenons la réponse que tout fonctionne pour eux. Nous recherchons la base de leur wrapper et trouvons sur github : TAuthAnswer = packed record TType: integer; Amount: UINT;
Conversion de type simple en pointeur sans appel de fonction.Nous commençons à soupçonner les mauvais esprits.CHAPITRE 17. Un orage a éclaté
Les gens commencent à retourner au bureau, hochant la tête avec sympathie. PO n'a pas l'air très heureux de connaître les dernières nouvelles.Ici, je me souviens d'un détail. Lorsque nous avons affiché les champs de la structure # 14 pour voir leurs valeurs, un octet de chaque ligne a été coupé. D'une part, d'autre partAttention!Dans la structure auth_answer14, le nom du produit est un caractère plus court que dans gate.dll TGoodsData. Corrigez cette erreur en standard
Peut-être que c'est toujours lié à ... Uneterrible supposition surgit dans le cerveau comme un éclair. Déclarez la structure comme typedef struct __attribute__((packed)) { int TType; unsigned long Amount; char RCode[3]; char AMessage[16]; int CType; char* Check; };
Et ...Rien ne change.Still Size = 0, Still Lock = NULL.La douleurPourriture.Involontairement, vous cherchez avec vos yeux une poutre confortable au plafond, telle qu'elle puisse supporter le poids. Après tant d'heures ininterrompues de codage et d'étude de la documentation, de minces rangées d'octets flottent sous nos yeux. Mais que faire si nous imprimons des octets qui sont généralement renvoyés? u32 i; for (i = 0; i < sizeof(answ); i++) { printf("%02x ", *((u8 *)&answ + i)); } printf("\n"); C:\banks\sber\sb_pilot>sb_pilot.exe 1 1000 01 00 00 00 e8 03 00 00 30 00 00 ce e4 ee e1 f0 e5 ed ee 00 00 00 00 00 00 00 00 02 00 00 00 f8 6c 7a 00 00
`30 00 00 ce` - ce qui signifie que Sberbank utilise toujours des structures Packed. Mais il n'y a pas un mot à ce sujet dans les en-têtes. Par conséquent, les exemples ne fonctionnent pas, par conséquent, il est impossible d'obtenir un pointeur sur le texte à la fin - car il est cassé en raison d'un décalage de 1 octet. Pierre énorme et épineuse vers Sberbank!Et puis une nuance maaaalenky a attiré mon attention. 4 + 4 + 3 + 16 + 4 + 4 = 35. Et voici 36 octets, Obelix.S'il y a 36 octets, le compilateur aligne toujours la structure. Ainsi, entre RCode et AMessage, un octet supplémentaire est toujours inséré. Mais pourquoi? Après tout, nous avons indiqué «__packed__»!CHAPITRE 18. Le voyage de retour
Les raisons pour lesquelles l'alignement est toujours activé sont apparues en 2012: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 . Un bug n'a été corrigé que dans GCC 8 (une pierre pour 6 ans de buggy!), Qui n'est pas encore possible d'être mis à jour. Heureusement, il existe une solution: -mno-ms-bitfields
Nous n'analyserons pas maintenant le mécanisme de fonctionnement de ce drapeau, il suffit de le passer au compilateur:
Slip! Chéri! Tu m'as manqué, je ne jurerai même pas à cause du krakozyabry, j'ai déjà jeté une pierre pour ça.Et enfin, nous nourrissons la pierre de Microsoft du fait que GlobalSize / Lock donne des zéros sur des pointeurs invalides.CHAPITRE 19. Dernier chapitre
Pour minimiser le nombre d'ifdefs pour la couche sb_pilot, nous avons écrit une application distincte qui imite complètement la version linux de sb_pilot. Ainsi, en laissant le code de couche # 1 identique, en ne laissant qu'une seule condition: #if defined(BXI_OS_GLX) #define GFJ_PILOT_EXECUTABLE "./sb_pilot" #elif defined(BXI_OS_WIN) #define GFJ_PILOT_EXECUTABLE "./sb_pilot.exe" #endif
Résultats de la bataille:- Sberbank: 12 pierres
- Microsoft: 7 pierres
- GCC: 1 pierre
Réalisation-souvenir sur notre tableau de commande: