English Русский Español Deutsch 日本語
preview
Desenvolvimento de um Cliente MQTT para o MetaTrader 5: Metodologia TDD (Parte 4)

Desenvolvimento de um Cliente MQTT para o MetaTrader 5: Metodologia TDD (Parte 4)

MetaTrader 5Integração | 17 abril 2024, 13:42
56 0
Jocimar Lopes
Jocimar Lopes

"Os objetos de software devem ser abertos para extensão, mas fechados para modificação" (princípio de abertura-fechamento da programação orientada a objetos)

Introdução

Primeiro, uma visão geral rápida. Na primeira parte desta série, começamos escrevendo testes simples para uma função capaz de gerar o cabeçalho fixo do pacote de controle MQTT. Começamos com o primeiro bit do primeiro byte.

Na segunda parte, introduzimos algumas funções gerais e definições em dois arquivos de cabeçalho.

Na terceira parte, começamos a ler os sinalizadores de confirmação CONNACK e os códigos de motivo de conexão. Esta foi nossa primeira experiência com a seção de comportamento operacional do padrão OASIS. 

Até esse momento, tudo era estático e ligado à tentativa de conexão. Se o primeiro byte estivesse errado, o usuário recebia uma mensagem de erro da corretora e poderia tentar se conectar novamente com um pacote corretamente formado. 

As propriedades são uma história à parte. Elas são atributos dinâmicos da ‘MQTT application message’ e podem mudar após a conexão. O valor máximo de QoS da corretora pode mudar temporariamente por razões operacionais. O ‘receive maximum’ poderia ser alterado por conta de gargalos na rede. Além disso, algumas propriedades são dinâmicas por design, como Content-Type, todas as propriedades Will e a propriedade User. Essas propriedades estarão em constante mudança.

O padrão OASIS é bastante claro em suas especificações, mas muito é deixado para os desenvolvedores de clientes decidirem como ler, responder, armazenar e atualizar as propriedades atuais. Além disso, a camada de persistência (persistence layer) para gerenciamento da Session está totalmente sob a responsabilidade do desenvolvedor do cliente. Teremos que implementar a camada de persistência para gerenciar corretamente as propriedades entre as sessões. A escolha do algoritmo aqui será fundamental para manter conformidade, confiabilidade e desempenho.

Os comentários na seção "Como lemos as propriedades do MQTT v5.0 em MQL5" podem ser úteis como documentação informal para aqueles que irão acompanhar a biblioteca. Alguns usuários finais da biblioteca também podem se beneficiar desses comentários. Nesta seção, examinaremos as propriedades do ponto de vista do desenvolvedor da biblioteca. Exploraremos seus tipos de dados, identificadores e localização no array de bytes para melhor descrever como os lemos. Abaixo, consideraremos as propriedades do ponto de vista do usuário da biblioteca. Tentaremos descrever sua semântica em cada um dos possíveis casos de uso.

Nota: Salvo indicação em contrário, todas as citações são retiradas do padrão OASIS


O que são propriedades no MQTT v5.0?

As propriedades fazem parte dos "mecanismos de extensibilidade" adicionados no MQTT v5.0. Elas não estavam presentes na versão anterior 3.1.1, que foi a última antes desta grande atualização, mas são comumente encontradas no MQTT v5.0. Mas o que são propriedades MQTT? Propriedades de quê exatamente?

A resposta é: propriedades da mensagem de aplicação (application message). De acordo com o padrão OASIS, a ‘application message’ é

"Dados transmitidos pela rede por meio do protocolo MQTT para um aplicativo. Quando uma mensagem do aplicativo é transmitida por MQTT, ela contém dados úteis, qualidade de serviço (quality of service, QoS), um conjunto de propriedades e um tópico" (destacado por mim).

Olhe para o retângulo amarelo dados úteis (payload data) na Figura 1 abaixo. Existe uma distinção terminológica importante que gostaríamos de destacar.

Esquema abstrato da mensagem do aplicativo MQTT 5.0

Fig. 01. Esquema abstrato da mensagem do aplicativo MQTT 5.0

No contexto do protocolo de troca de mensagens, quando vemos a palavra mensagem (message), geralmente pensamos em uma mensagem de usuário, frequentemente uma mensagem de texto. Na maioria das vezes, não pensamos na mensagem como um aplicativo inteiro. 

