Commit b7dc25ca authored by Jose Hugo Torres's avatar Jose Hugo Torres
Browse files

Configuracion para POSBC

Adición de conexión cliente hacia POSBC - modo de prueba transferencia directa mensaje CHEC a POSBC y de retorno.
parent 11b7efd5
......@@ -5,6 +5,10 @@ VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gatewayGK", "gatewayGK\gatewayGK.csproj", "{3E81A0D0-DF98-4680-89D2-B1FA801A42E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gatewayPruebaECO", "gatewayPruebaECO\gatewayPruebaECO.csproj", "{AE8C6130-DC0D-41EA-9F61-88DAC3D4A712}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gatewayPruebaECO_POSBC", "gatewayPruebaECO_POSBC\gatewayPruebaECO_POSBC.csproj", "{4A774AA8-BB85-4836-A011-D99952744E2B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -15,6 +19,14 @@ Global
{3E81A0D0-DF98-4680-89D2-B1FA801A42E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E81A0D0-DF98-4680-89D2-B1FA801A42E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E81A0D0-DF98-4680-89D2-B1FA801A42E2}.Release|Any CPU.Build.0 = Release|Any CPU
{AE8C6130-DC0D-41EA-9F61-88DAC3D4A712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE8C6130-DC0D-41EA-9F61-88DAC3D4A712}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE8C6130-DC0D-41EA-9F61-88DAC3D4A712}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE8C6130-DC0D-41EA-9F61-88DAC3D4A712}.Release|Any CPU.Build.0 = Release|Any CPU
{4A774AA8-BB85-4836-A011-D99952744E2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A774AA8-BB85-4836-A011-D99952744E2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A774AA8-BB85-4836-A011-D99952744E2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A774AA8-BB85-4836-A011-D99952744E2B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
......@@ -16,13 +16,25 @@ La aplicación espera el archivo de configuación con los parámetros de operaci
### Parámetros de configuración
En la siguiente lista se presentan los parámetros de configuración definidos. En la siguiente lista, los parámetros y sus valores.
En la siguiente lista se presentan los parámetros de configuración definidos.
- Indicar tipos de POS, según el tipo se activan los comandos adecuados.
- 'POS' valores: 'pruebas', 'gk', 'evapos'.
- Ubicación del SCO.
- 'IpSCO': string, dirección IP del SCO.
- 'PortSCO': string, puerto IP del SCO.
En la siguiente lista, los parámetros y sus valores.
Sección **GatewayConfig**:
- **POS** - Indica el tipo de POS al que se conecta el gateway. Según el tipo de POS se activan los comandos adecuados. Valores aceptados:
- 'ECO': usado para pruebas, retorna el mismo mensaje que recibe.
- 'ECO-POSBC': usado para pruebas, remite el mensaje que recibe a POSBC de pruebas y retorna respuesta sin cambios.
- 'POSBC': conecta con Toshiba POSBC.
- 'EVAPOS': conecta con pos EvaPOS.
- 'GK': conecta a pos GK Ommichannel.
- **IpSCO** string, dirección IP del SCO.
- **PortSCO** string, puerto IP del SCO.
Sección **POSBC**:
- **Ip**: IP del servidor POSBC.
- **Port**: Puerto del servidor POSBC.
### Programación de la configuración
......@@ -45,7 +57,6 @@ La siguiente es la secuencia de pasos que sigue el programa para procesar una so
- Según el tipo de mensaje se obtiene el comando adecuado y su adaptador. El factory de creación del directorio de comandos se encargga de inicializar los comandos adecuados al tipo de POS.
- Se ejecuta el comando.
## Agregar soporte a una nueva POS
Para soportar un nuevo tipo de POS, los pasos son:
......
......@@ -4,13 +4,13 @@
/// </summary>
public sealed class Config
{
public required string POS { get; set; } = "pruebas";
public required string IpSCO { get; set; } = "127.0.0.1";
public required int PortSCO { get; set; } = 6697;
public required string POS { get; set; } = "ECO";
public required string IpGateway { get; set; } = "127.0.0.1";
public required int PortGateway { get; set; } = 6697;
public required string Language { get; set; } = "en";
public override string ToString()
{
return base.ToString() + " - " + $"POS: '{POS}', IpSCO: '{IpSCO}', PortSCO: '{PortSCO}', Language: '{Language}'";
return base.ToString() + " - " + $"POS: '{POS}', IpGateway: '{IpGateway}', PortGateway: '{PortGateway}', Language: '{Language}'";
}
}
\ No newline at end of file
/// <summary>
/// Clase que representa los parámetros de configuración de la aplicación
/// registrados en el archivo appsettings.json asociados a POSBC.
/// </summary>
public sealed class ConfigPOSBC
{
public required string IpPOSBC { get; set; } = "127.0.0.1";
public required int PortPOSBC { get; set; } = 6698;
public override string ToString()
{
return base.ToString() + $" - IpPOSBC: '{IpPOSBC}', PortPOSBC: '{PortPOSBC}' ";
}
}
\ No newline at end of file
......@@ -8,18 +8,16 @@ public class DirectorioCmdsFactory
{
public static CreaDirectorioCmds CreaDirectorio(string tipoPOS)
{
switch (tipoPOS)
return tipoPOS switch
{
case "pruebas":
return DispensaDirectorioCmdsPruebas.Dispensa();
case "evapos":
return DispensaDirectorioCmdsEvaPOS.Dispensa();
case "gk":
return DispensaDirectorioCmdsGK.Dispensa();
case "gk_test":
return DispensaDirectorioCmdsGKPruebas.Dispensa();
default:
throw new ArgumentException("TipoPOS no válido", "tipoPOS");
}
"ECO" => DispensaDirectorioCmdsPruebas.Dispensa(),
//"ECO_POSBC" => throw new NotImplementedException(),
"ECO_POSBC" => DispensaDirectorioCmdsGKPruebas.Dispensa(),
"POSBC" => throw new NotImplementedException(),
"evapos" => DispensaDirectorioCmdsEvaPOS.Dispensa(),
"gk" => DispensaDirectorioCmdsGK.Dispensa(),
"gk_test" => DispensaDirectorioCmdsGKPruebas.Dispensa(),
_ => throw new ArgumentException("Valor no reconocido en archivo configuración, parámetro 'POS', valor encontrado {pos}.", tipoPOS),
};
}
}
......@@ -14,7 +14,7 @@ public class DispensaDirectorioCmdsGKPruebas : IDispensaDirectorioCmds
/// </summary>
public static CreaDirectorioCmds Dispensa()
{
Log.Information("Instancia comandos de GK de prueba.");
Log.Information("Instancia comandos de GK/ECO_POSBC de prueba.");
return new IniciaDirectorioCmds()
.AgregaCmd(new Gk.InitializeRequestCmd())
......
......@@ -10,3 +10,17 @@
- Manejar errores servicios web, ejemplo:
{"errorCode":{"errorCode":"GKR-POS-000001","message":"Invalid session","messageKey":"com.gk_software.pos.utils.error.ErrorCodeMessages.MSG_INVALID_SESSION","arguments":[]},"timestamp":"2023-08-04T07:16:57.066","additionalContextInfoMap":{}}
- El manejo de headers, la cookie no es obligatoria, para pasar la sessión este es obligatorio: request.AddHeader("_pos_session_", sessionId);
## 16 junio 24
- Cambios en archivo de configuración para facilitar pruebas con Toshiba POSBC.
- Target framework del proyecto cambiado a net8.0 (gatewayGK.csproj).
- public class DirectorioCmdsFactory modificada para aceptar nuevos tipos de POS en archivo de configuración.
- Creado proyecto getewayPruebaECO, emula un CHEC que interactura con el gateway
- Se agrega referencia del proyecto gateway al proyencto pruebasECO:
PS C:\jht\ApiGatewayCHEC\api-gateway-chec\gatewayPruebaECO> dotnet add ./gatewayPruebaECO.csproj reference ../gatewayGK/gatewayGK.csproj
Se ha agregado la referencia "..\gatewayGK\gatewayGK.csproj" al proyecto.
- Creado proyecto gatewayECO_POSBC, simulando servidor POSBC que retorna mensajes iguales a los que les llegan.
- Se agrega referencia del proyecto gateway el proyecto PruebaECO_POSBC:
PS C:\jht\ApiGatewayCHEC\api-gateway-chec\gatewayPruebaECO_POSBC> dotnet add ./gatewayPruebaECO_POSBC.csproj reference ../gatewayGK/gatewayGK.csproj
Se ha agregado la referencia "..\gatewayGK\gatewayGK.csproj" al proyecto.
using System.Net;
using System.Net.Sockets;
using System.Text;
using EvaPosSCOSrv;
using Serilog;
namespace gatewaySCO.POSBC;
// Maneja conexión al POSBC.
public class ClienteServidorPOSBC(string ip, int pto)
{
ILogger log = Log.ForContext<ClienteServidorPOSBC>();
int _pto = pto;
string _ip = ip;
Socket _socket = null;
public bool ConexionActiva { get; private set; } = false;
int _contadorMensajesEnviados = 0;
int _contadorMensajesRecibidos = 0;
public void AbreConexion()
{
_socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// Conectarse al servidor
try
{
_socket.Connect(new IPEndPoint(IPAddress.Parse(_ip), _pto));
ConexionActiva = true;
Log.Information("Conectado a POSBC {ip}:{pto} - {conectado}", _ip, _pto, _socket.Connected);
}
catch (SocketException ex)
{
Log.Error("Excepción socket conexión a POSBC {ip}:{pto} - código error socket {codigo} - {e}", _ip, ex.SocketErrorCode, _pto, ex);
}
catch (Exception e)
{
Log.Error("Excepción en conexión a POSBC {ip}:{pto} - {e}", _ip, _pto, e);
}
}
public void CierraConexion()
{
// Cerrar el socket
ConexionActiva = false;
try
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
catch (Exception e)
{
Log.Warning("Excepción cerrando conexión a POSBC {ip}:{pto} - {e}", _ip, _pto, e);
}
Log.Information("Conexión cerrada a POSBC {ip}:{pto} - {conectado}", _ip, _pto, _socket.Connected);
}
// Convierte el string del parámetro a un arreglo de bytes,
// anteponiendo 4 bytes con la longitud del mensaje.
public static byte[] ConvertirEnBytes(string mensaje)
{
// Codifica longitud del mensaje en un entero sin signo en los 4 primeros bytes.
var longitud = Convert.ToUInt32(mensaje.Length);
byte[] bytesConLongMensaje = BitConverter.GetBytes(longitud);
// Bytes mas significativos deben ir primero, usa 'big-endian'.
if (BitConverter.IsLittleEndian)
Array.Reverse(bytesConLongMensaje);
// Codifica en bytes texto del mensaje.
byte[] bytesConMensaje = Encoding.UTF8.GetBytes(mensaje);
// Copia los 2 arreglos de bytes en un arreglo unificado.
byte[] bytes = new byte[bytesConLongMensaje.Length + bytesConMensaje.Length];
Buffer.BlockCopy(bytesConLongMensaje, 0, bytes, 0, bytesConLongMensaje.Length);
Buffer.BlockCopy(bytesConMensaje, 0, bytes, bytesConLongMensaje.Length, bytesConMensaje.Length);
return bytes;
}
// Envia mensaje, espera respuesta y la retorna como un arreglo de bytes.
// El mensaje es un string, convertido a arreglo de bytes, y le antepone
// 4 bytes con la longitud del mensaje.
// TODO : validar que la respuesta sean mas de 1 mensaje!!!
public byte[] EnviaRecibe(byte[] bytesMensaje)
{
// Remite mensaje.
_socket.Send(bytesMensaje);
_contadorMensajesEnviados++;
Log.Information("Mensaje #{contador} para POSBIC {ip} - {msj}", _contadorMensajesEnviados, _ip, bytesMensaje.ToString);
Log.Information("Esperando respuesta..");
// Leer la respuesta del servidor
byte[] buffer = new byte[1024]; // Tamaño del buffer inicial
int totalBytesRead = 0;
int bytesRead;
byte[] bufferEntrada;
// Usar un MemoryStream para manejar datos de longitud arbitraria
using (var ms = new System.IO.MemoryStream(1024))
{
while ((bytesRead = _socket.Receive(buffer)) > 0)
{
ms.Write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// Romper el ciclo si se ha leído todo el mensaje
if (_socket.Available == 0) break;
}
// Obtener todos los datos recibidos como un arreglo de bytes
bufferEntrada = ms.ToArray();
}
_contadorMensajesRecibidos++;
Log.Debug("Respuesta #{contador} de POSBC {ip} - {msj}", _contadorMensajesRecibidos, _ip, bufferEntrada.ToString);
return bufferEntrada;
}
}
\ No newline at end of file
using System.Net.Sockets;
namespace gatewaySCO.POSBC;
/// <summary>
/// Esta clase almacena valores requeridos
/// para la operación del gateway conectado a POSBC.
/// </summary>
public class EntornoPOSBC
{
public int PortPOSBC { get; set; } = 6697;
public string IpPOSBC { get; set; } = "127.0.0.1";
/// <summary>
/// Socket en uso de conexión al POSBC.
/// </summary>
public ClienteServidorPOSBC ClientePOSBC { get; set; }
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration;
using EvaPosSCOSrv;
using EvaPosSrvAplicacionImp;
using EvaPosSrvRespImp;
using gatewaySCO.POSBC;
namespace GatewaySCO
{
......@@ -14,11 +15,12 @@ namespace GatewaySCO
{
static void Main(string[] args)
{
Program program = new Program();
program.ActivaServidor(args);
Console.WriteLine("*** Gateway SCO - Servidor: procesa peticiones de Toshiba CHEC ***");
Program program = new();
LeeActivaConfiguracion(args);
}
public Config LeeConfiguracion(string[] args)
public static void LeeActivaConfiguracion(string[] args)
{
// TODO - opción de incluir la activación en la cadena de configuración.
......@@ -41,6 +43,7 @@ namespace GatewaySCO
Config config = configBuilder.GetRequiredSection("GatewayConfig").Get<Config>()
?? throw new ApplicationException("Archivo de configuración sin sección 'GatewayConfig'.");
Log.Information(config.ToString());
Entorno<Config>.Instancia.set(config);
// Lee del archivo de configuración sección POS Gk si aplica.
// TODO - valor de parámetro POS de archivo de configuración en ENUM?
......@@ -55,22 +58,41 @@ namespace GatewaySCO
Entorno<EntornoGK>.Instancia.get().ConfigGk = configGk;
Log.Information($"GK {Entorno<EntornoGK>.Instancia.get().UrlBase}");
}
return config;
// Configuración para pruebas de emulación del POSBC.
if (config.POS == "ECO_POSBC")
{
ConfigPOSBC configPOSBC = configBuilder.GetRequiredSection("POSBC").Get<ConfigPOSBC>()
?? throw new ApplicationException("Archivo de configuración sin sección 'POSBC'.");
Log.Information(configPOSBC.ToString());
Entorno<EntornoPOSBC>.Instancia.set(new EntornoPOSBC());
Entorno<EntornoPOSBC>.Instancia.get().IpPOSBC = configPOSBC.IpPOSBC;
Entorno<EntornoPOSBC>.Instancia.get().PortPOSBC = configPOSBC.PortPOSBC;
// Se activa lógica de cliente POSBC
// La conexión con el POSBC se almacena en el entorno para uso
// posterior en la emisión y recepción de mensajes.
Entorno<EntornoPOSBC>.Instancia.get().ClientePOSBC = new ClienteServidorPOSBC(configPOSBC.IpPOSBC, configPOSBC.PortPOSBC);
Entorno<EntornoPOSBC>.Instancia.get().ClientePOSBC.AbreConexion();
if (Entorno<EntornoPOSBC>.Instancia.get().ClientePOSBC.ConexionActiva == false)
{
throw new ApplicationException("Error en conexión al POSBC.");
}
}
// Activa servidor Gateway.
// En este punto, el programa se bloquea esperando conexión y mensajes desde CHEC.
ActivaServidor(config.IpGateway, config.PortGateway, config.POS);
}
public void ActivaServidor(string[] args)
// Servidor sockets: acepta peticiones de SCO CHEC.
public static void ActivaServidor(string ip, int pto, string tipoPOS)
{
Console.WriteLine("*** Gateway SCO ***");
Config config = LeeConfiguracion(args);
try
{
Console.WriteLine("*** Gateway SCO ***");
// TODO - aunque hace uso de una "invocación fluida" no parece ser el tipo de api que se beneficia de ese esquema.
ServidorSocket servidor = new ServidorSocket()
.ConIp(config.IpSCO)
.EnPuerto(config.PortSCO)
.ConIp(ip)
.EnPuerto(pto)
.AgregaDispensadorAdaptadores(IniciaDirectorioAdaptadores.Cargar().DirectorioAdaptadores)
.AgregaDirectorioCmds(DirectorioCmdsFactory.CreaDirectorio(config.POS))
.AgregaDirectorioCmds(DirectorioCmdsFactory.CreaDirectorio(tipoPOS))
.AgregaProcesadorAplicacion(new Aplicacion())
.Activa();
}
......
using System;
using System.Collections.Generic;
using System.Xml;
using EvaPosSrvDTO;
......
......@@ -11,8 +11,9 @@ using GatewaySCO;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using EvaPosSrvAplicacion;
using EvaPOS_API_FRAME.RespuestasXML;
using EvaPOS_API_FRAME.Comandos;
using gatewaySCO.POSBC;
namespace EvaPosSCOSrv
{
/// <summary>
......@@ -28,7 +29,7 @@ namespace EvaPosSCOSrv
/// <summary>
/// Longitud máxima de mensaje de entrada.
/// </summary>
public const Int32 LongMaxMensaje = 1_024 * 8;
public const Int32 LongMaxMensaje = 1_024 * 32;
/// <summary>
/// Dirección ip para vincular el socket.
/// </summary>
......@@ -204,6 +205,10 @@ namespace EvaPosSCOSrv
socketEntrada = tcpSocket.Accept();
continuar = ProcesaConexion(socketEntrada, _numeroConexionesEntrantes++);
// TODO - Validar lo siguiente.
// Manejo de situación en la cual CHEC mantiene abierta la conexión pero
// remite un comando TERMINATE, con lo cual, hay que reciclar la conexión:
// se cierra el socket y se abre nuevamente.
while (!continuar)
{
tcpSocket.Close();
......@@ -215,7 +220,6 @@ namespace EvaPosSCOSrv
SocketType.Stream,
ProtocolType.Tcp);
tcpSocket2.LingerState = new LingerOption(true, 3);
tcpSocket2.NoDelay = true;
tcpSocket2.ReceiveTimeout = 0;
......@@ -241,17 +245,27 @@ namespace EvaPosSCOSrv
}
/// <summary>
/// Retorna longitud del mensaje representada en los 4 primeros bytes del arreglo bytes de argumento.
/// Longitud del mensaje, retorna entero representado en 4 bytes, sin signo.
/// </summary>
private uint LongitudMensaje(byte[] arregloBytes)
public static uint LongitudMensaje(byte[] bytesMensaje)
{
// Extrae longitud. Primeros 4 bytes del arreglo.
byte[] bytesConLongMensaje = new byte[4];
Buffer.BlockCopy(arregloBytes, 0, bytesConLongMensaje, 0, 4);
byte[] bytesLongitudMensaje = new byte[4];
Buffer.BlockCopy(bytesMensaje, 0, bytesLongitudMensaje, 0, 4);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytesConLongMensaje);
var longitud = BitConverter.ToUInt32(bytesConLongMensaje, 0);
return longitud;
Array.Reverse(bytesLongitudMensaje);
return BitConverter.ToUInt32(bytesLongitudMensaje, 0);
}
/// <summary>
/// Concatena dos arreglos de bytes en un tercero que retorna como respuesta.
/// </summary>
public static byte[] ConcatenateArrays(byte[] array1, byte[] array2)
{
byte[] result = new byte[array1.Length + array2.Length];
Buffer.BlockCopy(array1, 0, result, 0, array1.Length);
Buffer.BlockCopy(array2, 0, result, array1.Length, array2.Length);
return result;
}
/// <summary>
......@@ -259,7 +273,6 @@ namespace EvaPosSCOSrv
/// </summary>
private bool ProcesaConexion(Socket socket, long nroConexion)
{
bool result = true;
Respuestas respuestas = null;
int contIngreso = 0;
if (_isDebug)
......@@ -270,7 +283,8 @@ namespace EvaPosSCOSrv
((IPEndPoint)socket.RemoteEndPoint).Port.ToString());
contIngreso++;
}
while (true)
bool continua = true;
while (continua)
{
try
{
......@@ -284,94 +298,94 @@ namespace EvaPosSCOSrv
bytesLeidos += socket.Receive(bufferLongitud, bytesLeidos, bufferLongitud.Length, SocketFlags.None);
}
Log.Debug("Arriba un mensaje.");
// Lee porción de datos del mensaje, hasta la longitud indicada en los 4 primeros bytes.
var longitudMensaje = LongitudMensaje(bufferLongitud);
Log.Debug("Longitud mensaje {long} - buffer longitud mensaje >>{buffer}<<", longitudMensaje, bufferLongitud);
if (longitudMensaje > LongMaxMensaje) throw new Exception($"Mensaje recibido de {longitudMensaje} bytes supera máximo permitido de {LongMaxMensaje} bytes.");
Log.Debug("Leyendo bytes con mensaje...");
uint longitudMensaje = LongitudMensaje(bufferLongitud);
if (longitudMensaje > LongMaxMensaje) throw new Exception($"Mensaje {longitudMensaje} bytes supera máximo permitido de {LongMaxMensaje} bytes.");
var bufferEntrada = new byte[longitudMensaje];
bytesLeidos = 0;
while (bytesLeidos < longitudMensaje)
{
bytesLeidos += socket.Receive(bufferEntrada, bytesLeidos, bufferEntrada.Length, SocketFlags.None);
}
Log.Information("Bytes recibidos {bytes}", bytesLeidos);
Log.Information("Nuevo mensaje {bytes} bytes.", bytesLeidos);
// Procesando entrada: se obtiene mensaje, con el cual se
// identifica comando que lo procesa, se ejecuta el comando
// y se retornarn respuestas al cliente que ha emitido el mensaje.
TramaSCO msj = _sesion.Entrada(bufferEntrada, bytesLeidos);
IComando cmd = _presentacion.Entrada(msj);
string tipoPOS = Entorno<Config>.Instancia.get().POS;
//Enviamos la respuesta del pinpad primero del datafono antes de que entre al comando procesar
if (cmd.Referencia == "scsns:AddTender")
{
Respuestas respuestaDatafono = EjecutarProcesarDatafonoDirecto();
foreach (var respuesta in respuestaDatafono)
// -------------------------------------------------------------------------------
// Procesa mensajes desde CHEC según la modalida de configuración del Gateway
// -------------------------------------------------------------------------------
// --- Caso de pruebas mdalidad ECO: lo que recibe,
// lo transmite a POSBC y retorna a CHEC sus respuestas sin cambios.
if (tipoPOS == "ECO_POSBC")
{
Log.Debug("Respuesta del pinpad del datafono");
var bufferSalida = _sesion.Salida(respuesta.TramaSCO);
// Modo "past-throught": lo que entra se remite sin cambios al POSBC y
// su respeusta se remite sin cambios a CHEC.
byte[] mensajeEntrada = ConcatenateArrays(bufferLongitud, bufferEntrada);
// Enviar mensaje de entrada a POSBC y retornar respuestas.
byte[] bufferSalida = Entorno<EntornoPOSBC>.Instancia.get().ClientePOSBC.EnviaRecibe(mensajeEntrada);
// Remitir respuestas sin cambio a CHEC.
socket.Send(bufferSalida, 0, bufferSalida.Length, SocketFlags.None);
Log.Information("Bytes enviados {bytes}", bufferSalida.Length);
}
Log.Information("Respuesta remitida: {bytes}", bufferSalida);
// Procesando entrada: se obtiene mensaje, con el cual se
// identifica comando que lo procesa.
TramaSCO msj = Sesion.Entrada(bufferEntrada, bytesLeidos);
IComando cmd = _presentacion.Entrada(msj);
if (cmd.Referencia == "scsns:Terminate")
{
continua = false;
}
respuestas = _aplicacion.Procesar(cmd);
Log.Debug("Respuestas de cmd ref '{cmd}' : {nroRespuestas}", cmd.Referencia, respuestas.Count);
}
else
{
// --- Caso de operación normal, convierte mensajes en comandos para procesamiento.
// Enviando respuestas.
// Procesando entrada: se obtiene mensaje, con el cual se
// identifica comando que lo procesa, se ejecuta el comando
// y se retornarn respuestas al cliente que ha emitido el mensaje.
TramaSCO msj = Sesion.Entrada(bufferEntrada, bytesLeidos);
IComando cmd = _presentacion.Entrada(msj);
respuestas = _aplicacion.Procesar(cmd);
Log.Information("Comando '{cmd}'", cmd.Referencia);
int i = 1;
foreach (var respuesta in respuestas)
{
Log.Debug("Respuesta #{i}", i++);
var bufferSalida = _sesion.Salida(respuesta.TramaSCO);
// Enviando respuestas.
var bufferSalida = Sesion.Salida(respuesta.TramaSCO);
socket.Send(bufferSalida, 0, bufferSalida.Length, SocketFlags.None);
Log.Information("Bytes enviados {bytes}", bufferSalida.Length);
Log.Information("Respuesta {i}/{total} remitida, {bytes} bytes", i, respuestas.Count, bufferSalida.Length);
}
log.Information("Fin del ciclo, se enviaron {nro} respuestas", respuestas.Count);
if (cmd.Referencia == "scsns:Terminate")
{
result = false;
break;
continua = false;
}
}
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.ConnectionAborted)
{
// La conexión fue anulada por el software en el equipo remoto
log.Warning("La conexión fue anulada por el software en el equipo remoto.");
log.Warning("Conexión abortada por el equipo remoto.");
}
else
{
// Otra excepción de SocketException
log.Error("Error de Socket: {error}", ex);
}
result = false;
break;
continua = false;
}
catch (Exception e )
catch (Exception e)
{
log.Error("Error : {error}" , e);
result = false;
break;
log.Error("Error : {error}", e);
continua = false;
}
}
return result;
}
//Metodo de respuesta para el pinpad del datafono
public Respuestas EjecutarProcesarDatafonoDirecto()
{
Respuestas respuestaPinPad = null;
POSBCStatusEventPinPadStatus posbcEventWaitScreenPaidPad = null;
posbcEventWaitScreenPaidPad = new POSBCStatusEventPinPadStatus(1, TipoMensaje.Event, "INFO", "WAITING_FOR_PINPAD_INPUT", "Siga las instrucciones del Datafono");
respuestaPinPad = new Respuestas { posbcEventWaitScreenPaidPad };
return respuestaPinPad;
return continua;
}
}
......@@ -388,16 +402,17 @@ namespace EvaPosSCOSrv
/// Interpreta arreglo de bytes en mensaje, extrayendo del arreglo de bytes
/// y copiando el contenido en un objeto tipo Trama.
/// </summary>
public TramaSCO Entrada(byte[] buffer, int nroBytes)
public static TramaSCO Entrada(byte[] buffer, int nroBytes)
{
if (_isDebug)
{
log.Debug("Buffer entrada: >>{subBuffer}<<", buffer);
}
TramaSCO trama = new TramaSCO();
trama.Longitud = Convert.ToUInt32(nroBytes);
log.Debug("Longitud mensaje: {long}", trama.Longitud);
TramaSCO trama = new()
{
Longitud = Convert.ToUInt32(nroBytes)
};
// Extrae encabezado. String con patron soeps~<texto>~
string datos = Encoding.UTF8.GetString(buffer, 0, nroBytes);
......@@ -407,7 +422,7 @@ namespace EvaPosSCOSrv
// Extrae valor campo Session-Id en string.
int inicioSessionId = parteEncabezado.IndexOf("Session-Id");
int finSessionId = parteEncabezado.Substring(inicioSessionId).IndexOf("|");
int finSessionId = parteEncabezado[inicioSessionId..].IndexOf("|");
string sessionId = parteEncabezado.Substring(inicioSessionId, finSessionId);
string valorSessionId = sessionId.Split('=')[1];
trama.IdSesion = Int32.Parse(valorSessionId);
......@@ -421,7 +436,7 @@ namespace EvaPosSCOSrv
// Extraer contenido.
trama.TextoXML = datos.Substring(inicioXML);
log.Debug("Mensaje string con contenido: {contenido}", trama.TextoXML);
log.Information("{contenido}", trama.TextoXML);
return trama;
}
......@@ -431,7 +446,7 @@ namespace EvaPosSCOSrv
/// No interpreta string de mensaje.
/// <returns>Arreglo bytes con mensaje y cabecera con su longitud en bytes. Mensaje codificado UTF-8.</returns>
/// </summary>
public byte[] Salida(TramaSCO trama)
public static byte[] Salida(TramaSCO trama)
{
// Codifica longitud del mensaje en los 4 primeros bytes.
byte[] bytesConLongMensaje = BitConverter.GetBytes(trama.Longitud);
......@@ -447,6 +462,9 @@ namespace EvaPosSCOSrv
Buffer.BlockCopy(bytesConLongMensaje, 0, bytes, 0, bytesConLongMensaje.Length);
Buffer.BlockCopy(bytesConMensaje, 0, bytes, bytesConLongMensaje.Length, bytesConMensaje.Length);
if (_isDebug) log.Debug("Buffer salida: >>{bytes}<<", bytes);
Log.Information("Mensaje {long} bytes", trama.Longitud);
Log.Information(".. encabezado\n{encabezado}", trama.TextoEncabezado);
Log.Information(".. contenido\n{contenido}", trama.TextoXML);
return bytes;
}
}
......@@ -472,12 +490,8 @@ namespace EvaPosSCOSrv
IComando cmd;
try
{
XmlElement? docXml = mensaje.ContenidoXML.DocumentElement;
if (docXml == null)
throw new Exception("Contenido XML vacío.");
XmlNode? nodoRaiz = docXml.SelectSingleNode(".");
if (nodoRaiz == null)
throw new Exception("Contenido XML vacío.");
XmlElement? docXml = mensaje.ContenidoXML.DocumentElement ?? throw new Exception("Contenido XML vacío.");
XmlNode? nodoRaiz = docXml.SelectSingleNode(".") ?? throw new Exception("Contenido XML vacío.");
Log.Debug("Mensaje, contenido xml: '{nodoInicial}'", Util.ContenidoXmlComoString(nodoRaiz));
// Según el elemento XML raíz, se determina el comando y dto adecuados.
......@@ -496,7 +510,7 @@ namespace EvaPosSCOSrv
catch (Exception e)
{
Log.Error("Excepción procesando Mensaje XML: {e}", e.Message);
ErrorDTO dto = new ErrorDTO(mensaje.IdSesion, mensaje.TipoMensaje, $"Mensaje XML con valor nulo o no reconocido: '{e.Message}'");
ErrorDTO dto = new(mensaje.IdSesion, mensaje.TipoMensaje, $"Mensaje XML con valor nulo o no reconocido: '{e.Message}'");
cmd = new ErrorCmd();
cmd.CargaDTO(dto);
}
......
{
"GatewayConfig": {
"POS": "gk",
"POS": "ECO_POSBC",
"POS_comment": "Indicates the set of commands to instantiate, according to the type of POS: evapos, tests, gk, etc.",
"IpSCO": "127.0.0.1",
"IpSCO_comment": "SCO IP, local or remote",
"PortSCO": 6697,
"PortSCO_comment": "SCO IP Port",
"IpGateway": "127.0.0.1",
"IpGateway_comment": "Gateway IP, local or remote",
"PortGateway": 6697,
"PortGateway_comment": "Gateway IP Port",
"Language": "es",
"Language_comment": "Language code as needed by the POS application"
},
"POSBC": {
"IpPOSBC": "127.0.0.1",
"PortPOSBC": 6698
},
"DataGK": {
"IpGkSmartPOS": "10.10.117.10",
"IpGkSmartPOS_comment": "SCO IP, local or remote",
......@@ -29,7 +33,7 @@
"Serilog.Sinks.Console",
"Serilog.Sinks.File"
],
"MinimumLevel": "Verbose",
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
......
......@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WarningLevel>1</WarningLevel>
......
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace gatewayPruebaECO;
// Cliente de pruebas del gateway, remite mensajes, espera respuesta.
public class SocketClientECO
{
private const int Port = 6697; // Puerto del servidor gateway
private const string Server = "127.0.0.1"; // Dirección IP del servidor
// Crear un socket
static Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
static int numeroRespuestas = 0;
public static void Main()
{
try
{
// Conectarse al servidor
socket.Connect(new IPEndPoint(IPAddress.Parse(Server), Port));
Console.WriteLine("Conectado al servidor.");
string mensaje1 = """
soeps~Message-Type=REQ|Session-Id=400|~<?xml version="1.0" encoding="UTF-8"?><scsns:Initialize xmlns:scsns="http://bc.si.retail.ibm.com/POSBCSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://bc.si.retail.ibm.com/POSBCSchema C:\PosBc\POSBCSchema_main.xsd">
<InitializeRequest>
<OperatorID>NO_DEFAULT</OperatorID>
<TerminalNumber>400</TerminalNumber>
<Recovery>false</Recovery>
</InitializeRequest>
</scsns:Initialize>
""";
Mensaje(mensaje1);
Respuesta();
string mensaje2 = """
soeps~Message-Type=REQ|Session-Id=400|~<?xml version="1.0" encoding="UTF-8"?><scsns:Initialize xmlns:scsns="http://bc.si.retail.ibm.com/POSBCSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://bc.si.retail.ibm.com/POSBCSchema C:\PosBc\POSBCSchema_main.xsd">
<InitializeRequest>
<OperatorID>NO_DEFAULT</OperatorID>
<TerminalNumber>800</TerminalNumber>
<Recovery>false</Recovery>
</InitializeRequest>
</scsns:Initialize>
""";
Mensaje(mensaje2);
Respuesta();
string mensaje3 = """
soeps~Message-Type=REQ|Session-Id=400|~<?xml version="1.0" encoding="UTF-8"?><scsns:Initialize xmlns:scsns="http://bc.si.retail.ibm.com/POSBCSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://bc.si.retail.ibm.com/POSBCSchema C:\PosBc\POSBCSchema_main.xsd">
<InitializeRequest>
<OperatorID>NO_DEFAULT</OperatorID>
<TerminalNumber>900</TerminalNumber>
<Recovery>false</Recovery>
</InitializeRequest>
</scsns:Initialize>
""";
Mensaje(mensaje3);
Respuesta();
// Cerrar el socket
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
catch (SocketException e)
{
Console.WriteLine($"Error de socket: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
public static void Mensaje(string mensaje)
{
// Enviar un mensaje al servidor
socket.Send(Salida(mensaje));
Console.WriteLine($"Mensaje #1 enviado: {mensaje}");
}
public static void Respuesta()
{
// Recibir la respuesta del servidor
byte[] buffer = new byte[4096];
int bytesRead = socket.Receive(buffer);
numeroRespuestas++;
string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Respuesta #{numeroRespuestas} recibida: {response}");
}
public static byte[] Salida(string trama)
{
// Codifica longitud del mensaje en los 4 primeros bytes.
var longitud = Convert.ToUInt32(trama.Length);
byte[] bytesConLongMensaje = BitConverter.GetBytes(longitud);
// Bytes mas significativos deben ir primero, usa 'big-endian'.
if (BitConverter.IsLittleEndian)
Array.Reverse(bytesConLongMensaje);
// Codifica en bytes texto del mensaje.
byte[] bytesConMensaje = Encoding.UTF8.GetBytes(trama);
// Copia los 2 arreglos de bytes en un arreglo unificado.
byte[] bytes = new byte[bytesConLongMensaje.Length + bytesConMensaje.Length];
Buffer.BlockCopy(bytesConLongMensaje, 0, bytes, 0, bytesConLongMensaje.Length);
Buffer.BlockCopy(bytesConMensaje, 0, bytes, bytesConLongMensaje.Length, bytesConMensaje.Length);
return bytes;
}
}
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\gatewayGK\gatewayGK.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace gatewayPruebasECO_POSBC;
// Activa servidor para pruebas, acepta mensajes que retorna identicos.
public class SocketServer
{
private const int Port = 6698; // Puerto en el que el servidor escuchará
public static void Main()
{
Socket? listener = null;
try
{
// Crear un socket de escucha
listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Any, Port));
listener.Listen(10);
Console.WriteLine($"Servidor ECO_POSBC escuchando en el puerto {Port}...");
// Aceptar una conexión de cliente
using Socket clientSocket = listener.Accept();
Console.WriteLine("Cliente conectado.");
while (true)
{
Console.WriteLine("..esperando mensaje.");
// Recibir el mensaje del cliente
byte[] buffer = new byte[4096];
int bytesRead = clientSocket.Receive(buffer);
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Mensaje recibido: {message}");
// Enviar el mismo mensaje de vuelta al cliente
clientSocket.Send(buffer, bytesRead, SocketFlags.None);
Console.WriteLine("Mensaje enviado de vuelta al cliente.");
}
}
catch (SocketException e)
{
Console.WriteLine($"Error de socket: {e.Message}");
}
finally
{
// Cerrar el socket de escucha
listener?.Close();
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\gatewayGK\gatewayGK.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment