Sberbank ou lá e volta



CAPÍTULO 1. Convidados inesperados


Tudo começou naquela manhã infeliz, quando o Gerente do Projeto anunciou que os prazos de implementação do projeto deveriam ser rápida e decisivamente reduzidos em um mês. Mais precisamente, o projeto deve estar pronto em 4 dias. Não, nosso PO não é um animal, e nem parece uma coruja (talvez um pouco como um corvo), apenas aconteceu. Bem, se for necessário, é necessário, especialmente porque a equipe (e eu sou a desenvolvedora líder da equipe "C") recebeu algo saboroso. O relógio e o calendário eram quinta-feira, 11:00, na segunda-feira, o projeto deveria estar pronto.

Para começar, o que estamos fazendo? Estamos envolvidos na automação de cinemas - controle automático e remoto de equipamentos, automação de exibição de filmes, monitoramento, painéis de vídeo e agora também terminais para a venda de ingressos e bares. Especificamente, o último parágrafo é dedicado a este artigo.

O projeto em si, que tinha que ser concluído antes de segunda-feira, é um tipo de camada entre o servidor principal na Scala e o terminal de pagamento em ferro VeriFone VX 820 (na verdade, existem mais terminais, mas apenas como exemplo). É claro que ninguém nos dará transações dessa maneira, portanto, os utilitários e bibliotecas do Sberbank / Arcus e UCS são usados. Assim, o esquema de trabalho deve ser o seguinte:



Externamente, fica assim:



Além disso, esse subsistema deve ser usado em caixas registradoras padrão que todos viram em qualquer cinema nos caixas.

De acordo com a tradição interna, chamamos cada projeto de nossa equipe de um nome da mitologia nórdica antiga, para esse subsistema o nome Gefjon foi escolhido - o nome da deusa da fertilidade e fertilidade (um bom nome para um servidor de pagamento, não é? Bem, a lenda sobre os touros que cortam a ilha se encaixa idealmente na arquitetura atual, interromper o trabalho com equipamentos de uma linguagem de alto nível).

O formato das mensagens recebidas e enviadas é um servidor HTTP com uma carga JSON. Esse é o compromisso ideal entre o Scala, que é difícil de isolar dados binários dos fluxos de soquete e o C, que é difícil de transferir objetos pela rede. Não há muitas operações possíveis que precisam ser operadas: pagamento, cancelamento, devolução, vários tipos de relatórios, abertura do menu de serviço e ping. Parece nada complicado. Como existem três sistemas bancários (e é esperado um reabastecimento posterior da família), decidiu-se dividir o projeto em componentes:



Os blocos verdes são os que precisávamos fazer, os azuis são os que não podem ser alterados e são fornecidos pelo banco.

Como os principais problemas surgiram apenas com o software do Sberbank, o artigo como um todo será dedicado às armadilhas que contamos com nosso barco.

CAPÍTULO 2. Cordeiro assado



(foto: heaclub.ru)

... parece algo assim. O código desse protótipo, que foi escrito há vários meses para deixar claro para todas as pessoas de nível superior que podemos trabalhar com aplicativos bancários, parecia o mesmo.

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 

É claro que isso não era adequado para a versão de produção, por isso era essencialmente necessário escrever tudo de novo.

Cada banco que fornece bibliotecas para trabalhar com o terminal geralmente oferece duas opções de conexão: através das funções da biblioteca (.so / .dll) ou através de um utilitário pronto que precisa passar apenas dois valores - o tipo de operação e a quantidade (quando necessário). Em teoria, nada complicado, apenas algo

 char buffer[100]; sprintf(buffer, "%d %d", atoi(argv[1]), atoi(argv[2])); system(buffer); 

O resultado da operação será colocado no arquivo "e" e a verificação no arquivo "p". Envie esses arquivos para o stdout com conversão para JSON, para que o servidor HTTP os envie como carga útil sem pensar no que está lá.

Mas este artigo não teria sido publicado se tudo tivesse sido tão simples.

CAPÍTULO 4. Do outro lado da montanha e embaixo da montanha


A versão inicial da implementação foi uma chamada de aplicativo simples - o servidor HTTP chamou o wrapper necessário com parâmetros unificados (por exemplo, o relatório X é 4) e o utilitário, por exemplo, gfj_pilot, lançou sb_pilot com o parâmetro necessário para esta operação (por exemplo, o relatório X é 9) . Em seguida, o utilitário wrapper leu o resultado da operação no arquivo eletrônico (por exemplo, 2000 - "pagamento recusado, repita a operação") e o converteu em um erro universal (por exemplo 3 - "Erro ao ler ou processar o cartão / conta, repita a operação"). Depois disso, o arquivo "p" foi convertido em base64 para evitar a quebra da formatação e enviado junto com o resultado ao stdout como JSON.

Tudo isso funcionou perfeitamente até que, em um belo momento, fomos informados de que ...

... isso não funciona no Windows.



Bem, mais precisamente, o próprio Windows não tem problemas (exceto que o slip é gerado na codificação Cp-1251 e o console funciona no CP866). O arquivo "e" simplesmente não foi gerado. Lançou um utilitário bancário diretamente:

 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> 

De fato, não há arquivo "e". Pedra em direção a Sberbank # 1. Estamos escrevendo uma carta para o Sberbank (subsequentemente recebemos uma resposta que deveria ser assim) e, como não há tempo para correspondência e precisamos começar agora, estamos procurando soluções para obter o resultado.

 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 

Sim, o resultado pode ser obtido no log sbkernel.log. Inconvenientemente, além disso, não há hash de mapa para parafusar "obrigado" do Sberbank posteriormente. Não é bom

Você precisará se conectar à biblioteca pilot_nt.dll e importar funções dela. Tudo ficaria bem, mas ... Uma pedra para o Sberbank # 2: não existe essa biblioteca no Linux, você terá que criar dois aplicativos diferentes para plataformas diferentes - para linux, chame o utilitário sb_pilot (analogue para loadparm.exe, a propósito, pedra # 3 para um nome de utilitário diferente em plataformas diferentes) ), no Windows, conecte-se à biblioteca pilot_nt.dll.

CAPÍTULO 5. Enigmas no escuro


Às 19:00 horas.

O Sberbank é uma grande empresa, a maioria das soluções de software é produzida de acordo com GOSTs e documentos formais. Entramos no catálogo que o Sberbank fornece com as bibliotecas:

 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 

Muito bom, mas estamos interessados ​​apenas no diretório para desenvolvedores:

 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 

Um monte de desperdício de papel, por precaução, releia pilot_nt, com o qual aprendemos o seguinte:
Tabela 1. SO sb_pilot suportado.
OSCapacidadeNome do módulo
Windows32.sb_pilot.exe
Linux32.sb_pilot
Dos16sb_pilot.exe

Acontece que o utilitário para Windows ainda deve ser chamado de sb_pilot. Bem, uma pedra para o Sberbank # 4 pela inconsistência de sua própria documentação.
Transferência dos resultados do programa.

No final do programa, dois arquivos de texto são formados - o arquivo de troca e o arquivo de verificação.

O primeiro é nomeado e e destina-se a passar parâmetros da operação perfeita para o programa de chamada. A primeira linha deste arquivo contém o código do resultado da operação e um texto separado por vírgula explicando a mensagem de texto. Código 0 significa pagamento bem-sucedido, qualquer outro valor - recusa ou incapacidade de efetuar o pagamento.




Preguiçosamente jogamos mais uma pedra e começamos a estudar a documentação para conectar a biblioteca diretamente.
O procedimento para chamar funções de biblioteca

Ao pagar (devolver) compras com cartão de crédito, o programa de caixa deve chamar a função card_authorize () da biblioteca Sberbank preenchendo os campos TType e Amount e especificando valores zero nos campos restantes. No final da função, você precisa analisar o campo RCode. Se contiver o valor "0" ou "00", a autorização será considerada concluída com êxito, caso contrário será rejeitada. Além disso, você deve verificar o valor do campo Verificar.

Se não for NULL, deverá ser enviado para impressão (no modo não fiscal) e, em seguida,
exclua chamando a função GlobalFree (). Ao fechar um turno, o programa de caixa deve chamar a função close_day () da biblioteca Sberbank preenchendo o campo TType = 7 e especificando valores zero nos campos restantes. No final da função, verifique o valor do campo Verificar.

Se o campo Verificar não for NULL, ele deverá ser enviado para impressão (no modo não fiscal) e excluído chamando a função GlobaFree ().
Parece fácil, até um arquivo de cabeçalho é fornecido. Bem, conecte, compile e ...

 $ 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; /**< [in, out]                            .   . ::auth_answer */ ^ pilot_nt.h:544:3: error: unknown type name 'auth_answer' auth_answer ans; /**< [in, out]                            .   . ::auth_answer */ ^ pilot_nt.h:567:3: error: unknown type name 'auth_answer' auth_answer ans; /**< [in, out]                            .   . ::auth_answer */ ^ pilot_nt.h:590:3: error: unknown type name 'auth_answer' auth_answer ans; /**< [in, out]                            .   . ::auth_answer */ ^ pilot_nt.h:627:3: error: unknown type name 'auth_answer' auth_answer ans; /**< [in, out]                            .   . ::auth_answer */ ^ pilot_nt.h:668:3: error: unknown type name 'auth_answer' auth_answer ans; /**< [in, out]                            .   . ::auth_answer */ 

Ummm ... o que? Abra 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]     */ }; 

Imediatamente, sem olhar para a pedra para comentários em russo na codificação CP1251.

Bem, a pedra mais séria: queridos desenvolvedores de C ++. Se você escrever “C” externo, isso significa que o código dentro do bloco deve ser compilado pelo compilador C. Se você NÃO fez um `typedef` de uma estrutura, toda vez que você o menciona como referência de tipo, deve escrever a palavra-chave` struct`.

Corrija o arquivo para desenvolvedores, substituindo a palavra `struct` sempre que necessário. Link para a biblioteca `pilot_nt.dll`. Vitória não? Lançamos nosso aplicativo.

CAPÍTULO 6. Do Fogo às Chamas


Bem, você entendeu, certo? O aplicativo apenas trava. Até o principal. Medite, adicione o analógico NIH da função errno para Windows: GetLastError (pedra # 3 para a Microsoft, os dois primeiros são para codificações).

 C:\banks\sber\WIN>sb_pilot.exe 1 1000 E: !g_sblibrary (0xc0000096) 

0xc0000096? GetLastError não deve retornar um código de erro adequado?
Para obter uma lista completa dos códigos de erro fornecidos pelo sistema operacional, consulte Códigos de erro do sistema.
Sim, abra o artigo aqui :
Os tópicos a seguir fornecem listas de códigos de erro do sistema. Esses valores são definidos no arquivo de cabeçalho WinError.h.

  • Códigos de erro do sistema (0-499) (0x0-0x1f3)
  • Códigos de erro do sistema (500-999) (0x1f4-0x3e7)
  • Códigos de erro do sistema (1000-1299) (0x3e8-0x513)
  • Códigos de erro do sistema (1300-1699) (0x514-0x6a3)
  • Códigos de erro do sistema (1700-3999) (0x6a4-0xf9f)
  • Códigos de erro do sistema (4000-5999) (0xfa0-0x176f)
  • Códigos de erro do sistema (6000-8199) (0x1770-0x2007)
  • Códigos de erro do sistema (8200-8999) (0x2008-0x2327)
  • Códigos de erro do sistema (9000-11999) (0x2328-0x2edf)
  • Códigos de erro do sistema (12000-15999) (0x2ee0-0x3e7f)
Ok, obtivemos um erro não documentado, jogamos uma pedra e abrimos o onisciente google:


A essência do erro é que algumas sub-rotinas usam uma das instruções

  • _inp ()
  • _inpw ()
  • _inpd ()
  • _outp ()
  • _outpw ()
  • _outpd ()

O uso é proibido nos núcleos do NT, pois eles tentam trabalhar diretamente com a porta paralela. Aparentemente, esse código é chamado no inicializador da biblioteca, ou seja, a biblioteca na inicialização deseja pesquisar as portas em busca de dispositivos, mas o kernel do NT requer trabalho através do driver.

Situação sem esperança?

CAPÍTULO 8. Aranhas e moscas


22:00 Apenas no caso, surge a idéia de verificar se isso não se deve ao fato de usarmos a compilação cruzada com o Linux usando mingw. Ao mesmo tempo, entendemos que o Sberbank oferece apenas um aplicativo de 32 bits, por isso não funcionará com um aplicativo de 64 bits, tudo bem, mas ainda lançaremos uma pedra no Sberbank para a versão de 32 bits em 2019.

Dado : instalado no virtualbox windows 7;
Necessário : Instale o Visual Studio e copie o MVP.

Acessamos o site da Microsoft, baixamos o Visual Studio 2017. Tomamos a licença da comunidade, pois levamos para verificação, para os negócios, a licença será comprada se ela decolar.
Baixe algumas centenas de megabytes e ...

Vimos que nossa versão do sistema operacional (Windows 7) não é suportada.

Ok, vamos a todos os tipos de sites obscenos, estamos procurando pelo Visual Studio 2008, baixamos algumas centenas de megabytes novamente e ...

Nós obtemos o arquivo iso.

Ok, vamos tentar instalar o Daemon Tools 10 (já que esta é a versão que o site oferece) para inserir esse disco virtual.

Execute o binário baixado. Erro de ignição, requer o .NET Framework 4.5, faça o download, instale.
Iniciamos o binário baixado, a instalação começou, o carregador de inicialização diz que precisa do 4.5.2, faça o download, instale.
Iniciamos o binário baixado, a instalação foi iniciada, o carregador de inicialização diz que não irá a lugar nenhum até instalar a atualização de segurança KB3033929, baixar, instalar.

E recebemos um tapa na cara da Microsoft como uma mensagem:



Lançamos violentamente uma pedra muito afiada contra a Microsoft, baixamos as antigas Daemon Tools de torrents, descompactamos o Visual Studio com êxito, instalamos e finalmente (00:00) compilamos o MVP, obtemos o mesmo erro. Bem, havia uma boa versão, mas não cresceu junto.

CAPÍTULO 11. No limiar


Estamos escrevendo para o segundo programador, que neste momento conclui urgentemente o servidor e o procedimento de registro. Ele lembra que existe um repositório git que se conecta a essa biblioteca no NT e trabalha com ele.

Olhando desconfiado para o repositório, baixe-o, compile e execute-o. Isso funciona.



Observamos o código ainda mais desconfiado. O código é idêntico, exceto que está escrito em C ++ e não em C.
Entendemos que a linguagem não tem nada a ver com isso. Nós olhamos para as bibliotecas do Sberbank, que o código extrai.
Vemos o último commit.

E aqui estamos esperando por outra surpresa.

Acontece que as versões da biblioteca Sberbank podem ser diferentes. A última confirmação aumenta a versão de 23 para 27. Copie a versão do gita para o seu computador de teste - FUNCIONA!

Verificamos todos os arquivos enviados pelo Sberbank, comparamos as versões e construímos um tablet:
VersãoTrabalhos
26.0.15 - Básiconão
27.4.12 - Do repositóriosim
23.0.13 - Do repositóriosim
29.0.9 - As últimas de Satsim
23.0.13 - Com um patch para o sistema Cryptorsim

Ótimo, agora vamos curar. Nos sistemas em que custa 26, atualize para 29 ou 27 e tudo decolará.
Jogamos a pedra nº 9 em direção ao Sberbank por quebrar o comportamento nos sistemas NT.

CAPÍTULO 12. O que os esperava lá dentro


Arquivo e insuficiente? Não importa, pegamos títulos corrigidos, vinculamos dinamicamente à biblioteca para retornar corretamente um erro, escrevemos um código que simplesmente grava o código de retorno da função no arquivo "e", vamos chamar o biner sb_pilot.exe e ...

Para trabalhar, funciona.

Essa é apenas a versão do sistema "Cryptor" não cria um arquivo "p".

Tristemente, olhamos para o sangue pingando nas juntas e para o entalhe na parede.

Para iniciantes, qual é o sistema Cryptor.

A Cryptera é uma empresa dinamarquesa que produz equipamentos de criptografia / equipamentos de segurança / chaves etc. Acho que todos viram uma das cópias de seus produtos:



Portanto, o Sberbank usa seu módulo de criptografia para pinpads e libera uma biblioteca especial "corrigida", na qual, como já entendemos, o arquivo "p" não é criado. Escrevemos ao Sberbank sobre isso e em alguns dias obteremos a resposta de que "no sistema original, o arquivo" p "será criado, mas no patch do Cryptor não será". Nós daremos a eles a pedra nº 10 em alguns dias, porque você precisa trabalhar agora.

Felizmente ou infelizmente, as funções que usamos para conduzir operações retornam a estrutura já mencionada:

 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    */ }; 

Ah, tudo bem, a verificação já está lá, podemos salvá-la em um arquivo ou imprimi-la imediatamente em JSON ...

 printf("%s\n", answer.Check); 

E temos o aplicativo travando devido ao acesso a um ponteiro inválido.

CAPÍTULO 14. Fogo e água