Mas aqui, a mensagem enviada pelos usuários via MQTT faz parte dos dados úteis, e as propriedades fazem parte de um modelo abstrato do protocolo chamado ‘application message’. Assim, quando enviamos uma mensagem de usuário via MQTT, podemos ter não apenas propriedades relacionadas a essa "mensagem de usuário", mas também propriedades relacionadas à mensagem de aplicação como um todo: propriedades para conexão, publicação, assinatura e cancelar assinatura de tópicos, propriedades para autenticação, etc.

Além disso, existem as propriedades Will, que estão ligadas à mensagem Will.

"A mensagem Will consiste em propriedades Will, tópico Will e carga útil Will na carga útil de CONEXÃO (CONNECT payload)."

A terminologia pode parecer um pouco confusa quando você começa a implementar o protocolo, mas farei o meu melhor para torná-la o mais clara possível.


Para que são usadas as propriedades?

Além de transportar metadados de dados úteis, as propriedades podem ser usadas para configurar cada aspecto da interação entre Cliente e Servidor (corretora), bem como interações entre diferentes Clientes. Desde a conexão até a desconexão, elas podem ser usadas para estabelecer o formato do Content-Type, solicitar informações da corretora, determinar a validade da mensagem, escolher o método de autenticação e até realizar o redirecionamento do servidor, entre outras possibilidades de uso. Como mostrado nas tabelas abaixo, com exceção dos pacotes PINGREQ e PINGRESP, que são usados para atualizar o período de ’keep alive’, todos os tipos de pacotes podem conter algumas propriedades específicas dependendo do contexto do pacote.

A propriedade do usuário (user property) é um caso especial de propriedade que pode ser usada em todos os pacotes e cujo valor é definido pelo aplicativo, ou seja, sua semântica não é determinada pelo protocolo. Abordaremos brevemente a ‘user property’ na última seção, onde discutimos como as propriedades podem ser usadas para expandir o protocolo.

Embora os nomes das propriedades claramente definam seus propósitos, precisamos saber:

  • quando podem ser usadas
  • quando devem ser usadas
  • e o que acontece se forem configuradas incorretamente

Para facilitar a leitura e a compreensão das descrições a seguir, na tabela abaixo, nós as agrupamos em cores diferentes de acordo com sua funcionalidade. Note que a agrupação é um tanto arbitrária, pois o uso das propriedades se sobrepõe segundo os diferentes tipos de pacotes.

Nas descrições a seguir, usaremos as palavras DEVE (MUST) e PODE (MAY) conforme são usadas no padrão OASIS, que por sua vez as utiliza conforme descrito no documento RFC 2119 da Internet Engineering Task Force (IETF).


Propriedades de conexão
CONNECT Intervalo de expiração da sessão (session expiry interval), máximo de recepção (receive maximum), tamanho máximo do pacote (maximum packet size), máximo de pseudônimos de tópicos (topic alias maximum), solicitar informação de resposta (request response information), solicitar informação de problema (request problem information), propriedade do usuário (user property), método de autenticação (authentication method), dados de autenticação (authentication data)
Carga útil CONNECT (CONNECT Payload) Intervalo de atraso Will (Will delay interval), indicador de formato de carga útil (payload format indicator), intervalo de expiração da mensagem (message expiry interval), tipo de conteúdo (content type), tópico de resposta (response topic), dados de correlação (correlation data), propriedade do usuário (user property)
CONNACK intervalo de expiração da sessão (session expiry interval), máximo de recepção (receive maximum), QoS máximo (maximum QoS), retenção disponível (retain available), tamanho máximo do pacote (maximum packet size), identificador de cliente atribuído (assigned client identifier), máximo de pseudônimos de tópicos (topic alias maximum), string de razão (reason string), propriedade do usuário (user property), assinatura curinga disponível (wildcard subscription available), identificadores de assinaturas disponíveis (subscriptions identifiers available), assinatura compartilhada disponível (shared subscription available), manter atividade do servidor (server keep alive), informação de resposta (response information), referência do servidor (server reference), método de autenticação (authentication method), dados de autenticação (authentication data)
DISCONNECT Intervalo de expiração da session (session expiry interval), string de razão (reason string), propriedade do usuário (user property), referência do servidor (server reference)

Tabela 1. MQTT v5.0 - Propriedades agrupadas por funcionalidade — Propriedades de conexão

Identificador de cliente atribuído - DEVE ser valor de CONNECT. Se não estiver configurado, o broker PODE atribuir um identificador no CONNACK.

