Commit 56f10e15 authored by bsarmiento31's avatar bsarmiento31
Browse files

Initial commit

parents
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Respuesta a petición de agregar item - (AddReceiptLinesResponse)
/// </summary>
public class AddReceiptLinesResponse : Respuesta
{
public int RequestID { get; set; }
private string _xml;
public AddReceiptLinesResponse(int sessionId,
TipoMensaje messageType,
int requestID) : base(sessionId, messageType)
{
RequestID = requestID;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:AddReceiptLinesResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<AddReceiptLinesResult>
<RequestID>{RequestID}</RequestID>
</AddReceiptLinesResult>
</schema:AddReceiptLinesResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
public class SignOffResponse : Respuesta
{
public string RequestID { get; set; }
private string _xml;
public SignOffResponse(int sessionId,
TipoMensaje messageType,
string requestID) : base(sessionId, messageType)
{
RequestID = requestID;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:SignOffResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<SignOffResult>
<RequestID>{RequestID}</RequestID>
</SignOffResult>
</schema:SignOffResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace gatewayGK.RespuestasXML.Suspend
{
public class SuspendTransactionResponse : Respuesta
{
public int RequestID { get; set; }
private string _xml;
public SuspendTransactionResponse(int sessionId,
TipoMensaje messageType,
int requestID) : base(sessionId, messageType)
{
RequestID = requestID;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:SuspendTransactionResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<SuspendTransactionResult>
<RequestID>{RequestID}</RequestID>
</SuspendTransactionResult>
</schema:SuspendTransactionResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML.Suspend
{
/// <summary>
/// Respuesta cuando no esta disponible el comando suspender
/// </summary>
public class SuspendTransactionResponseError : Respuesta
{
public int RequestID { get; set; }
public string Message { get; set; }
public string ErrorCode { get; set; }
private string _xml;
public SuspendTransactionResponseError(int sessionId,
TipoMensaje messageType,
int requestID,
string message,
string errorCode) : base(sessionId, messageType)
{
RequestID = requestID;
Message = message;
ErrorCode = errorCode;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:SuspendTransactionResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<SuspendTransactionResult>
<RequestID>{RequestID}</RequestID>
<ExceptionResult>
<Message>{Message}</Message>
<ErrorCode>{ErrorCode}</ErrorCode>
</ExceptionResult>
</SuspendTransactionResult>
</schema:SuspendTransactionResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML.TerminateLane
{
/// <summary>
/// Respuesta para cerrar la lane
/// </summary>
public class TerminateResponse : Respuesta
{
public bool RequestID { get; set; }
private string _xml;
public TerminateResponse(int sessionId,
TipoMensaje messageType,
bool requestID) : base(sessionId, messageType)
{
RequestID = requestID;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:TerminateResponse xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<TerminateResult>
<RequestID>{RequestID}</RequestID>
</TerminateResult>
</schema:TerminateResponse>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
/// <summary>
/// Respuesta a petición de agregar item - (TotalsEvent)
/// Tambien se usa para la segunda respuesta de finalizar y pagar
/// </summary>
public class TotalsEventResponse : Respuesta
{
public int RequestID { get; set; }
public string Key { get; set; }
public int Value { get; set; }
public string Total { get; set; }
public string SubTotal { get; set; }
public string Tax { get; set; }
public string BalanceDue { get; set; }
public string ChangeDue { get; set; }
public string FoodstampChangeDue { get; set; }
public string FoodstampTotal { get; set; }
public string FoodstampBalanceDue { get; set; }
public string CouponTotal { get; set; }
public int TotalItems { get; set; }
public int TotalCoupons { get; set; }
public string TotalSavings { get; set; }
public string TenderApplied { get; set; }
private string _xml;
public TotalsEventResponse(int sessionId,
TipoMensaje messageType,
int requestID,
string total,
string subTotal,
string tax,
string balanceDue,
string changeDue,
string foodstampChangeDue,
string foodstampTotal,
string foodstampBalanceDue,
string couponTotal,
int totalItems,
int totalCoupons,
string totalSavings,
string tenderApplied) : base(sessionId, messageType)
{
RequestID = requestID;
//Key = key;
//Value = value;
Total = total;
SubTotal = subTotal;
Tax = tax;
BalanceDue = balanceDue;
ChangeDue = changeDue;
FoodstampChangeDue = foodstampChangeDue;
FoodstampTotal = foodstampTotal;
FoodstampBalanceDue = foodstampBalanceDue;
CouponTotal = couponTotal;
TotalItems = totalItems;
TotalCoupons = totalCoupons;
TotalSavings = totalSavings;
TenderApplied = tenderApplied;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:TotalsEvent xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<RequestID>{RequestID}</RequestID>
<TransactionTotals>
<Total>{Total}</Total>
<SubTotal>{SubTotal}</SubTotal>
<Tax>{Tax}</Tax>
<BalanceDue>{BalanceDue}</BalanceDue>
<ChangeDue>{ChangeDue}</ChangeDue>
<FoodstampChangeDue>{FoodstampChangeDue}</FoodstampChangeDue>
<FoodstampTotal>{FoodstampTotal}</FoodstampTotal>
<FoodstampBalanceDue>{FoodstampBalanceDue}</FoodstampBalanceDue>
<CouponTotal>{CouponTotal}</CouponTotal>
<TotalItems>{TotalItems}</TotalItems>
<TotalCoupons>{TotalCoupons}</TotalCoupons>
<TotalSavings>{TotalSavings}</TotalSavings>
<TenderApplied>{TenderApplied}</TenderApplied>
</TransactionTotals>
</schema:TotalsEvent>
""";
}
}
}
using EvaPosSrvDTO;
using EvaPosSrvResp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EvaPOS_API_FRAME.RespuestasXML
{
public class TransactionStatusEvent : Respuesta
{
public int RequestID { get; set; }
public string Status { get; set; }
public int ID { get; set; }
public string Type { get; set; }
public string Category { get; set; }
public string Date { get; set; }
public string Time { get; set; }
private string _xml;
public TransactionStatusEvent(int sessionId,
TipoMensaje messageType,
int requestID,
string status,
int iD,
string type,
string category,
string date,
string time) : base(sessionId, messageType)
{
RequestID = requestID;
Status = status;
ID = iD;
Type = type;
Category = category;
Date = date;
Time = time;
}
public override string TextoXML
{
get => _xml = $"""
<?xml version="1.0" encoding="UTF-8"?>
<schema:TransactionStatusEvent xmlns:schema="http://bc.si.retail.ibm.com/POSBCSchema">
<RequestID>{RequestID}</RequestID>
<TransactionStatus>
<Status>{Status}</Status>
<ID>{ID}</ID>
<Type>{Type}</Type>
<Category>{Category}</Category>
<Date>{Date}</Date>
<Time>{Time}</Time>
</TransactionStatus>
</schema:TransactionStatusEvent>
""";
}
}
}
using EvaPosSrvResp;
namespace EvaPosSrvAplicacion
{
public interface IAplicacion
{
public Respuestas Procesar(IComando comando);
}
}
\ No newline at end of file
using EvaPosSrvResp;
using EvaPosSrvAplicacion;
namespace EvaPosSrvAplicacionImp
{
/// <summary>
/// Clase que ejecuta el comando y retorna respuesta.
/// </summary>
public class Aplicacion : IAplicacion
{
public Respuestas Procesar(IComando comando)
{
return comando.Ejecutar();
}
}
}
\ No newline at end of file
// Espacio de nombres que reune objetos DTO (data transfer object ) para comunicación con capa de presentación.
// Los DTO no incluyen lógica, solo datos (no son un remplazo de las Entidades - objetos del negocio.)
using System;
namespace EvaPosSrvDTO
{
/// <summary>
/// TipoMensaje encapsula identificadores permitidos de tipo de mensaje.
/// </summary>
public class TipoMensaje
{
/// <summary>
/// Valor del tipo de mensaje.
/// </summary>
//REVISAR: Creado para xml, set era private
public string Tipo { get; set; }
//REVISAR: Creado para xml constructor vacio
public TipoMensaje()
{
}
private TipoMensaje(string tipo)
{
Tipo = tipo;
}
/// <summary>
/// Mensajes tipo EVENT.
/// </summary>
//REVISAR: Creado para xml, set era private
public static TipoMensaje Event { get; set; } = new TipoMensaje("EVENT");
/// <summary>
/// Mensajes tipo REQest.
/// </summary>
//REVISAR: Creado para xml, set era private
public static TipoMensaje Req { get; set; } = new TipoMensaje("REQ");
/// <summary>
/// Mensajes tipo RESPonse.
/// </summary>
//REVISAR: Creado para xml, set era private
public static TipoMensaje Resp { get; set; } = new TipoMensaje("RESP");
/// <summary>
/// Retorna un tipo de mensaje correspondiente al parámetro.
/// </summary>
public static TipoMensaje FijaTipoMensaje(string tipo)
{
var t = tipo switch
{
"REQ" => TipoMensaje.Req,
"EVENT" => TipoMensaje.Event,
"RESP" => TipoMensaje.Resp,
_ => throw new Exception($"Tipo de mensaje no reconocido {tipo}")
};
return t;
}
}
/// <summary>
/// Clase base de DTO.
/// </summary>
public abstract class DTOBase
{
/// <summary>
/// Id de la sesión.
/// </summary>
//REVISAR: Creado para xml, set era private
public Int32 IdSesion { get; set; }
/// <summary>
/// Tipo del mensaje.
/// </summary>
//REVISAR: Creado para xml, set era private
public TipoMensaje TipoMensaje { get; set; }
/// <summary>
/// Constructor base.
/// </summary>
public DTOBase(int idSesion, TipoMensaje tipo)
{
IdSesion = idSesion;
TipoMensaje = tipo;
}
//REVISAR: Creado para xml, set era private, cosntructor vacio
public DTOBase()
{
}
}
// TODO - ARREGLAR EL MECANISMO DE TERMINACION DEL SERVIDOR.
/// <summary>
/// DTO con mensaje de terminación de conexión, para pruebas.
/// </summary>
public class FinDePruebaDTO : DTOBase
{
/// <summary>
/// Mensaje de terminación de conexión, para pruebas.
/// </summary>
public string MensajeFin { get; private set; } = "<|ACK|>";
public FinDePruebaDTO() : base(1, TipoMensaje.Event)
{}
}
// TODO - validar manejo de errores y su notificación a clientes que interactuan con el servidor.
/// <summary>
/// Retorna mensajes de error.
/// </summary>
public class ErrorDTO : DTOBase
{
/// <summary>
/// Texto del mensaje de error.
/// </summary>
public string Texto { get; private set; }
/// <summary>
/// Constructor con texto del mensaje de error.
/// </summary>
public ErrorDTO(int sessionId, TipoMensaje messageType, string texto) : base(sessionId, messageType)
{
Texto = texto;
}
/// <summary>
/// Representación del objeto en un string.
/// </summary>
public override string ToString()
{
return $"ErrorDTO - Texto: {Texto}";
}
}
/// <summary>
/// InitializeRequest DTO
/// </summary>
public class InitializeRequestDTO : DTOBase
{
/// <summary>
/// Campo OperatorID
/// </summary>
public string OperatorID { get; set; }
/// <summary>
/// Campo TerminalNumber
/// </summary>
public int TerminalNumber { get; set; }
/// <summary>
/// Campo Recovery
/// </summary>
public bool Recovery { get; set; }
public InitializeRequestDTO(int sessionId, TipoMensaje messageType) : base(sessionId, messageType) {}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Xml;
using EvaPosSrvDTO;
namespace EvaPosSrvResp
{
/// <summary>
/// Interface que define adaptadores para conversión de
/// mensaje XML en DTO.
/// </summary>
public interface IAdaptadorDTO
{
/// <summary>
/// Identifica el mensaje al cual el adaptador genera DTO.
/// Le referencia se usa por el servidor de mensajes para interpretar
/// el mensaje en un DTO a pasar a un comando.
/// La referencia corresponde a un string contenido en el mensaje
/// que permite identificar su tipo.
/// </summary>
public string Referencia { get; set; }
/// <summary>
/// Nombre del elemento xml con los datos para el DTO.
/// El nombre del elemento se presenta en formato xpath para ubicar el nodo desde el
/// elemento raiz del mensaje.
/// </summary>
public string NombreElementoXMLDatos { get; set; }
/// <summary>
/// Inicializar DTO adecuado al mensaje.
/// </summary>
public DTOBase ObtieneDTO(int idSesion, TipoMensaje tipoMensaje, XmlElement docXml);
/// <summary>
/// Retorna una "shallow copy" del objeto.
/// </summary>
public IAdaptadorDTO CreaCopia();
}
/// <summary>
/// Directorio de adaptadores DTO.
/// Almacena instancia de adaptadores indexadas por su Referencia.
/// Usado para identificar el adaptador que procesa un mensaje particular
/// asociado por la propiedad Referencia del adaptador.
/// </summary>
public class DirectorioAdaptadoresDTO
{
private Dictionary<string, IAdaptadorDTO> dir = new Dictionary<string, IAdaptadorDTO>();
/// <summary>
/// Agrega un adaptador al directorio.
/// </summary>
public void AgregaAdaptador(IAdaptadorDTO adp)
{
dir.Add(adp.Referencia, adp);
}
/// <summary>
/// Recupera una "shallow copy" del adaptador asociado al parámmetro.
/// </summary>
public IAdaptadorDTO ObtieneAdaptador(string referencia)
{
IAdaptadorDTO adp;
try {
adp = dir[referencia];
} catch (KeyNotFoundException)
{
throw new Exception($"No se encuentra adaptador con referencia '{referencia}'.");
}
return adp.CreaCopia();
}
}
/// <summary>
/// Trama, integra las partes que incluyen mensaje xml e información de control.
/// </summary>
public struct TramaSCO
{
/// <summary>
/// Longitud del mensaje.
/// Es la suma de la longitud de las cadenas de texto de la sección de encabezado
/// y de la sección te contenido xml.
/// </summary>
public UInt32 Longitud { get; set; }
/// <summary>
/// Id de la sesión.
/// </summary>
public Int32 IdSesion { get; set; }
/// <summary>
/// Tipo del mensaje, con valores predefinidos según el tipo de datos.
/// </summary>
public TipoMensaje TipoMensaje { get; set; }
/// <summary>
/// Texto del encabezado según los valores de tipo de mensaje e id de sesión.
/// </summary>
public string TextoEncabezado { get => $"soeps~Message-Type={TipoMensaje.Tipo}|Session-Id={IdSesion}|~"; }
/// <summary>
/// Texto del contenido xml del mensaje, con sus valores incluidos.
/// Debe ser inicializado por el que crea el objeto.
/// </summary>
public string TextoXML { get; set; }
/// <summary>
/// Objeto xml del mensaje, construido a partir del texto xml.
/// </summary>
public XmlDocument ContenidoXML
{
get
{
var doc = new XmlDocument();
doc.LoadXml(TextoXML);
return doc;
}
}
/// <summary>
/// Constructor genérico.
/// </summary>
public TramaSCO(UInt32 longitud, Int32 idSesion, TipoMensaje tipo, string textoXML)
{
Longitud = longitud;
IdSesion = idSesion;
TipoMensaje = tipo;
TextoXML = textoXML;
}
/// <summary>
/// Constructor usado para calcular Longitud del mensaje (encabezado + texto xml).
/// </summary>
public TramaSCO(Int32 idSesion, TipoMensaje tipo, string textoXML)
{
IdSesion = idSesion;
TipoMensaje = tipo;
TextoXML = textoXML;
Longitud = Convert.ToUInt32(TextoEncabezado.Length + TextoXML.Length);
}
}
/// <summary>
/// Definición mensajes respuesta para el SCO.
/// </summary>
public abstract class Respuesta
{
/// <summary>
/// String con el encabezado y contenido xml de mensajes de respuesta.
/// </summary>
public Int32 SessionId { get; private set; }
public TipoMensaje MessageType { get; private set; }
public abstract string TextoXML { get; }
public TramaSCO TramaSCO
{
get => new TramaSCO(SessionId, MessageType, TextoXML);
}
public Respuesta(int sessionId, TipoMensaje tipo)
{
SessionId = sessionId;
MessageType = tipo;
}
}
/// <summary>
/// Respuesta en modo texto genérico. Usada para pruebas básicas de comunicación.
/// </summary>
public class RespuestaGenerica : Respuesta
{
private string _mensaje;
/// <summary>
/// Constructor mensaje básico.
/// </summary>
public RespuestaGenerica(string mensaje) : base(1, TipoMensaje.Resp)
{
_mensaje = mensaje;
}
/// <summary>
/// Retorna mensaje.
/// </summary>
public override string TextoXML { get => _mensaje; }
}
/// <summary>
/// Colección de respuestas.
/// </summary>
public class Respuestas : List<Respuesta> { }
}
\ No newline at end of file
using System;
using System.Xml;
using EvaPOS_API_FRAME.Adaptadores;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using GatewaySCO;
// ****************************************************************
//
// ESPACIO DE NOMBRES PARA IMPLEMENTAR ADAPTADORES Y RESPUESTAS
//
// ****************************************************************
namespace EvaPosSrvRespImp
{
/// <summary>
/// Clase para inicializar el directorio de adaptadores de entrada.
/// Los adaptadores de entrada inicializan al dto asociada con el mensaje de entrada.
/// Para agregar un adaptador de entrada, modifique el método ' public static IniciaDirectorioAdaptadores Cargar()'
/// agregando la nueva instancia del objeto adaptador, usando el método 'AgregaAdaptador'. Ejemplo:
///
/// public static IniciaDirectorioAdaptadores Cargar()
/// {
/// return new IniciaDirectorioAdaptadores()
/// .AgregaAdaptador(new AdaptadorInitializeRequest())
/// .AgregarAdaptador(new AdaptadorQueryStatusRequest());
/// }
///
/// Agrega nueva instancia del AdaptadorQueryStatusRequest() al directorio de adaptadores.
/// </summary>
public class IniciaDirectorioAdaptadores
{
public DirectorioAdaptadoresDTO DirectorioAdaptadores {get; private set;} = new DirectorioAdaptadoresDTO();
public IniciaDirectorioAdaptadores AgregaAdaptador(IAdaptadorDTO adaptador)
{
DirectorioAdaptadores.AgregaAdaptador(adaptador);
return this;
}
public static IniciaDirectorioAdaptadores Cargar()
{
return new IniciaDirectorioAdaptadores()
.AgregaAdaptador(new AdaptadorInitializeRequest())
.AgregaAdaptador(new AdaptadorQueryStatusRequest())
.AgregaAdaptador(new AdaptadorReportStatusEventsRequest())
.AgregaAdaptador(new AdaptadorAddItemRequest())
.AgregaAdaptador(new AdaptadorSignOffRequest())
.AgregaAdaptador(new AdaptadorAddReceiptLinesRequest())
.AgregaAdaptador(new AdaptadorGetTotalsRequest())
.AgregaAdaptador(new AdaptadorAddTenderDebitRequest())
.AgregaAdaptador(new AdaptadorPrintCurrentReceipts())
.AgregaAdaptador(new AdaptadorReprintReceiptsRequest())
.AgregaAdaptador(new AdaptadorTerminateRequest())
.AgregaAdaptador(new AdaptadoresVoidTransactionRequest())
.AgregaAdaptador(new AdaptadorRemoveReceiptLines())
.AgregaAdaptador(new AdaptadorCancelAction())
.AgregaAdaptador(new AdaptadorAddCustomerBirthdateRequest())
.AgregaAdaptador(new AdaptadorAddCustomerRequest())
.AgregaAdaptador(new AdaptadorSuspendTransactionRequest());
}
}
}
#define TRACE
#define DEBUG
// EvaPos-API : servidor api, sockets y rest.
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Xml;
using Serilog;
using Serilog.Events;
using GatewaySCO;
using EvaPosSrvDTO;
using EvaPosSrvResp;
using EvaPosSrvAplicacion;
using EvaPOS_API_FRAME.RespuestasXML;
using EvaPOS_API_FRAME.Comandos;
namespace EvaPosSCOSrv
{
/// <summary>
/// Esta clase <c>ServidorSocket</c>activa servidor socktes.
/// Usa por default ip localhost 127.0.0.1 y puerto 11.0000
/// La operación del servidor es como sigue:
///
/// </summary>
public class ServidorSocket
{
ILogger log = Log.ForContext<ServidorSocket>();
readonly static bool _isDebug = Log.IsEnabled(LogEventLevel.Debug);
/// <summary>
/// Longitud máxima de mensaje de entrada.
/// </summary>
public const Int32 LongMaxMensaje = 1_024 * 8;
/// <summary>
/// Dirección ip para vincular el socket.
/// </summary>
public string Direccion { get; private set; } = "127.0.0.1";
/// <summary>
/// Puerto a vincular socket.
/// </summary>
public Int32 Puerto { get; private set; } = 11_000;
/// <summary>
/// Longitud de la cola de conexión al socket.
/// </summary>
public int LongColaConexiones { get; private set; } = 10;
/// <summary>
/// Fija timeout usado para enviar / recibir em modo sincrónico, en milisegundos.
/// Si el timeout se excede, se presenta una excepción.
/// Default a 5 segundos.
/// </summary>
public int TimeOutSincMs { get; private set; } = 3000;
long _numeroConexionesEntrantes = 0;
DirectorioAdaptadoresDTO _adaptadores;
CreaDirectorioCmds _comandos;
Sesion _sesion;
Presentacion _presentacion;
IAplicacion _aplicacion;
/// <summary>
/// Constructor servidor socket. Usa ip y puerto defualt.
/// El método IniciarAync() activa el servidor.
/// </summary>
public ServidorSocket() { }
/// <summary>
/// Constructor servidor socket. Usa ip y puerto defualt.
/// El método IniciarAync() activa el servidor.
/// <param name="direccion">Dirección IP servidor, típicamente '127.0.0.1'.</param>
/// <param name="puerto">Número de puerto para el socket, default 11.000</param>
/// <returns>Retorna tipo Task.</returns>
/// </summary>
public ServidorSocket(string direccion, Int32 puerto)
{
Direccion = direccion;
Puerto = puerto;
}
/// <summary>
/// Fija IP del servidor.
/// </summary>
public ServidorSocket ConIp(string direccion)
{
Direccion = direccion;
return this;
}
/// <summary>
/// Fija puerto del servidor.
/// </summary>
public ServidorSocket EnPuerto(int puerto)
{
Puerto = puerto;
return this;
}
/// <summary>
/// Fija directorio de adaptadores.
/// </summary>
public ServidorSocket AgregaDispensadorAdaptadores(DirectorioAdaptadoresDTO adaptadores)
{
_adaptadores = adaptadores;
return this;
}
/// <summary>
/// Fija directorio de comandos.
/// </summary>
public ServidorSocket AgregaDirectorioCmds(CreaDirectorioCmds comandos)
{
_comandos = comandos;
return this;
}
/// <summary>
/// Fija aplicación a procesar comandos.
/// </summary>
public ServidorSocket AgregaProcesadorAplicacion(IAplicacion aplicacion)
{
_aplicacion = aplicacion;
return this;
}
// TODO - este metodo no se puede usar en la invocación fluida, estudiar.
/// <summary>
/// Pone en marcha el servidor socket, vinculando a la IP y puertos asignados.
/// </summary>
public ServidorSocket Activa()
{
_sesion = new Sesion();
_presentacion = new Presentacion(_adaptadores, _comandos);
ActivaServidor();
return this;
}
/// <summary>
/// Activa el servidor en ip y puerto indicado en los parámetros globales.
/// <returns>Retorna tipo Task.</returns>
/// </summary>
private void ActivaServidor()
{
IPEndPoint ipEndPoint = new(IPAddress.Parse(Direccion), Puerto);
int cont = 0;
// Clave usar 'using' para liberar correctamente recursos.
using Socket tcpSocket = new(
ipEndPoint.AddressFamily,
SocketType.Stream,
ProtocolType.Tcp);
// Configuración comportamiento socket tcp.
// No se permite a otro socket compartir el puerto.
// TODO - Investigar: cuando servidor y cliente están en la misma instanción de s.o.
// no comparten el puerto?.
//tcpSocket.ExclusiveAddressUse = true;
// El socket espera los segundos del parémetro para terminar de enviar
// datos (si hay en el buffer de salida), despues que se llama Socket.Close.
tcpSocket.LingerState = new LingerOption(true, 3);
// Desactiva algoritmo Nagle.
tcpSocket.NoDelay = true;
// Timeout entrada / salida.
tcpSocket.ReceiveTimeout = 0;
tcpSocket.SendTimeout = TimeOutSincMs;
//tcpSocket.Blocking = false;
tcpSocket.Bind(ipEndPoint);
tcpSocket.Listen(LongColaConexiones);
// Inicializa objetos de procesamiento de los mensajes de comunicación.
Sesion sesion = new Sesion();
Presentacion presentacion = new Presentacion(_adaptadores, _comandos);
// Id proceso. Compilación difiere según .net usado.
int id = -1;
#if NETFRAMEWORK
id = Process.GetCurrentProcess().Id;
#elif (NETSTANDARD || NET5_0_OR_GREATER)
id = Environment.ProcessId;
#endif
log.Information("EvaPOS servidor socket en {ip} : {puerto}, proceso id {id}", Direccion, Puerto, id);
if (_isDebug)
{
log.Debug("Versión .Net {version}", Environment.Version);
log.Debug("Longitud máxima mensaje aceptado bytes {long}", LongMaxMensaje);
Log.Debug("Tcp Socket configuración:");
Log.Debug($" Blocking {tcpSocket.Blocking}");
Log.Debug($" Version eva 1.3");
Log.Debug($" ExclusiveAddressUse {tcpSocket.ExclusiveAddressUse}");
Log.Debug($" LingerState {tcpSocket.LingerState.Enabled}, {tcpSocket.LingerState.LingerTime}");
Log.Debug($" NoDelay {tcpSocket.NoDelay}");
Log.Debug($" ReceiveBufferSize {tcpSocket.ReceiveBufferSize}");
Log.Debug($" ReceiveTimeout {tcpSocket.ReceiveTimeout}");
Log.Debug($" SendBufferSize {tcpSocket.SendBufferSize}");
Log.Debug($" SendTimeout {tcpSocket.SendTimeout}");
Log.Debug($" Ttl {tcpSocket.Ttl}");
Log.Debug($" IsBound {tcpSocket.IsBound}");
}
// TODO - El socket temporal debe liberar recursos?
// Socket entrada.
Socket socketEntrada;
try
{
bool continuar = true;
while (continuar)
{
log.Information("Esperando conexión...");
socketEntrada = tcpSocket.Accept();
continuar = ProcesaConexion(socketEntrada, _numeroConexionesEntrantes++);
while (!continuar)
{
tcpSocket.Close();
IPEndPoint ipEndPoint2 = new(IPAddress.Parse(Direccion), Puerto);
using Socket tcpSocket2 = new(
ipEndPoint2.AddressFamily,
SocketType.Stream,
ProtocolType.Tcp);
tcpSocket2.LingerState = new LingerOption(true, 3);
tcpSocket2.NoDelay = true;
tcpSocket2.ReceiveTimeout = 0;
tcpSocket2.SendTimeout = TimeOutSincMs;
tcpSocket2.Bind(ipEndPoint);
tcpSocket2.Listen(LongColaConexiones);
Sesion sesion2 = new Sesion();
Presentacion presentacion2 = new Presentacion(_adaptadores, _comandos);
socketEntrada = tcpSocket2.Accept();
continuar = ProcesaConexion(socketEntrada, _numeroConexionesEntrantes++);
}
}
if (tcpSocket.Connected)
tcpSocket.Shutdown(SocketShutdown.Both);
log.Information("Conexion Cerrada");
}
finally
{
tcpSocket.Close();
}
}
/// <summary>
/// Retorna longitud del mensaje representada en los 4 primeros bytes del arreglo bytes de argumento.
/// </summary>
private uint LongitudMensaje(byte[] arregloBytes)
{
// Extrae longitud. Primeros 4 bytes del arreglo.
byte[] bytesConLongMensaje = new byte[4];
Buffer.BlockCopy(arregloBytes, 0, bytesConLongMensaje, 0, 4);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytesConLongMensaje);
var longitud = BitConverter.ToUInt32(bytesConLongMensaje, 0);
return longitud;
}
/// <summary>
/// Procesa socket entrada de conexión.
/// </summary>
private bool ProcesaConexion(Socket socket, long nroConexion)
{
bool result = true;
Respuestas respuestas = null;
int contIngreso = 0;
if (_isDebug)
{
Log.Debug("Conexión remota #{nro} ip {ip} en puerto {pto}",
nroConexion,
IPAddress.Parse(((IPEndPoint)socket.RemoteEndPoint).Address.ToString()),
((IPEndPoint)socket.RemoteEndPoint).Port.ToString());
contIngreso++;
}
while (true)
{
try
{
// Lee mensajes hasta que llegue mensaje de terminación o socket cerrado por el cliente.
// Lee longitud mensaje entrante, 4 bytes.
Log.Debug("Esperando mensaje...");
var bufferLongitud = new byte[4];
int bytesLeidos = 0;
while (bytesLeidos < 4)
{
bytesLeidos += socket.Receive(bufferLongitud, bytesLeidos, bufferLongitud.Length, SocketFlags.None);
}
// 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...");
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);
// 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);
//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)
{
Log.Debug("Respuesta del pinpad del datafono");
var bufferSalida = _sesion.Salida(respuesta.TramaSCO);
socket.Send(bufferSalida, 0, bufferSalida.Length, SocketFlags.None);
Log.Information("Bytes enviados {bytes}", bufferSalida.Length);
}
}
respuestas = _aplicacion.Procesar(cmd);
Log.Debug("Respuestas de cmd ref '{cmd}' : {nroRespuestas}", cmd.Referencia, respuestas.Count);
// Enviando respuestas.
int i = 1;
foreach (var respuesta in respuestas)
{
Log.Debug("Respuesta #{i}", i++);
var bufferSalida = _sesion.Salida(respuesta.TramaSCO);
socket.Send(bufferSalida, 0, bufferSalida.Length, SocketFlags.None);
Log.Information("Bytes enviados {bytes}", bufferSalida.Length);
}
log.Information("Fin del ciclo, se enviaron {nro} respuestas", respuestas.Count);
if (cmd.Referencia == "scsns:Terminate")
{
result = false;
break;
}
}
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.");
}
else
{
// Otra excepción de SocketException
log.Error("Error de Socket: {error}", ex);
}
result = false;
break;
}
catch (Exception e )
{
log.Error("Error : {error}" , e);
result = false;
break;
}
}
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;
}
}
/// <summary>
/// Clase que controla el nivel básico de entrada y salida de mensajes.
/// Extrae la información del mensaje, limpiando la información de control.
/// </summary>
class Sesion
{
static ILogger log = Log.ForContext<Sesion>();
readonly static bool _isDebug = Log.IsEnabled(LogEventLevel.Debug);
/// <summary>
/// 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)
{
if (_isDebug)
{
log.Debug("Buffer entrada: >>{subBuffer}<<", buffer);
}
TramaSCO trama = new TramaSCO();
trama.Longitud = Convert.ToUInt32(nroBytes);
log.Debug("Longitud mensaje: {long}", trama.Longitud);
// Extrae encabezado. String con patron soeps~<texto>~
string datos = Encoding.UTF8.GetString(buffer, 0, nroBytes);
int inicioXML = datos.IndexOf("<");
string parteEncabezado = datos.Substring(0, inicioXML);
log.Debug("Encabezado: {encabezado}", parteEncabezado);
// Extrae valor campo Session-Id en string.
int inicioSessionId = parteEncabezado.IndexOf("Session-Id");
int finSessionId = parteEncabezado.Substring(inicioSessionId).IndexOf("|");
string sessionId = parteEncabezado.Substring(inicioSessionId, finSessionId);
string valorSessionId = sessionId.Split('=')[1];
trama.IdSesion = Int32.Parse(valorSessionId);
// Extrae valor campo Message-Type en string.
int inicioMessageType = parteEncabezado.IndexOf("Message-Type");
int finMessageType = parteEncabezado.Substring(inicioMessageType).IndexOf("|");
string messageType = parteEncabezado.Substring(inicioMessageType, finMessageType);
string valorMessageType = messageType.Split('=')[1];
trama.TipoMensaje = TipoMensaje.FijaTipoMensaje(valorMessageType);
// Extraer contenido.
trama.TextoXML = datos.Substring(inicioXML);
log.Debug("Mensaje string con contenido: {contenido}", trama.TextoXML);
return trama;
}
/// <summary>
/// Empaca string de entrada en arreglo de bytes.
/// Agrega encabeado con longitud del mensaje (4 bytes big-endian).
/// 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)
{
// Codifica longitud del mensaje en los 4 primeros bytes.
byte[] bytesConLongMensaje = BitConverter.GetBytes(trama.Longitud);
// Bytes mas significativos deben ir primero, usa 'big-endian'.
if (BitConverter.IsLittleEndian)
Array.Reverse(bytesConLongMensaje);
Log.Debug("Longitud mensaje: {long} - bytes con longitud: {bytes}", trama.Longitud, bytesConLongMensaje);
// Codifica en bytes texto del mensaje.
byte[] bytesConMensaje = Encoding.UTF8.GetBytes(trama.TextoEncabezado + trama.TextoXML);
Log.Debug("Texto mensaje: '{texto}'", trama.TextoEncabezado + trama.TextoXML);
// 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);
if (_isDebug) log.Debug("Buffer salida: >>{bytes}<<", bytes);
return bytes;
}
}
/// <summary>
/// Clase que interpreta los mensajes y los transforma en objetos DTO.
/// </summary>
class Presentacion
{
DirectorioAdaptadoresDTO _adaptadores;
CreaDirectorioCmds _comandos;
public Presentacion(DirectorioAdaptadoresDTO adaptadores, CreaDirectorioCmds comandos)
{
_comandos = comandos;
_adaptadores = adaptadores;
}
/// <summary>
/// Retorna DTO con los datos del mensaje.
/// </summary>
public IComando Entrada(TramaSCO mensaje)
{
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.");
Log.Debug("Mensaje, contenido xml: '{nodoInicial}'", Util.ContenidoXmlComoString(nodoRaiz));
// Según el elemento XML raíz, se determina el comando y dto adecuados.
// Se puede considerar incluir en el comando pasar los datos de mensaje
// directamente, sin embargo, esto no se hace así: el comando solo debe
// depender de los datos pasados en una DTO. De esta manera, el comando
// es independizado del formato del mensaje.
// Al adaptador se encarga de tranformar los datos del mensaje en
// valores inicializados en el DTO.
cmd = _comandos.ObtieneComando(nodoRaiz.Name);
IAdaptadorDTO adaptador = _adaptadores.ObtieneAdaptador(nodoRaiz.Name);
DTOBase dto = adaptador.ObtieneDTO(mensaje.IdSesion, mensaje.TipoMensaje, docXml);
cmd.CargaDTO(dto);
}
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}'");
cmd = new ErrorCmd();
cmd.CargaDTO(dto);
}
return cmd;
}
}
}
{
"GatewayConfig": {
"POS": "gk_test",
"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",
"Language": "es",
"Language_comment": "Language code as needed by the POS application"
},
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
"Args": { }
},
{
"Name": "File",
"Args": {
"path": "logs/scogateway-log.txt",
"rollingInterval": "Day"
}
}
]
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Copiar el appsetting.json al directorio de salida de compilación. -->
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="RestSharp" Version="110.2.0" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
</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