English Русский Deutsch 日本語 Português
preview
Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD (Parte 4)

Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD (Parte 4)

MetaTrader 5Integración | 27 marzo 2024, 16:53
159 0
Jocimar Lopes
Jocimar Lopes

"Los objetos de software deben estar abiertos a la ampliación, pero cerrados a la modificación" (principio abierto-cerrado de la programación orientada a objetos).

Introducción

En primer lugar, hagamos un breve resumen. En la primera parte de esta serie, empezamos a escribir pruebas sencillas para una función capaz de generar un encabezado fijo del paquete de control MQTT. Empezamos con el primer bit del primer byte.

En la segunda parte, introdujimos algunas funciones y definiciones comunes en dos archivos de encabezado.

En la tercera parte, empezamos a leer las banderas de confirmación CONNACK y los códigos de motivo de conexión. Este fue nuestro primer encuentro con la sección de comportamiento operativo del Estándar OASIS. 

Hasta este momento, todo era estático y estaba vinculado a un intento de conexión. Si el primer byte era incorrecto, el usuario recibía una respuesta de error del bróker y podía intentar reconectarse con un paquete correctamente formado. 

Las propiedades son otra historia aparte. Son atributos dinámicos de MQTT Application Message y pueden cambiar después de la conexión. El valor máximo de QoS del bróker puede cambiar temporalmente por razones operativas. El Receive Maximum puede haber cambiado debido a cuellos de botella en la red. Además, algunas propiedades son dinámicas por diseño, como Content-Type, todas las propiedades Will y la propiedad User. Estas propiedades cambiarán de forma constante.

El estándar OASIS resulta bastante inequívoco en sus especificaciones, pero los desarrolladores de clientes tienen muchas opciones para decidir cómo leer, reaccionar, almacenar y actualizar las propiedades actuales. Además, la capa de persistencia (persistence layer) para la gestión de sesiones es enteramente responsabilidad del desarrollador del cliente. Tendremos que implementar una capa de persistencia para gestionar las propiedades entre sesiones de la forma correspondiente. La selección del algoritmo en este caso será fundamental para la coherencia, la fiabilidad y el rendimiento.

Los comentarios de la sección "Cómo leemos las propiedades de MQTT v5.0 en MQL5" pueden resultar útiles como documentación informal para aquellos que vayan a mantener la biblioteca. Algunos usuarios finales de la biblioteca también podrán beneficiarse de estos comentarios. En esta sección, analizaremos las propiedades desde la perspectiva de un desarrollador de bibliotecas. Veremos sus tipos de datos, sus identificadores y la ubicación en el array de bytes para describir mejor cómo los leemos. A continuación analizaremos las propiedades desde la perspectiva de un usuario de biblioteca. Vamos a intentar describir su semántica en cada uno de sus posibles usos.

Nota: Salvo que se indique lo contrario, todas las citas procederán del Estándar OASIS


¿Qué son las propiedades en MQTT v5.0?

Las propiedades forman parte de los "mecanismos de extensibilidad" añadidos en MQTT v5.0. No estaban presentes en la versión anterior 3.1.1, que fue la última versión antes de esta gran actualización, sin embargo están omnipresentes en MQTT v5.0. Pero, ¿cuáles son las propiedades de MQTT? ¿Propiedades de qué, concretamente?

La respuesta es la propiedad Application Message. Según el estándar OASIS, Application Message serían

"los datos transmitidos a través del protocolo MQTT por la red para la aplicación. Cuando un mensaje de aplicación se transmite a través de MQTT, contiene datos útiles, calidad de servicio (QoS), un conjunto de propiedades y un tema" (destacado por mí).

Fíjese en el rectángulo amarillo de datos Payload data (carga útil) de la figura 1. Existe una distinción terminológica importante sobre la que queremos llamar su atención.

Esquema de abstracción de los mensajes de la aplicación MQTT 5.0

Fig. 01. Esquema de abstracción de los mensajes de la aplicación MQTT 5.0

En el contexto de un protocolo de mensajería, cuando vemos la palabra mensaje, solemos pensar en un mensaje de usuario, con frecuencia un mensaje de texto; la mayoría de las veces, no pensamos en el mensaje como una aplicación en su conjunto. 

Pero aquí, el mensaje enviado por los usuarios a través de MQTT forma parte de los datos de carga útil, mientras que las propiedades son parte de un modelo de protocolo abstracto llamado Application Message. Así, cuando enviamos un mensaje personalizado a través de MQTT, podemos tener no solo propiedades asociadas a ese "mensaje personalizado", sino también propiedades asociadas al mensaje de la aplicación en su conjunto: propiedades para unirse, publicar, suscribirse y desuscribirse de temas, propiedades para la autenticación, etc.

Además, existen propiedades Will adjuntas al mensaje Will.

"El mensaje Will consta de las propiedades Will, el tema Will y la carga Will en la carga útil CONNECT. "

La terminología puede parecer un poco confusa al empezar a aplicar el protocolo, pero haremos todo lo posible para que quede lo más claro posible.


¿Para qué sirven las propiedades?

Además de transferir los metadatos de carga útil, las propiedades pueden utilizarse para personalizar cada aspecto de la interacción entre el Cliente y el Servidor (bróker), así como la interacción entre diferentes Clientes. Desde la conexión hasta la desconexión, pueden utilizarse para establecer el formato de un tipo de contenido, solicitar información a un bróker, determinar la validez de un mensaje, seleccionar un método de autenticación e incluso realizar la redirección del servidor, entre otros usos. Como podemos ver en las tablas siguientes, a excepción de los paquetes PINGREQ y PINGRESP, que se utilizan para actualizar el periodo Keep Alive, todos los tipos de paquetes pueden contener algunas propiedades específicas dependiendo del contexto del paquete.

La propiedad de usuario es un caso especial de propiedad que puede usarse en todos los paquetes y cuyo valor viene definido por la aplicación, es decir, su semántica no está definida por el protocolo. Vamos a repasar brevemente las propiedades de usuario en la última sección, que habla de cómo se pueden utilizar las propiedades para ampliar el protocolo.

Aunque los nombres de las propiedades definen explícitamente su finalidad, necesitamos saber:

  • cuándo pueden usarse
  • cuándo deben usarse
  • y qué ocurre si no se instalan correctamente

Para facilitar la lectura y comprensión de las siguientes descripciones, las hemos agrupado en la tabla de abajo con distintos colores según su funcionalidad. Tenga en cuenta que la agrupación es un tanto arbitraria porque el uso de las propiedades se solapa en los distintos tipos de paquetes.

En las siguientes descripciones, usaremos las palabras MUST y MAY tal y como se utilizan en el estándar OASIS, que a su vez las utiliza tal y como se describen en el documento RFC 2119 de la Internet Engineering Task Force (IETF).


Propiedades de los conectores
CONNECT Session Expiry Interval (intervalo de caducidad de la sesión), Receive Maximum (obtener máximo), Maximum Packet Size (tamaño máximo de paquetes), Topic Alias Maximum (máximo de alias de temas), Request Response Information (información de respuesta de solicitud), Request Problem Information (información de problema de solicitud), User Property (propiedad de usuario), Authentication Method (método de autenticación), Authentication Data (datos de autenticación)
Carga útil CONNECT (CONNECT Payload) Will Delay Interval (intervalo de retraso Will), Payload Format Indicator (indicador de formato de carga útil), Message Expiry Interval (intervalo de caducidad del mensaje), Content Type (tipo de contenido), Response Topic (tema de la respuesta), Correlation Data (datos de correlación), User Property (propiedad de usuario)
CONNACK Session Expiry Interval (intervalo de finalización de la sesión), Receive Maximum (obtener máximo), Maximum QoS (calidad máxima del servicio), Retain Available (retención disponible), Maximum Packet Size (tamaño máximo del paquete), Assigned Client Identifier (identificador de cliente asignado), Topic Alias Maximum (alias máximo del tema), Reason String (línea del motivo), User Property (propiedad de usuario), Wildcard Subscription Available (suscripción comodín disponible), Subscriptions Identifiers Available (identificadores de suscripción disponibles), Shared Subscription Available (suscripción general disponible), Server Keep Alive (mantener activo el servidor), Response Information (información sobre la respuesta), Server Reference (indicación al servidor), Authentication Method (método de autenticación), Authentication Data (datos de autenticación)
DISCONNECT Session Expiry Interval (intervalo de finalización de la sesión), Reason String (línea de motivo), User Property (propiedad de usuario), Server Reference (indicación al servidor)

Tabla 1. MQTT v5.0 - Propiedades agrupadas por funcionalidad - Propiedades de conexión

Assigned Client Identifier - DEBE ser CONNECT. Si no se establece, el agente PUEDE asignar el identificador a CONNACK.

El identificador de cliente es obligatorio para los paquetes CONNECT. No obstante, se permite al bróker aceptar un identificador con longitud de cero bytes y asignar el identificador al cliente. En este caso, el bróker lo retornará en un paquete CONNACK con esta propiedad.

Maximum Packet Size - PUEDE no estar establecido, pero no puede ser cero.

Representa el "número total de bytes del paquete de control". Es usado por el Cliente y el Bróker para determinar el tamaño máximo de paquete que están dispuestos a aceptar. Si nuestro cliente establece esta propiedad y el bróker envía un paquete que supere este límite, deberemos desconectarnos (DISCONNECT) con el código de motivo 0x95 (Packet too large - Paquete demasiado grande). El bróker no enviará líneas de motivo o cualquier propiedad de usuario si su inclusión hace que el paquete sea superior a esa propiedad.