O identificador de cliente é obrigatório para pacotes CONNECT. No entanto, é permitido que o broker aceite um identificador de comprimento zero e atribua um identificador ao cliente. Nesse caso, o broker o retornará no pacote CONNACK com essa propriedade.

Tamanho máximo do pacote – PODE não ser definido, mas não pode ser igual a zero.

Representa "o número total de bytes em um pacote de controle". É utilizado pelo Cliente e pela Corretora para determinar o tamanho máximo do pacote que estão dispostos a aceitar. Se nosso cliente estabelecer essa propriedade, e o broker enviar um pacote que exceda esse limite, devemos nos desconectar (DISCONNECT) com o código de motivo 0x95 (packet too large, pacote muito grande). O broker não enviará strings de razão ou algumas propriedades do usuário se a inclusão delas fizer com que o pacote exceda esse tamanho.

QoS máximo – usado pelo broker no CONNACK e PODE não ser definido

Informa ao cliente sobre a capacidade do broker de lidar com os níveis de QoS. Se o broker aceitar o nível QoS 2, esta propriedade não será definida. Nosso cliente DEVE respeitar essa limitação do servidor, não enviando pacotes PUBLISH com um nível de QoS mais alto. É permitido manter em nosso cliente apenas o nível de QoS 0 e ainda assim manter a conformidade.

Comentário não normativo

"Não é necessário que o cliente suporte pacotes PUBLISH QoS 1 ou QoS 2. Nesse caso, o Cliente simplesmente limita o campo máximo de QoS em quaisquer comandos SUBSCRIBE enviados ao valor que ele pode suportar".

Intervalo de expiração da mensagem – PODE não ser definido.

Também é parte das propriedades PUBLISH. Estabelece o tempo de vida da mensagem Will. Se esse parâmetro não for definido, quando o broker publicar a mensagem Will, ela não terá um prazo de expiração definido.

"Se o intervalo de expiração da mensagem passar e o Servidor não conseguir iniciar a outra entrega ao assinante correspondente, então ele DEVE deletar a cópia da mensagem para esse assinante".

Também pode ser definido nas propriedades Will para a carga útil de CONEXÃO. Se o parâmetro não for definido, o prazo de validade da mensagem não é limitado.

Indicador de formato de carga útil – DEVE ser definido se estamos enviando dados caracteres.

O indicador de formato de carga útil pode ser definido nas propriedades Will para a carga útil de CONEXÃO. Indica se a mensagem Will é composta por dados caracteres em codificação UTF-8 ou se são "bytes indefinidos". 

"O broker PODE verificar o formato da mensagem Will e a falta de envio de CONNACK com o código de motivo 0x99 (payload format invalid, formato de carga útil inválido)"

Essa é uma parte das propriedades PUBLISH que indica o formato do ‘payload’. A verificação da corretora também não é obrigatória. Mas, ao verificar o formato de carga útil, podemos esperar PUBACK, PUBREC ou DISCONNECT com o mesmo código de motivo (0x99), se o formato for diferente do declarado.

Se a propriedade não for estabelecida, presume-se que o formato da carga útil seja "bytes indefinidos". Ou seja, se estamos enviando dados caracteres, a definição desta propriedade é obrigatória.

String de razão – PODE ser usada em todos os ACK, DISCONNECT e AUTH pelo cliente ou pelo broker.

String de razão — uma das novas funcionalidades do MQTT v5.0. Ela pode ser usada para complementar o código de motivo com uma ferramenta de diagnóstico que pode ser interpretada por humanos. Por exemplo, pode ser utilizada para registrar atividades (logging). Podemos solicitar à corretora que não a envie, definindo o parâmetro ‘request problem information’ como 0 nas propriedades do CONNECT. 

Máximo de recepção – PODE não ser definido, mas não pode ser igual a zero. 

Nosso cliente pode usar essa propriedade no CONNECT para limitar o número de publicações QoS 1 e QoS 2 que estamos dispostos a processar simultaneamente na conexão de rede atual. O broker pode defini-lo no CONNACK. Se não definido, o valor padrão é 65,535.

Com QoS 0, não há necessidade de esperar por PUBACK (QoS 1) ou PUBCOMP (QoS 2), porque, como sabemos, QoS 0 opera com base no princípio de "atirar e esquecer". Essa propriedade estabelece quantas mensagens nosso cliente ou corretora está disposto a enviar/receber antes de receber o PUBACK ou PUBCOMP correspondente. Podemos considerar essa propriedade como um meio de dizer quantas mensagens podem estar em "espera por confirmação" antes que novas mensagens sejam enviadas.