4 da manhã Realizamos Seth Bandha Sarvangasana para nos acalmar e lemos atentamente o manual:
[out] imagem do cheque, deve ser liberada pela GlobalFree no programa de chamada
O que isso nos dá? Muito. Primeiro, como o ponteiro precisa ser limpo usando o GlobalFree, ele foi alocado no GlobalAlloc . Portanto, ele não emite um ponteiro para a memória, como na versão de 16 bits, mas um número de objeto com o tipo semanticamente declarado HGLOBAL, que pode ser alimentado na função GlobalSize para obter o tamanho do bloco alocado e o GlobalLock para bloquear uma parte da memória, mas obter o ponteiro original. By the way, pedra # 6 em direção à Microsoft para NIH malloc e grátis, que estão na biblioteca padrão.

 printf("%s\n", GlobalLock(answer.Check)); 

E ainda tem uma gota. Ok, o que o GlobalSize está mostrando? Zero? De alguma forma estranha.

Verificamos outras funções que também devem dar um deslize - vemos a mesma imagem.

Ocorre-me apenas que eu gerarei um deslize de acordo com os dados que a função de pagamento mais legal pode fornecer (sim, o Sberbank tem funções chamadas card_authorize2..14, não jogarei uma pedra para isso):

 struct auth_answer14 { auth_answer ans; /**< [in, out]   . . ::auth_answer */ char AuthCode[MAX_AUTHCODE]; /**< [out]  . 7 . */ char CardID[CARD_ID_LEN]; /**< [out]  . 25 .     ,   6   4,    '*'.*/ int ErrorCode; /**< [out]  . */ char TransDate[TRANSDATE_LEN]; /**< [out]     */ int TransNumber; /**< [out]    . , .    */ int SberOwnCard; /**< [out]     */ char Hash[CARD_HASH_LEN]; /**< [in, out]  SHA1   ,   ASCII     . 40 .*/ char Track3[CARD_TRACK3_LEN]; /**< [out]   */ DWORD RequestID; /**< [in,out]   .  PCI DSS .*/ DWORD Department; /**< [in]     0  14-, .      0xFFFFFFFF,          .        ,      4191. */ char RRN[MAX_REFNUM]; /**< [in,out]   ,  .    ,     .   12-  .       (   pilot_nt.dll),     –  (     ;     ,   ).*/ DWORD CurrencyCode; /**< [in]    (810, 643, 840, 978  ..) */ char CardEntryMode; /**< [out]    ('D'-., 'M'- , 'C'-, 'E'- EMV, 'R'- magstripe, 'F'-fallback)*/ char CardName[MAX_CARD_NAME_LEN]; /**< [out]    */ char AID[MAX_AID_ASCII_LEN]; /**< [out] Application ID   (   ASCIIZ-)*/ char FullErrorText[MAX_FULL_ERROR_TEXT]; /**< [out]     */ DWORD GoodsPrice; /**< [in]    ,  (34.99->3499)*/ DWORD GoodsVolume; /**< [in]  ,  .  (30.234->30234)*/ char GoodsCode[MAX_GOODS_CODE+1]; /**< [in]     .*/ char GoodsName[MAX_GOODS_NAME]; /**< [in]     . !   auth_answer14         gate.dll TGoodsData.     */ }; /** @brief     * @param[in] track2      .  NULL,     . * @param[in,out] auth_answer . ::auth_answer14 * @param[in,out] payinfo     * @return int  . */ PILOT_NT_API int card_authorize14( char *track2, struct auth_answer14 *auth_answer, struct payment_info_item *payinfo ); 

Estamos tentando selecionar os campos ... Descobrimos que apenas uma coisa nos separava da felicidade - Sobrenome e Nome do titular do cartão. Sem eles, um deslize não é considerado legal :
Detalhes: identificador de um caixa eletrônico, terminal eletrônico ou outro meio técnico destinado a transações com cartões de pagamento; tipo de operação; data da transação; valor da transação; moeda de transação; código de autorização do valor da comissão; detalhes do cartão de pagamento.
É uma pena, mas formar uma falha legal com os dados que temos não funcionará.

Vamos cavar na documentação novamente.

Encontramos o exemplo que o Sberbank fornece no diretório "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; //GlobaFree(argument.auth_answ.Check); } 

Simplesmente exibe o texto localizado no ponteiro. Mas já vimos que não funciona assim ... Só por precaução, vamos compilar o exemplo deles e executá-lo. Partida na linha `file << argument.auth_answ.Check;`, bem, Sberbank, segure a pedra # 11 para exemplos que não funcionam.