Máxima QoS - la utiliza el bróker en CONNACK y PUEDE no estar configurada.

Informa al cliente de la capacidad del bróker para gestionar niveles de QoS. Si el agente acepta el nivel 2 de QoS, esta propiedad no será establecida. Nuestro cliente DEBE respetar esta restricción del servidor no enviando paquetes PUBLISH con un nivel de QoS superior. Solo se nos permite mantener el nivel 0 de QoS en nuestro cliente y seguir manteniendo la conformidad.

Comentarios no normativos

" El cliente no tiene por qué admitir paquetes PUBLISH de QoS 1 o QoS 2. En este caso, el Cliente simplemente restringirá el campo QoS máximo en cualquier comando SUBSCRIBE que envíe a un valor que pueda soportar".

Message Expiry Interval - PUEDE no estar configurado.

También forma parte de las propiedades PUBLISH. Establecerá el tiempo de vida útil del mensaje Will. Si no se establece este parámetro, cuando el bróker publique un mensaje Will, este no tendrá una fecha de caducidad definida.

"Si el intervalo de caducidad del mensaje ha pasado y el Servidor no ha podido iniciar una nueva entrega al abonado en cuestión, DEBERÁ borrar la copia del mensaje para ese abonado."

También puede establecerse en las propiedades Will para CONNECT Payload. Si no se establece el parámetro, la validez del mensaje será ilimitada.

Payload Format Indicator - DEBE establecerse si estamos enviando datos de caracteres.

El indicador de formato de la carga útil puede configurarse en las propiedades Will para la carga útil CONNECT. Indica si el mensaje Will son datos de caracteres UTF-8 o "bytes indefinidos". 

"El servidor PUEDE comprobar el formato del mensaje Will y el hecho de que no se haya enviado CONNACK con el código de motivo 0x99 (Payload format invalid - Formato de carga útil no válido)."

Esta es la parte de las propiedades PUBLISH que especifica el formato de la carga útil. La verificación del bróker también es opcional, pero al comprobar el formato de la carga útil, podemos esperar PUBACK, PUBREC o DISCONNECT con el mismo código de motivo (0x99) si el formato es diferente del formato declarado.

Si la propiedad no está establecida, se asumirá que el formato de la carga útil es "bytes indefinidos". Es decir, si estamos enviando datos de caracteres, establecer esta propiedad será obligatorio.

Reason String - PUEDE ser usada en todos los ACK, DISCONNECT y AUTH por el cliente o bróker.

Reason String es una de las nuevas características de MQTT v5.0. Puede usarse para complementar el código de motivo con una herramienta de diagnóstico comprensible para el ser humano. Puede usare, por ejemplo, para llevar un registro. Podemos pedir al bróker que no lo envíe poniendo a 0 el parámetro Request Problem Information en las propiedades de CONNECT. 

Receive Maximum - PUEDE no estar configurado, pero no puede ser igual a cero. 

Nuestro cliente puede usar esta propiedad en CONNECT para limitar el número de publicaciones QoS 1 y QoS 2 que estamos dispuestos a gestionar simultáneamente en la conexión de red actual. El bróker puede establecerlo en CONNACK. Si no se configura, se usará el valor por defecto de 65.535.

Con QoS 0, no tenemos necesidad de esperar a PUBACK (QoS 1) o PUBCOMP (QoS 2) porque, como sabemos, QoS 0 funciona sobre la base "disparar y olvidar". Esta propiedad establece cuántos mensajes está dispuesto a enviar/recibir nuestro cliente o bróker antes de recibir el correspondiente PUBACK o PUBCOMP. Podemos pensar en esta propiedad como un medio para decir cuántos mensajes pueden encontrarse en estado "pendiente de confirmación" hasta que se envíen nuevos mensajes.

Request Problem Information - PUEDE establecerse en CONNECT.

Se usa para informar al servidor de que queremos recuperar la(s) líneas(s) de motivo y las propiedades del usuario en caso de fallo. Si no se establece, el bróker podría enviarlas.

Request Response Information - PUEDE establecerse en CONNECT.

Forma parte de la interacción solicitud/respuesta a través de MQTT en lugar de la interacción habitual publicar/suscribir. Si el valor no se establece, le diremos al bróker que no queremos que envíe información de respuesta. El bróker está autorizado a no enviar ninguna información de respuesta, aunque se la solicitemos. Si la propiedad no existe, no se establecerá el valor por defecto.

Response Information - PUEDE establecerse en CONNECT.

Forma parte de la interacción solicitud/respuesta a través de MQTT en lugar de la interacción habitual publicar/suscribir. 

Comentarios no normativos

" Normalmente se usa para transmitir una parte globalmente única del árbol de temas que está reservada para ese Cliente, al menos durante la duración de su sesión. Con frecuencia no se trata de un nombre cualquiera, ya que tanto el Cliente solicitante como el Cliente receptor deben estar autorizados a utilizarlo. La propiedad puede usarse como raíz del árbol de temas para un Cliente concreto. Para que el Servidor retorne esta información, normalmente deberá estar configurada correctamente. El uso de este mecanismo permite realizar la configuración una vez en el Servidor en lugar de en cada Cliente."

Response Topic - PUEDE estar configurado como CONNECT o PUBLISH. 

Forma parte de la interacción solicitud/respuesta a través de MQTT en lugar de la interacción habitual publicar/suscribir. Cuando está activado, el bróker interpreta el mensaje Will como una solicitud. A diferencia del filtro de temas usado en los paquetes SUBSCRIBE, Response Topic no puede contener caracteres de comodín.

"Request Message es un Application Message con un Response Topic".

Así, esta propiedad caracteriza al Application Message como parte de la interacción petición/respuesta.

Retain Available - PUEDE estar presente en CONNACK.

La propiedad Retain Available indica a nuestro cliente si el bróker soporta mensajes retenidos. Si están ausentes, los mensajes guardados se encontrarán disponibles.

Server Keep Alive - PUEDE estar presente en CONNACK.

La propiedad Keep Alive del servidor tiene prioridad sobre la propiedad Keep Alive solicitada en CONNECT. Si la propiedad no se encuentra en CONNACK, podremos utilizar Keep Alive. De lo contrario, se aplicarán las reglas de Server Keep Alive.

Server Reference - PUEDE estar presente en CONNACK o DISCONNECT.

Informa a nuestro cliente de la redirección del servidor. Puede referirse a una redirección temporal o permanente. En ambos casos, el otro servidor puede ser ya conocido por nuestro cliente o se especificará usando esta propiedad.

Comentarios no normativos

Ejemplos de Server Reference:

myserver.xyz.org

myserver.xyz.org:8883

10.10.151.22:8883 [fe80::9610:3eff:fe1c]:1883

El bróker puede no enviar nunca esta propiedad y nuestro cliente puede igualmente ignorarla.

Session Expiry Interval - PUEDE establecerse en CONNECT.

Determina cuánto tiempo se almacenará la sesión tras la desconexión. Si el parámetro no se establece o está ausente, la sesión se terminará al desconectarse la conexión. Podemos establecer un tiempo de validez de sesión ilimitado estableciendo esta propiedad como UINT_MAX. Debemos mantener Session State si esta propiedad es superior a cero. Podemos comprobarlo con el indicador Session Present en CONNACK.

Esta propiedad puede resultar útil cuando la conexión de red es intermitente, permitiendo a nuestro cliente reanudar la sesión cada vez que se restablezca la conexión de red.

Shared Subscription Available - PUEDE estar presente en CONNACK.

Informa a nuestro cliente si el bróker admite suscripciones compartidas. Si la propiedad no existe, la admite.

Subscription Identifiers Available - PUEDE estar presente en CONNACK.

Informa a nuestro cliente si el bróker admite identificadores de suscripción. Si la propiedad no existe, la admite.

Topic Alias Maximum - PUEDE establecerse CONNECT y PUEDE estar presente en CONNACK.

Informa al bróker del número máximo de alias o pseudónimos de tema que nuestro cliente está dispuesto a aceptar para esta conexión en particular. Si la propiedad es nula o no ha sido establecida, el agente no enviará el alias de tema en esta conexión. Lo contrario también será cierto: si esta propiedad no está presente en CONNACK o está presente pero su valor es nulo, nuestro cliente no deberá enviar el alias del tema.

Wildcard Subscription Available - PUEDE estar presente en CONNACK.

Si la propiedad no está establecida (es igual a null), el bróker no admitirá suscripciones con caracteres comodín. En este caso, el bróker se desconectará (DISCONNECT) tras recibir una suscripción (SUBSCRIBE) con una solicitud de caracteres comodín (Wildcard Subscription). Pero incluso si el agente admite esta función, podrá rechazar una solicitud de suscripción con caracteres comodín específica y retornar un SUBACK con el mismo código de motivo 0xA2 (Wildcard Subscriptions not supported - suscripciones con caracteres comodín no admitidas). Si la propiedad no está presente en CONNACK, el agente admitirá esta función.

Will Delay Interval - PUEDE establecer las propiedades Will de la carga útil CONNECT.

Establece el retardo en segundos que el bróker deberá cumplir antes de enviar un mensaje Will. Esta propiedad resulta especialmente útil para evitar que se envíe un mensaje Will cuando la conexión de red es inestable.