Solicitar informação de problema – PODE ser definido no CONNECT.

Usada para informar ao servidor que queremos receber a string de motivo e propriedades do usuário em caso de falhas. Se não definido, o broker pode enviá-las.

Solicitar informação de resposta – PODE ser definido no CONNECT.

Faz parte da interação "solicitação/resposta" através do MQTT em vez da interação "publicação/assinatura" comum. Se não definido, informamos ao broker que não queremos que ele envie informações de resposta. O broker tem permissão para não enviar informações de resposta, mesmo que as solicitemos. Se a propriedade estiver ausente é porque não há um valor padrão definido.

Informação de resposta – PODE ser definida no CONNECT.

Faz parte da interação "solicitação/resposta" através do MQTT em vez da interação "publicação/assinatura" comum. 

Comentário não normativo

"Geralmente usada para transmitir uma parte globalmente única da árvore de tópicos, parte essa que está reservada para este Cliente, pelo menos durante a duração de sua sessão. Muitas vezes, não é apenas um nome aleatório, já que tanto o Cliente solicitante quanto o Cliente respondente devem ser autorizados a usá-lo. A propriedade pode ser usada como a raiz da árvore de tópicos para um Cliente específico. Para que o Servidor retorne essa informação, ele geralmente precisa ser configurado corretamente. Usar este mecanismo permite que a configuração seja feita uma vez no Servidor e não, em cada Cliente".

Tópico de resposta – PODE ser definido no CONNECT ou PUBLISH. 

Faz parte da interação "solicitação/resposta" através do MQTT em vez da interação "publicação/assinatura" comum. Quando incluído, o broker interpreta a mensagem Will como uma solicitação. Ao contrário do filtro de tópicos usado nos pacotes SUBSCRIBE, o tópico de resposta não pode conter caracteres curinga.

"Mensagem de solicitação - é uma ‘application message’ com ’response topic’".

Assim, esta propriedade caracteriza a ‘application message’ como parte da interação solicitação/resposta.

Retenção disponível – PODE estar presente no CONNACK.

A propriedade retenção disponível informa ao nosso cliente se o broker suporta mensagens retidas. Se ausente, as mensagens retidas estão disponíveis.

Manter atividade do servidor – PODE estar presente no CONNACK.

A propriedade ‘server keep alive’ tem prioridade sobre a propriedade ‘keep alive’ solicitada no CONNECT. Se a propriedade não estiver no CONNACK, podemos usar ‘keep alive’. Caso contrário, as regras ‘server keep alive’.

Referência do servidor – PODE estar presente no CONNACK ou DISCONNECT.

Informa nosso cliente sobre o redirecionamento do servidor. Pode se referir a um redirecionamento temporário ou permanente. Em ambos os casos, o outro servidor pode já ser conhecido pelo nosso cliente ou será indicado por meio desta propriedade.

Comentário não normativo

Exemplos de ‘server reference’:

myserver.xyz.org

myserver.xyz.org:8883

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

O broker tem permissão para nunca enviar esta propriedade, e nosso cliente tem permissão para ignorá-la.

Intervalo de expiração da sessão – PODE ser definido no CONNECT.

Determina quanto tempo a sessão será mantida após a desconexão. Se o parâmetro não for definido ou estiver ausente, a sessão termina quando a conexão é encerrada. É possível estabelecer um tempo de vida ilimitado da sessão, definindo este valor como UINT_MAX. Devemos manter o estado da sessão se esta propriedade for maior que zero. Podemos verificar isso mediante sinalizador ’session present’ no CONNACK.

Esta propriedade pode ser útil quando a conexão de rede é intermitente, para permitir que nosso cliente retome a sessão sempre que a conexão de rede for restabelecida.

Assinatura compartilhada disponível – PODE estar presente no CONNACK.

Informa nosso cliente se o broker suporta assinaturas compartilhadas. Se a propriedade estiver ausente, então é suportada.

Identificadores de assinaturas disponíveis – PODE estar presente no CONNACK.

Informa nosso cliente se o broker suporta identificadores de assinatura. Se a propriedade estiver ausente, então é suportada.

Máximo de pseudônimos de tópicos – PODE ser definido no CONNECT e PODE estar presente no CONNACK.

Informa o broker sobre o número máximo de pseudônimos de tópicos que nosso cliente está disposto a aceitar para essa conexão específica. Se a propriedade for zero ou não estiver estabelecida, o broker não enviará pseudônimos de tópicos nessa conexão. O inverso também é verdadeiro: se esta propriedade estiver ausente no CONNACK ou presente mas com valor zero, nosso cliente não deve enviar pseudônimos de tópicos.

Assinatura curinga disponível – PODE estar presente no CONNACK.

Se a propriedade não for estabelecida (igual a zero), o broker não suporta assinaturas com caracteres curinga. Nesse caso, o broker se desconectará (DISCONNECT) após receber uma assinatura (SUBSCRIBE) com uma solicitação curinga (wildcard subscription). No entanto, mesmo que o broker suporte esta função, ele tem permissão para rejeitar uma solicitação específica de assinatura curinga e retornar um SUBACK com o mesmo código de motivo 0xA2 (wildcard subscriptions not supported, assinaturas curinga não suportadas). Se a propriedade estiver ausente no CONNACK, o broker suporta essa função.

Intervalo de atraso Will – PODE ser definido nas propriedades Will da carga útil de CONNECT.

Estabelece o atraso, em segundos, que o broker deve observar antes de enviar a mensagem Will. Esta propriedade é particularmente útil para prevenir o envio de mensagens Will em conexões de rede instáveis.


Propriedades de publicação
PUBLISH Indicador de formato de carga útil (Payload Format Indicator), intervalo de expiração da mensagem (Message Expiry Interval), pseudônimo de tópico (Topic Alias), tópico de resposta (Response Topic), dados de correlação (Correlation Data), propriedade do usuário (User Property), identificador de assinatura (Subscription Identifier), tipo de conteúdo (Content Type)
PUBACK String de razão (Reason String), propriedade do usuário (User Property)
PUBREC String de razão, propriedade do usuário
PUBREL String de razão, propriedade do usuário
PUBCOMP String de razão, propriedade do usuário

Tabela 2. MQTT v5.0 - Propriedades agrupadas por funcionalidade - propriedades de publicação

Pseudônimo de tópico – PODE ser definido em PUBLISH

Pseudônimo de tópico é também uma das novas funcionalidades do MQTT v5.0. Permite que o broker ou o cliente reduza o tamanho dos pacotes substituindo o nome do tópico por um pequeno número inteiro - um pseudônimo (alias). A redução do tamanho pode ser significativa, pois os nomes dos tópicos podem ter até 65,535 bytes (UINT_MAX).

Dados de correlação – PODE ser definido em PUBLISH e nas propriedades Will

Faz parte da interação "solicitação/resposta" através do MQTT em vez da interação "publicação/assinatura" comum. O valor da propriedade é importante apenas para o aplicativo (broker e clientes). São dados binários usados na interação solicitação/resposta "pelo remetente da mensagem para identificar a qual solicitação a mensagem de resposta pertence quando recebida".

Tipo de conteúdo – PODE ser definido em PUBLISH e nas propriedades Will

Também pode ser usado em CONNECT para definir o tipo de conteúdo da mensagem Will. 

O broker verifica apenas a codificação da própria propriedade. O valor da propriedade depende do cliente.


Propriedades de assinatura/desassinatura
SUBSCRIBE Identificador de assinatura (subscription identifier), propriedade do usuário (user property)
SUBACK String de razão (reason string), propriedade do usuário
UNSUBSCRIBE Propriedade do usuário
UNSUBACK  String de razão, propriedade do usuário

Tabela 3: MQTT v5.0 - Propriedades agrupadas por funcionalidade - propriedades de assinatura/desassinatura

Identificador de assinatura – PODE ser definido em SUBSCRIBE

É um identificador numérico que pode ser definido em SUBSCRIBE. Será retornado pelo broker na mensagem que permite ao(s) cliente(s) identificar qual assinatura(s) levou à entrega da mensagem. Pode variar de 1 a 268,435,455. NÃO DEVE ser definido como zero e NÃO DEVE ser usado em publicações (PUBLISH) do cliente para o servidor.


Propriedades de autenticação
AUTH
Método de autenticação (authentication method), dados de autenticação (authentication data), string de razão (reason string), propriedade do usuário (user property)

Tabela 4. MQTT v5.0 - propriedades agrupadas por funcionalidade - propriedades de autenticação

Não é surpresa que essas propriedades também possam ser usadas em conexões.

Método de autenticação

Além da autenticação básica de rede usando nome de usuário e senha, o MQTT v5.0 suporta "autenticação avançada". Esta propriedade informa o método escolhido. O método é selecionado pelos desenvolvedores do aplicativo. O broker informará se o método é suportado.

O método de autenticação geralmente representa um mecanismo SASL (simple authentication and security layer, camada simples de autenticação e segurança), e usar tal nome registrado facilita a comunicação. No entanto, o método de autenticação não está limitado ao uso de mecanismos SASL registrados.

Dados de autenticação

Esta propriedade é usada pelo cliente e pelo broker para trocar dados de autenticação de acordo com o método de autenticação escolhido.


Como lemos as propriedades do MQTT v5.0 em MQL5

Até agora, nas partes anteriores desta série, trabalhamos com configurações "para cada sessão", definidas usando sinalizadores de bits, nomeadamente, sinalizadores de conexão em CONNECT, código de motivo em CONNACK e o sinalizador CONNACK session present. Essas configurações são lidas/escritas/armazenadas uma vez por sessão. No entanto, com as propriedades, a situação é diferente. Elas fazem parte da ‘application message’ e podem conter grandes volumes de dados importantes em alguns perfis de aplicações. Assim, nosso cliente deve estar pronto para ler e escrever propriedades continuamente.

Para escrever um teste para a leitura de propriedades enviadas pelo broker, precisamos de um exemplo de array de bytes. Começaremos com um exemplo de array de bytes para o pacote CONNACK, já que este é o primeiro pacote com o qual nosso Cliente irá interagir. Como todos os pacotes de controle MQTT, ele tem um cabeçalho fixo de dois bytes e um cabeçalho variável de dois bytes, que consiste em um byte para os sinalizadores ‘connect acknowledge’ e um byte para o código ‘connect reason’. As propriedades são o último campo no pacote CONNACK. Elas não têm identificador de pacote nem carga útil.

MQTT 5.0 - estrutura do pacote CONNACK

Fig. 02. Estrutura do pacote CONNACK no MQTT 5.0

Pelo padrão, sabemos que:

"O conjunto de propriedades consiste em um comprimento de propriedade (property length), seguido pelas propriedades".

Também sabemos que:

"O comprimento da propriedade é codificado como um inteiro de bytes variáveis (variable byte integer). O comprimento da propriedade não inclui os bytes usados para sua codificação, mas inclui o comprimento das propriedades. Se não houver propriedades, isso DEVE ser indicado incluindo um comprimento de propriedade igual a zero".

Assim, o comprimento remanescente do cabeçalho fixo e o comprimento da propriedade, codificados como um inteiro de bytes variáveis, são as primeiras peças de informação que precisamos ler antes de acessar as propriedades. Se o comprimento da propriedade for zero, não há nada para ler.

Assim, nosso exemplo de array de bytes pode parecer o seguinte para um CONNACK sem propriedades:

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

onde X é o comprimento restante do cabeçalho fixo. O algoritmo para decodificação de um inteiro de byte variável é fornecido pelo padrão. Em MQL5, este algoritmo pode ser expresso assim:

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

onde buf[idx] representa "o próximo byte do fluxo".

Embora o algoritmo de decodificação de inteiros de byte variável seja fornecido pelo padrão, também escrevemos um teste muito simples para ele, apenas para garantir que, nesta fase, a implementação funcione como esperado:

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, para fins de teste, o valor do comprimento restante será codificado rigidamente. Para o CONNACK acima mencionado sem quaisquer propriedades, seria:

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

Um exemplo de array de bytes para o CONNACK com uma propriedade de byte único, configurada como o indicador de formato de carga útil em codificação UTF-8, poderia parecer algo assim:

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

Como você pode ver, verificar a presença de propriedades em CONNACK é bastante simples. Nós só precisamos ler o quinto byte, que contém o comprimento da propriedade. Se não for zero, temos propriedades. Nosso primeiro teste se parece com isso:

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

Consulte o artigo anterior para ver testes de métodos protegidos, e também dê uma olhada no código anexado para ver um exemplo de teste falho.

A primeira implementação, suficiente para passar nos testes nesta fase, é a seguinte:

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

Aqui usamos operador ternário para minimizar o código.

Devemos levar em conta que a posição do byte de comprimento da propriedade depende do tipo de pacote. Isso ocorre porque, embora as propriedades sejam sempre o último campo no cabeçalho variável, existem pacotes que requerem um identificador de pacote de dois bytes antes do comprimento da propriedade. No CONNACK isso não é um problema.

Você pode dizer: "Então este código não funcionará para outros tipos de pacotes!" Sim, você estaria correto. Mas lembre-se de que aqui estamos usando a abordagem TDD. Uma das principais vantagens dessa abordagem é que podemos nos concentrar na tarefa que temos em mãos, em vez de tentar lidar com todos os problemas futuros nas fases iniciais de desenvolvimento. Vamos tratar com outros tipos de pacotes quando chegar a hora e nosso teste falhar. Depois disso, reescreveremos nosso teste(s), eventualmente fazendo refatoração do código.

Inicialmente isso pode parecer um pouco ilógico, mas depois você não conseguirá escrever código de outra maneira. Essa abordagem simplifica o trabalho e até o torna prazeroso. Aliás, na próxima parte desta série, começaremos a escrever e ler as propriedades dos pacotes PUBLISH. Portanto, fique atento a mais atualizações!

Se o comprimento da propriedade não for zero, podemos procurar o identificador da propriedade no próximo byte. O identificador da propriedade nos informa o tipo de dados da propriedade.

"A propriedade consiste de um identificador, que define seu uso e o tipo de dados, seguido pelo valor".

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

O tipo de dados nos informa quantos bytes precisamos ler. O tipo de dados pode ser:

Um inteiro de um byte

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

Um inteiro de dois bytes

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

Um inteiro de quatro bytes

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

Inteiro de byte variável (apenas para o identificador de assinatura) 

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);
  }

Ler/decodificar (e escrever/codificar) essa propriedade requer muito mais dados do que este teste verifica agora.

"Um inteiro de byte variável é codificado usando um esquema que usa um byte para valores até 127. Valores maiores são tratados da seguinte maneira. Os sete bits menos significativos de cada byte codificam os dados, e o bit mais significativo é usado para indicar se há mais bytes no representação. Assim, cada byte codifica 128 valores e o 'bit de continuação'" (destacado por mim).

Nós lidaremos com as propriedades ‘variable byte integer’ na implementação de pacotes SUBSCRIBE, pois são usadas apenas para a propriedade ‘subscription identifier’. Também existem os seguintes três tipos de dados: strings em codificação UTF-8, dados binários e pares de strings UTF-8. Eles serão descritos em detalhes no contexto de uso de solicitação/resposta e implementação do caso especial da propriedade do usuário.

Strings em codificação UTF-8 têm um prefixo de seu comprimento.

"Cada uma dessas strings é prefixada com um campo de comprimento de um inteiro de dois bytes, que indica o número de bytes na própria string em codificação UTF-8, como mostrado na Figura 1.1 Estrutura de strings em codificação UTF-8 abaixo. Assim, o tamanho máximo de uma string em codificação UTF-8 é de 65.535 bytes. Se não especificado de outra forma, todas as strings em codificação UTF-8 podem ter qualquer comprimento no intervalo de 0 a 65.535 bytes".

MQTT-v5-utf8-encoded-strings-structure-OASIS

Fig. 03. MQTT 5.0 - Estrutura de strings em codificação UTF-8 - Captura de tela da tabela OASIS

Strings em codificação UTF-8 devem ser verificadas para a presença de pontos de código Unicode proibidos (mais sobre isso mais tarde).

"A seção 1.6.4 descreve os pontos de código Unicode proibidos que não devem ser incluídos em uma string em codificação UTF-8. A implementação do Cliente ou Servidor pode escolher verificar se esses pontos de código não são usados em strings codificadas em UTF-8, como o nome do tópico ou propriedades".

Dados binários também têm um prefixo de seu comprimento.

"Dados binários são representados por um inteiro de dois bytes, que indica a quantidade de bytes de dados, seguido por essa quantidade de bytes. Assim, o comprimento dos dados binários é limitado de 0 a 65.535 bytes".

Continuamos a contar o número de bytes lidos para saber quando todas as propriedades foram lidas. Não precisamos nos preocupar com a ordem das propriedades.

"A ordem das propriedades com diferentes identificadores não importa".


Como as propriedades podem ser usadas para expandir o protocolo

Como mencionado no início deste artigo, as propriedades são parte do "mecanismo de extensibilidade" do MQTT 5.0, e a propriedade mais importante desse mecanismo é a propriedade do usuário (user property), que pode ser usada em qualquer pacote de controle MQTT. As propriedades do usuário são pares de chave-valor, cujo significado é opaco para o protocolo. Ou seja, seu valor é definido pelo aplicativo.