7h Você já pode escrever para os desenvolvedores de outro wrapper que foi escrito no Delphi há vários anos. Temos a resposta de que tudo funciona para eles. Estamos procurando a base de seu invólucro e encontramos no github :

 TAuthAnswer = packed record TType: integer; Amount: UINT; // IN     Rcode: array [0 .. 2] of AnsiChar; AMessage: array [0 .. 15] of AnsiChar; CType: integer; Check: PAnsiChar; end; Result := Func(nil, @FAuthAnswer); FLastError := Result; FCheque := PAnsiChar(FAuthAnswer.Check); 

Conversão simples de tipo para ponteiro sem nenhuma chamada de função.

Começamos a suspeitar de maus espíritos.

CAPÍTULO 17. A tempestade entrou em erupção


As pessoas começam a retornar ao escritório, balançando a cabeça com simpatia. O PO não parece muito feliz em saber as últimas notícias.

Aqui me lembro de um detalhe. Quando exibimos os campos da estrutura nº 14 para ver seus valores, um byte de cada linha foi cortado. Por um lado, por outro
Atenção!Na estrutura auth_answer14, o nome do produto é um caractere menor que no gate.dll TGoodsData. Corrija este erro como padrão
Talvez ainda esteja conectado com ... Um

palpite terrível surge no cérebro como um raio. Declare a estrutura como

 typedef struct __attribute__((packed)) { int TType; /**< [in]  .  ::OpetationTypes */ unsigned long Amount; /**< [in]    */ char RCode[3]; /**< [out]    */ char AMessage[16]; /**< [out]    */ int CType; /**< [in,out]   */ char* Check; /**< [out]  ,   GlobalFree    */ }; 

E ...

nada muda.

Tamanho estático = 0, Bloqueio estático = NULL.

Dor

Decadência.

Involuntariamente, você está procurando com seus olhos uma viga confortável no teto, de modo que ela possa suportar o peso. Depois de tantas horas sem parar de codificar e estudar a documentação, fileiras delgadas de bytes flutuam diante de nossos olhos. Mas e se imprimirmos bytes que geralmente são retornados?

  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» - o que significa que o Sberbank ainda utiliza estruturas compactadas. Mas não há uma palavra sobre isso nos cabeçalhos. Portanto, os exemplos não funcionam, portanto, é impossível obter um ponteiro para o texto no final - porque está quebrado devido a uma mudança de 1 byte. Pedra enorme e espinhosa em direção a Sberbank!

E então uma nuance maaaalenky chamou minha atenção. 4 + 4 + 3 + 16 + 4 + 4 = 35. E aqui estão 36 bytes, Obelix.

Se houver 36 bytes, o compilador ainda está alinhando a estrutura. Portanto, entre RCode e AMessage, um byte extra ainda é inserido. Mas porque? Afinal, indicamos `__packed__`!

CAPÍTULO 18. A jornada de volta


Os motivos pelos quais o alinhamento ainda está presente apareceram em 2012: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 . Um bug foi corrigido apenas no GCC 8 (uma pedra para 6 anos de buggy!), Que ainda não é possível ser atualizado. Felizmente, há uma solução alternativa:

 -mno-ms-bitfields 

Agora não vamos analisar o mecanismo de operação dessa flag, apenas passá-lo para o compilador:



Slip! Querida! Senti sua falta, nem vou jurar por causa do krakozyabry, já joguei uma pedra por isso.

E, finalmente, alimentamos a Microsoft com uma pedra, porque o GlobalSize / Lock dá zeros a ponteiros inválidos.

CAPÍTULO 19. Último capítulo


Para minimizar o número de ifdefs para a camada sb_pilot, escrevemos um aplicativo separado que imita completamente a versão linux do sb_pilot. Assim, deixando o código da camada # 1 o mesmo, deixando apenas uma condição:

 #if defined(BXI_OS_GLX) #define GFJ_PILOT_EXECUTABLE "./sb_pilot" #elif defined(BXI_OS_WIN) #define GFJ_PILOT_EXECUTABLE "./sb_pilot.exe" #endif 



Resultados da batalha:

  • Sberbank: 12 pedras
  • Microsoft: 7 pedras
  • GCC: 1 pedra

Recordação de conquistas em nosso painel de comando:

Source: https://habr.com/ru/post/pt451188/


All Articles