Propiedades de publicación
PUBLISH Payload Format Indicator (indicador de formato de la carga útil), Message Expiry Interval (intervalo de caducidad del mensaje), Topic Alias (alias del tema), Response Topic (tema de la respuesta), Correlation Data (datos de correlación), User Property (propiedades de usuario), Subscription Identifier (identificador de suscripción), Content Type (tipo de contenido)
PUBACK Reason String (línea de motivo), User Property (propiedad de usuario)
PUBREC Reason String, User Property
PUBREL Reason String, User Property
PUBCOMP Reason String, User Property

Tabla 2. MQTT v5.0 - Propiedades agrupadas por funcionalidad - Propiedades de publicación

Topic Alias - PUEDE establecerse PUBLISH

Topic Alias es también una de las nuevas características de MQTT v5.0. Permite a un bróker o cliente reducir el tamaño de los paquetes sustituyendo el nombre del tema por un pequeño número entero, o sea, un alias. La reducción de tamaño puede ser significativa porque los nombres de los temas son líneas que pueden tener hasta 65 535 bytes de longitud (UINT_MAX).

Correlation data - PUEDEN establecerse las propiedades PUBLISH y Will

Forma parte de la interacción solicitud/respuesta a través de MQTT en lugar de la interacción habitual publicar/suscribir. El valor de la propiedad solo resulta importante para la aplicación (bróker y clientes). Se trata de los datos binarios usados en una solicitud/respuesta "por el emisor del mensaje para determinar a qué solicitud va destinado el mensaje de respuesta cuando lo recibe".

Content type - PUEDE establecerse como PUBLISH y Will Properties

También puede usarse en CONNECT para establecer el tipo de contenido de un mensaje Will. 

El bróker solo comprobará la codificación de la propiedad en sí. El valor de la propiedad dependerá del cliente.


Propiedades de suscripción/desuscripción
SUBSCRIBE Identificador de suscripción, propiedad del usuario (User Property)
SUBACK Línea de motivo, propiedad de usuario
UNSUBSCRIBE User Property
UNSUBACK  Reason String, User Property

Tabla 3: MQTT v5.0 - Propiedades agrupadas por funcionalidad - Propiedades de suscripción/desuscripción

Subscription Identifier - PUEDE establecerse SUBSCRIBE

Se trata de un identificador numérico que puede configurarse como SUSCRIBE. Será retornado por el bróker en el mensaje, permitiendo al cliente(s) determinar qué suscripción(es) causaron que el mensaje fuera entregado. Puede tomar valores de 1 a 268.435.455. NO DEBE ponerse a cero y NO DEBE usarse al publicar (PUBLISH) de cliente a servidor.


Propiedades de autenticación
AUTH
Authentication Method (método de autenticación), Authentication Data (datos de autenticación), Reason String (línea de motivo), User Property (propiedad de usuario)

Tabla 4. MQTT v5.0 - Propiedades agrupadas por funcionalidad - Propiedades de autenticación

No en vano, estas propiedades también pueden usarse en compuestos.

Authentication Method

Además de la autenticación de red básica usando un nombre de usuario y una contraseña, MQTT v5.0 admite la "autenticación avanzada". Esta propiedad informa del método seleccionado, que eligen los desarrolladores de la aplicación. El bróker le indicará si el método es compatible.

El método de autenticación suele ser un mecanismo SASL (Simple Authentication and Security Layer), y usar un nombre registrado de este tipo facilita el intercambio. No obstante, el método de autenticación no se limita al uso de mecanismos SASL registrados.

Authentication Data

Esta propiedad la usan el cliente y el bróker para intercambiar datos de autenticación según el método de autenticación elegido.


Cómo leemos las propiedades de MQTT v5.0 en MQL5

Hasta ahora, en las partes anteriores de esta serie, hemos tratado con los ajustes "por sesión" configurados con la ayuda de banderas de bits, a saber, las banderas de conexión en CONNECT, el código de motivo CONNACK, y la bandera CONNACK Session Present. Dichos ajustes se leen/escriben/guardan una vez por sesión. Las propiedades, en cambio, son otra historia. Forman parte del mensaje de la aplicación y pueden contener grandes cantidades de datos importantes en algunos perfiles de aplicación. Por ello, nuestro cliente deberá estar dispuesto a leer y registrar propiedades de forma continua.

Para escribir una prueba que lea las propiedades enviadas por el servidor, necesitaremos un array de bytes de ejemplo. Empezaremos con un array de bytes de ejemplo para el paquete CONNACK, ya que es el primer paquete con el que tratará nuestro Cliente. Como todos los paquetes de control MQTT, tiene una encabezado fijo de dos bytes y un encabezado variable de dos bytes que consta de un byte para los indicadores de confirmación de la conexión y un byte para el código de motivo de conexión. Propiedades es el último campo del paquete CONNACK. No tiene identificador de paquete ni carga útil.

MQTT 5.0 - Estructura del paquete CONNACK

Fig. 02. Estructura del paquete CONNACK en MQTT 5.0

Por el Estándar sabemos que:

"Un conjunto de propiedades consta de una Longitud de Propiedad (Property Lenght) seguida de propiedades".

También sabemos que:

" La longitud de la propiedad se codifica como un número entero variable (Variable Byte Integer). La longitud de una propiedad no incluye los bytes usados para codificarla, pero sí incluye la longitud de las propiedades. Si no hay propiedades, esto DEBERÁ indicarse incluyendo una longitud de propiedad igual a cero."

Por lo tanto, la longitud fija restante de la encabezado y la longitud de la propiedad, codificada como un número entero de bytes variables, serán las primeras piezas de información que necesitaremos leer antes de acceder a las propiedades. Si la longitud de la propiedad es cero, no habrá nada que leer.

Así, nuestro ejemplo de array de bytes podría tener este aspecto para CONNACK sin propiedades:

uchar connack_response[] = {2, X, 0, 0, 0};

donde X es la longitud restante del encabezado fijo. El algoritmo para descodificar un número entero de bytes variables se ofrece en el Estándar. En MQL5, este algoritmo se puede escribir de la forma siguiente:

uint DecodeVariableByteInteger(uint &buf[], uint idx)
  {
   uint multiplier = 1;
   uint value = 0;
   uint encodedByte;
   do
     {
      encodedByte = buf[idx];
      value += (encodedByte & 127) * multiplier;
      if(multiplier > 128 * 128 * 128)
        {
         Print("Error(Malformed Variable Byte Integer)");
         return -1;
        }
      multiplier *= 128;
     }
   while((encodedByte & 128) != 0);
   return value;
  };

donde buf[idx] representa "el siguiente byte del flujo".

Aunque el algoritmo para descodificar números enteros de bytes variables se ofrece en el Estándar, también hemos escrito una prueba muy simple para ello, solo para asegurarnos de que en esta etapa la implementación funciona como esperamos:

bool TEST_DecodeVariableByteInteger()
  {
   Print(__FUNCTION__);
   uint buf[] = {1, 127, 0, 0, 0};
   uint expected = 127;
   uint result = DecodeVariableByteInteger(buf, 1);
   ZeroMemory(buf);
   return AssertEqual(expected, result);
  }

Obviamente, a efectos de prueba, el valor de longitud restante se codificará. Para el CONNACK anterior sin propiedades, sería así:

uchar connack_response[] = {2, 3, 0, 0, 0};

Un array de bytes de ejemplo para CONNACK con la propiedad de indicador de formato de carga útil de un byte establecido en formato de carga útil de línea codificada UTF-8 podría tener este aspecto:

uchar connack_response_one_byte_property = {2, 5, 0, 0, 2, 1, 1};

Como podemos ver, la comprobación de propiedades en CONNACK es bastante sencilla. Solo tenemos que leer el quinto byte, que contiene la longitud de la propiedad. Si no es cero, tendremos propiedades. Nuestra primera prueba tendrá el aspecto que sigue:

bool TestProtectedMethods::TEST_HasProperties_CONNACK_No_Props()
  {
   Print(__FUNCTION__);
//--- Arrange
   bool expected = false;
   uchar connack_no_props[5] = {2, 3, 0, 0, 0};
//--- Act
   CSrvResponse *cut = new CSrvResponse();
   bool result =  this.HasProperties(connack_no_props);
//--- Assert
   bool isTrue = AssertEqual(expected, result);
//--- cleanup
   delete cut;
   ZeroMemory(result);
   return  isTrue ? true : false;
  }

En el artículo anterior podrá ver las pruebas de los métodos protegidos; asimismo, encontrará el código adjunto para ver una variante de la prueba fallida.

La primera implementación, suficiente para superar las pruebas en la fase actual, tendrá este aspecto:

bool CSrvResponse::HasProperties(uchar &resp_buf[])
  {
   return resp_buf[4] != 0 ? true : false;
  }

Aquí usamos un operador ternario para minimizar el código.

Debemos considerar que la ubicación del byte de longitud de la propiedad dependerá del tipo de paquete. Esto se debe a que, aunque las propiedades sean siempre el último campo de la encabezado variable, hay paquetes que requieren un identificador de paquete de dos bytes antes de la longitud de la propiedad. En CONNACK, esto no supone un problema.

Claro que usted podría decir: "¡Entonces este código no funcionará para otros tipos de paquetes!". Y tendría razón. Pero recuerde, aquí hemos adoptado un enfoque TDD. Una de las principales ventajas de este planteamiento es que podemos centrarnos en la tarea que tenemos entre manos, en vez de intentar resolver todos los posibles problemas futuros en las primeras fases del desarrollo. Ya hablaremos de otros tipos de paquetes cuando llegue el momento y nuestra prueba falle. A continuación, reescribiremos nuestras pruebas y, finalmente, refactorizaremos el código.

Puede parecer un poco contario a la intuición al principio, pero luego ya no podrá escribir código como antes. Este enfoque facilita el trabajo y lo hace aún más divertido. Por cierto, en la próxima parte de esta serie, comenzaremos a escribir y leer las propiedades de los paquetes PUBLISH. Estén atentos a las novedades.

Si la longitud de la propiedad no es igual a cero, podremos buscar el identificador de la propiedad en el siguiente byte. El identificador de la propiedad nos ofrece el tipo de datos de la propiedad.

"Una propiedad consta de un identificador que define su uso y un tipo de dato seguido de un valor".

uchar CSrvResponse::GetPropertyIdentifier(uchar &resp_buf[])
  {
   return resp_buf[5];
  }

El tipo de dato nos da el número de bytes a leer. El tipo de datos puede ser:

Número entero de un byte

uchar CSrvResponse::ReadOneByteProperty(uchar &resp_buf[])
  {
   return resp_buf[6];
  }

Número entero de dos bytes

void CSrvResponse::ReadTwoByteProperty(uchar &resp_buf[], uchar &dest_buf[])
  {
   ArrayCopy(dest_buf, resp_buf, 0, 6, 2);
  }

Número entero de cuatro bytes

void CSrvResponse::ReadFourByteProperty(uchar &resp_buf[], uchar &dest_buf[])
  {
   ArrayCopy(dest_buf, resp_buf, 0, 6, 4);
  }

Número entero de bytes variables (solo para el identificador de suscripción) 

void CSrvResponse::ReadVariableByteProperty(uint &resp_buf[], uint &dest_buf[], uint start_idx)
  {
   uint value = DecodeVariableByteInteger(resp_buf, start_idx);
   ArrayResize(dest_buf,value,7);
   ArrayFill(dest_buf, 0, 1, value);
  }

La lectura/decodificación (y escritura/codificación) de esta propiedad requerirá muchos más datos que los que esta prueba está comprobando ahora.

"Un número entero de bytes variables se codifica usando un esquema de codificación que utiliza un byte para valores de hasta 127". Los valores grandes se manejan de la siguiente manera: los siete bits menos significativos de cada byte codifican los datos, mientras que el bit más significativo se utiliza para indicar si hay más bytes en la representación. Así, cada byte codifica 128 valores y un 'bit de continuación'" (el subrayado es mío).

Nos ocuparemos de las propiedades Variable Byte Integer al implementar los paquetes SUBSCRIBE, ya que solo se usan para la propiedad Subscription Identifier. También están disponibles los tres tipos de datos siguientes: líneas codificadas en UTF-8, datos binarios y pares de líneas UTF-8. Se describirán con detalle en el contexto del uso de la consulta/respuesta y la implementación del caso especial de la propiedad de usuario.

Las líneas codificadas en UTF-8 llevan como prefijo su longitud.

"Cada una de estas líneas lleva como prefijo un campo de longitud entero de dos bytes que indica el número de bytes de la propia líneas codificada en UTF-8, como se muestra en la Figura 1.1 Estructura de líneas codificadas en UTF-8 que aparece a continuación. Por lo tanto, el tamaño máximo de una línea codificada en UTF-8 es de 65.535 bytes. A menos que se especifique lo contrario, todas las líneas codificadas en UTF-8 pueden tener cualquier longitud entre 0 y 65.535 bytes."

Estructura de líneas codificadas MQTT-v5-utf8-OASIS

Fig. 03. MQTT 5.0 - Estructura de líneas UTF-8 - Captura de pantalla de la tabla OASIS

Las líneas codificadas en UTF-8 deben comprobarse en busca de puntos de código Unicode prohibidos (más información al respecto más adelante).

" La sección 1.6.4 describe los puntos de código Unicode prohibidos que no deben incluirse en una línea codificada en UTF-8. La implementación del Cliente o del Servidor puede elegir si desea comprobar que estos puntos de código no se usan en líneas codificadas en UTF-8, como el nombre del asunto o las propiedades."

Los datos binarios también van precedidos de su longitud.

" Los datos binarios se representan con ayuda de un número entero de dos bytes que indica el número de bytes de datos seguido de ese número de bytes. Así, la longitud de los datos binarios estará limitada al rango de 0 a 65 535 bytes".

Seguiremos contando el número de bytes leídos para saber cuándo hemos leído todas las propiedades. No debemos preocuparnos por el orden de las propiedades.

"El orden de las propiedades con distintos identificadores no importa".


Cómo pueden usarse las propiedades para ampliar el protocolo

Como hemos indicado al principio de este artículo, las propiedades forman parte del "mecanismo de extensibilidad" de MQTT 5.0, y la propiedad más importante de este mecanismo será la Propiedad de Usuario (User Property) que puede utilizarse en cualquier paquete de control MQTT. Las propiedades de usuario son pares clave-valor cuyo significado resulta opaco al protocolo. Es decir, su valor lo determina la aplicación.

Imaginemos un caso de uso para nuestro dominio: el destinatario copia señales comerciales de tres proveedores diferentes. Cada proveedor utiliza distintos brókeres. Cada bróker puede asignar nombres simbólicos distintos a un mismo activo, por ejemplo, el oro.

  • El agente A usa GOLD
  • El bróker B usa XAUUSD.
  • El bróker C usa XAUUSD.s

Además, cada proveedor de señales puede usar más de un bróker. Por lo tanto, el par proveedor_señales: proveedor_bróker puede cambiar en cualquier momento, incluso durante una sesión comercial. (Sí, aquí se observa una explosión casi combinatoria). El destinatario necesita conocer (idealmente en milisegundos) el valor del nombre de símbolo que recibe para poder convertirlo en el nombre de símbolo que utiliza su bróker para reproducir correctamente la solicitud comercial.

Sin propiedades de usuario, como ocurría en versiones anteriores del protocolo, estos metadatos (proveedor_señales: proveedor_bróker) se incorporarían en la carga útil, donde cabría esperar que solo se encontraran (y analizaran) los datos necesarios de la señal comercial.

Por el contrario, si cada proveedor de señales tiene su propia propiedad de usuario con el nombre de su bróker, la carga útil solo podrá contener los datos de señal requeridos.

Este es un ejemplo simplificado de un caso de uso de este tipo, pero estos metadatos pueden ampliarse a cualquier información importante, incluidas líneas JSON/XML e incluso archivos enteros. Las posibilidades son, en cierto sentido, ilimitadas.


Conclusión

En la cuarta parte de nuestra serie, hemos presentado una breve descripción de las propiedades de MQTT v5.0, su semántica y algunos casos de uso. También hemos visto su implementación para CONNACK, y hemos dado un ejemplo sencillo de cómo se pueden utilizar para ampliar el protocolo. En la siguiente sección los aplicaremos en el contexto de los paquetes PUBLISH, usando siempre el enfoque TDD para hacer frente a la complejidad de las especificaciones.

Si puede contribuir al desarrollo de su propio cliente MQL5 que pasará a formar parte de CodeBase, deje un mensaje en los comentarios o escríbame en el chat. ¡Cualquier ayuda será bienvenida! :)


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13651

Algoritmos de optimización de la población: Algoritmo de búsqueda de sistema cargado (Charged System Search, CSS) Algoritmos de optimización de la población: Algoritmo de búsqueda de sistema cargado (Charged System Search, CSS)
En este artículo, analizaremos otro algoritmo de optimización inspirado en la naturaleza inanimada: el algoritmo de búsqueda de sistema cargado (CSS). El objetivo de este artículo es presentar un nuevo algoritmo de optimización basado en los principios de la física y la mecánica.
Redes neuronales: así de sencillo (Parte 61): El problema del optimismo en el aprendizaje por refuerzo offline Redes neuronales: así de sencillo (Parte 61): El problema del optimismo en el aprendizaje por refuerzo offline
Durante el aprendizaje offline, optimizamos la política del Agente usando los datos de la muestra de entrenamiento. La estrategia resultante proporciona al Agente confianza en sus acciones. No obstante, dicho optimismo no siempre está justificado y puede acarrear mayores riesgos durante el funcionamiento del modelo. Hoy veremos un método para reducir estos riesgos.
Redes neuronales: así de sencillo (Parte 62): Uso del transformador de decisiones en modelos jerárquicos Redes neuronales: así de sencillo (Parte 62): Uso del transformador de decisiones en modelos jerárquicos
En artículos recientes, hemos visto varios usos del método Decision Transformer, que permite analizar no solo el estado actual, sino también la trayectoria de los estados anteriores y las acciones realizadas en ellos. En este artículo, veremos una variante del uso de este método en modelos jerárquicos.
Características del Wizard MQL5 que debe conocer (Parte 07): Dendrogramas Características del Wizard MQL5 que debe conocer (Parte 07): Dendrogramas
La clasificación de datos para el análisis y la predicción es un área muy diversa del aprendizaje automático con un gran número de enfoques y métodos. En este artículo analizaremos uno de estos enfoques, a saber, la Clasificación Jerárquica Aglomerativa (Agglomerative Hierarchical Classification).