Vamos imaginar um cenário de uso em nosso domínio: um receptor copia sinais de negociação de três diferentes fornecedores. Cada fornecedor usa diferentes brokers. Cada broker pode atribuir diferentes nomes simbólicos ao mesmo ativo, digamos, ouro.

  • Broker A usa GOLD
  • Broker B usa XAUUSD.
  • Broker C usa XAUUSD.s

Além disso, cada fornecedor de sinais pode usar mais de um broker. Assim, o par fornecedor_de_sinais/broker_do_fornecedor pode mudar a qualquer momento, mesmo durante uma sessão de negociação. (Sim, aqui observamos uma explosão quase combinatória). O receptor precisa saber (idealmente em milissegundos) o valor do nome do símbolo que está recebendo para poder convertê-lo no nome do símbolo que seu broker usa para reproduzir corretamente a ordem de negociação.

Sem as propriedades do usuário, como nas versões anteriores do protocolo, esses metadados (fornecedor_de_sinais/corretora_do_fornecedor) teriam que ser embutidos na carga útil, onde se esperaria encontrar (e analisar) apenas os dados necessários dos sinais de negociação.

Em contrapartida, se cada fornecedor de sinais tem sua própria propriedade de usuário com o nome de seu broker, a carga útil pode conter apenas os dados necessários do sinal.

Este é um exemplo simplificado de tal uso. Mas esses metadados podem ser expandidos para qualquer informação importante, incluindo strings JSON/XML e até arquivos inteiros. As possibilidades são, de certa forma, ilimitadas.


Considerações finais

Na quarta parte de nossa série de artigos, apresentamos uma breve descrição das propriedades do MQTT v5.0, sua semântica e alguns usos. Também examinamos sua implementação para CONNACK, e demos um exemplo simples de como podem ser usadas para expandir o protocolo. Na próxima parte, aplicaremos essas propriedades no contexto dos pacotes PUBLISH, sempre usando a abordagem TDD para lidar com a complexidade das especificações.

Se você puder contribuir para o desenvolvimento de seu próprio cliente MQL5 que se tornará parte do CodeBase, por favor, deixe um comentário ou escreva no chat. Qualquer ajuda é bem-vinda! :)


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13651

Algoritmos de otimização populacional: Busca em sistema carregado (Charged System Search, CSS) Algoritmos de otimização populacional: Busca em sistema carregado (Charged System Search, CSS)
Neste artigo, vamos explorar outro algoritmo de otimização inspirado pela natureza inanimada, a busca em sistema carregado (CSS). O objetivo deste artigo é apresentar um novo algoritmo de otimização baseado nos princípios da física e mecânica.
Desenvolvendo um sistema de Replay (Parte 46): Projeto do Chart Trade (V) Desenvolvendo um sistema de Replay (Parte 46): Projeto do Chart Trade (V)
Cansado de perder tempo procurando aquele arquivo, que é preciso para fazer a sua aplicação funcionar ?!?! Que tal embutir tudo no executável ? Assim você nunca irá perder tempo procurando as coisas. Sei que muitos fazem uso, exatamente daquela forma de distribuir e guardar as coisas. Mas existe uma maneira bem mais adequada. Pelo menos no que diz respeito a distribuição de executáveis e armazenamento dos mesmos. A forma que irei explicar aqui, pode vim a lhe ser de grande ajuda. Já que você pode usar o próprio MetaTrader 5 como sendo um grande ajudante, assim como o MQL5. Não é algo lá tão complexo, ou difícil de ser entendido.
Redes neurais de maneira fácil (Parte 62): uso do transformador de decisões em modelos hierárquicos Redes neurais de maneira fácil (Parte 62): uso do transformador de decisões em modelos hierárquicos
Nos últimos artigos, exploramos várias formas de usar o método Decision Transformer. Ele permite analisar não só o estado atual, mas também a trajetória de estados anteriores e as ações realizadas neles. Neste artigo, proponho que você conheça uma forma de usar este método em modelos hierárquicos.
Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI (Parte 5): Escolhendo o Algoritmo do agente Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI (Parte 5): Escolhendo o Algoritmo do agente
Este capítulo da série aborda algoritmos de aprendizado por reforço, focando em Q-Learning, Deep Q-Network (DQN), e Proximal Policy Optimization (PPO). Explora como essas técnicas podem ser integradas para melhorar a automação de tarefas, detalhando suas características, vantagens, e aplicabilidades práticas. A seleção do algoritmo mais adequado é vista como crucial para otimizar a eficiência operacional em ambientes dinâmicos e incertos, prometendo discussões futuras sobre a implementação prática e teórica desses métodos.