Decodificando o Protocolo FAST: Exemplos

Depois de toda a explicação sobre a definição do protocolo FAST, eu notei que tinha uma coisa faltando: Exemplos, para que fique mais fácil de entender.

Mesmo aviso do post anterior: Como isso tudo é baseado na minha experiência pessoal, algumas coisas podem estar erradas. Eu tentei o meu melhor para deixar tudo correto, mas eu posso ter entendido alguma coisa errada, ou digitei errado aqui.

Ainda, a medida que eu for encontrando casos que eu acredite sejam interessantes, novos exemplos serão adicionados.

Hello World

Este exemplo é basicamente o mesmo da JetTek mas é realmente simples de explicar, então vamos nós:

Template

<?xml version="1.0" encoding="UTF-8"?>
<templates xmlns="http://www.fixprotocol.org/ns/fast/td/1.1">
  <template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="HelloWorld" id="1">
    <string name="String" id="1">
      <default value=""/>
    </string>
  </template>
</templates>

Dados de Entrada

Bytes:

1110_0000   1000_0001   0100_1000   0110_0101
0110_1100   0110_1100   0100_1111   0101_0111
0110_1111   0111_0010   0110_1100   1110_0100

Processamento

O primeiro byte é o Mapa de Presença. Removendo o bit de parada, temos 110_0000. Esse Mapa de Presença tem um campo que não é descrito no template: O Template ID (ID de Template). Como o primeiro bit está ligado, nós sabemos que o ID está lá. Ainda, tenha em mente que o Template ID é o único campo que nós sabemos que existe até agora; não existe nenhuma outra informação sobre o que é aquele segundo bit no Mapa de Presença -- nós precisamos encontrar qual template vamos usar antes de mais nada.

O próximo byte é lido: 1000_0001. Como mencionado acima, isso é o Template ID. Sendo um inteiro sem sinal (e provavelmente mandatório, mas não me pergunte como isso funciona) e removendo o bit de parada, temos o inteiro "1", que é exatamente o mesmo ID que temos no nosso template; agora nós sabemos quais campos devem ser processados.

O primeiro campo do template é uma string com um valor default. Como o campo usa o operador Default, nós precisamos verificar se o valor está presente nos dados de entrada ou se devemos usar esse valor default. O bit no Mapa de Presença para este campo é 1, o que indica que a string está presente nos dados de entrada e que nós devemos lê-la.

O campo é uma string, então vamos ler todos os bytes até encontrarmos um com o bit de parada. Ainda, sendo string, nós não precisamos "juntar" os bits: cada byte é uma letra da tabela ASCIII. A sequência é 100_10000 (72), 110_0101 (101), 110_1100 (108), 110_1100 (108), 100_1111 (79), 101_0111 (87), 110_1111 (79), 111_0010 (114), 110_1100 (108) e 110_0100 (100). Observe que nós consumimos todos os bites e o último tinha o bit de parada, então terminamos o pacote com a string. Convertendo os bytes usando a tabela ASCII, temos "HelloWorld".

Assim, temos o resultado: Nós recebemos um registro do tipo "HelloWorld" cujo campo com ID "1" (chamado "String") tem o valor "HelloWorld".

Sequências

Vamos expandir nosso exemplo adicionando uma sequência e alguns operadores:

Template

<?xml version="1.0" encoding="UTF-8"?>
<templates xmlns="http://www.fixprotocol.org/ns/fast/td/1.1">
  <template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="HelloWorld" id="1">
    <string name="String" id="1">
      <default value=""/>
    </string>
  </template>

  <template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="SequenceOfSequences" id="2">
    <sequence name="OuterSequence">
      <length name="NoOuterSequence" id="3"/>
      <uInt32 name="GroupID" id="2"/>
      <sequence name="InnerSequence">
        <length name="NoInnerSequence" id="25"/>
        <string name="Username" id="4"/>
        <uInt32 name="ID" id="32" presence="optional">
          <increment/>
        </uInt32>
      </sequence>
    </sequence>
  </template>
</templates>

Apesar do FAST ser criado para trabalhar com FIX e o mercado financeiro, não existe nada que iniba o mesmo de ser usado para outras coisas. O novo template descreve um group de usuários, então nós temos uma lista de grupos e, para cada grupo, uma lista de usuários e seus IDs.

Dados de Entrada

1100_0000   1000_0010   1000_0011   0000_0011
0010_0011   0001_1000   1110_0111   1000_0010
1100_0000   0101_0101   0111_0011   0110_0101
0111_0010   1011_0001   1000_0100   1000_0000
0101_0101   0111_0011   0110_0101   0111_0010   
1011_0010   1111_1111   1000_0001   1100_0000
0101_0101   1011_0001   1111_1111   0000_1000 
1000_0000   1000_0010   1100_0000   1100_1001
1011_0110   1000_0000   0100_1101   1110_0101

Processamento

Como mencionado anteriormente, o primeiro byte, 1100_0000 é o Mapa de Presença do elemento raiz. Existe apenas um bit ligado, o que indica que o Template ID está presente.

Como o Template ID está presente no Mapa de Presença, nós lemos o próximo byte 1000_0010. Como este byte tem o bit de parada, nós paramos de ler. Removendo este bit, nós temos 000_0010, que é "2", e agora sabemos que estamos lidando com o template "SequenceOfSequences".

Agora que temos o template e conhecemos os campos, nós sabemos o que precisamos ler. O primeiro campo do nosso template é uma sequência. A primeira coisa que nós temos em uma sequência (e é a primeira coisa em todas as sequências) é o tamanho desta. Assim, nós lemos o próximo byte, 1000_0011, que é o único byte que precisamos ler. Ele representa um inteiro sem sinal, que é 3, então esta sequência tem 3 registros -- e usando nossa descrição anterior, nós sabemos que temos 3 grupos de usuários.

Um ponto ser visto aqui: Como todos os campos desta sequência não tem qualquer operador, o Mapa de Presença não é necessário e, assim, ele não existe (ou, melhor, nós não devemos tentar ler alguma coisa e assumir que é o Mapa de Presença). Para sequências, o começo de cada registro contém um Mapa de Presença apenas se pelo menos um dos campos na sequência precisa do Mapa. O que não é o caso aqui.

Como não há Mapa de Presença para a sequência "OuterSequence", os próximos bytes são o campo "GroupID". Nós devemos ler os bytes até encontrar o bit de parada; assim, recebemos 0000_0011, 0010_0011, 0001_1000 e 1110_0111. Para cada byte nós removemos o bit de mais alta ordem (o bit de parada) e juntamos tudo em uma única coisa, neste caso, 00_0011 010_0011 001_1000 110_0111 ou simplesmente 0000_0110_1000_1100_1100_0110_0110. Este valor, sendo um inteiro sem sinal, é "6868070".

Aqui é um bom ponto para lembrar que, como o campo é mandatório, isto significa que este é realmente o valor para "GroupID"; se o campo fosse opcional, o valor de verdade seria "6868069".

Agora para o campo "InnerSequence". O primeiro passo é recuperar o número de elementos (o "length" da sequência). Esse é o byte 1000_0010, que é 2. Assim, há dois usuários neste grupo.

Como o "InnerSequence" tem um campo que precisa do Mapa de Presença ("ID" usa o operador Increment, o que indica que ou iremos ler o o valor vindo dos dados de entrada ou iremos incrementar o valor anterior), o primeiro byte depois do tamanho é o Mapa de Presença para este registro. O byte 1100_0000 indica que o primeiro campo que precisa do Mapa de Presença está presente.

Mas ainda não é o momento de usar o Mapa de Presença. O campo logo após o tamanho é "Username", que é uma string mandatória. Strings mandatórias sem operadores estão sempre presentes e nós não precisamos olhar o Mapa de Presença. Assim como fizemos com "String" no exemplo anterior, nós lemos os bytes até encontrar um com o bit de parada, mas não juntamos os mesmos: 0101_0101 (85), 0111_0011 (115), 0110_0101 (101), 0111_0010 (114) e 1011_0001 (49, se removermos o bit de parada), que convertidos pela tabela ASCII nos dá o valor "User1".

Agora é o momento de usar o Mapa de Presença, já que estamos lendo o campo "ID" e ele tem um operador que usa o Mapa. O Mapa de Presença que lemos anteriormente foi 100_0000 (com o bit de parada removido), então sim, o "ID" está presente nos dados de entrada. Nós lemos o próximo byte, 1000_0100, que é "4". Mas tem uma pegadinha aqui: O campo é opcional. Então embora tenhamos lido "4", o valor de verdade é "3" -- se o valor lido fosse "0", significaria que o ID é Null.

Beleza. Terminamos de ler o primeiro registro de "InnerSequence": O usuário "User1" tem ID "3" e pertence ao grupo "6868070". Agora vamos ler o segundo registro.

Não precisamos ler o tamanho de novo, mas precisamos ler o Mapa de Presença deste registro. É o byte 1000_0000 um Mapa de Presença indicando que nenhum dos campos com operadores estão presentes. Mas, de novo, não é hora de usar o Mapa de Presença, porque nós temos que ler o "Username". Os bytes do campo são 0101_0101 (85), 0111_0011 (115), 0110_0101 (101), 0111_0010 (114) e 1011_0001 (50), que gera o valor "User2".

O segundo registro tem um Mapa de Presença vazio (1000_0000) o que indica que o ID não está presente nos dados de entrada. Como o campo tem o operador Increment, nós precisamos pegar o valor anterior -- "3" -- e incrementar em 1> Assim, "User2" tem o ID "4".

E assim termina o "InnerSequence" do primeiro regsitro do "OuterSequence". Agora mais rápido:

Decimais

A parte divertida do FAST é tentar decodificar decimais. Então vamos expandir nosso exemplo de novo.

Template

<?xml version="1.0" encoding="UTF-8"?>
<templates xmlns="http://www.fixprotocol.org/ns/fast/td/1.1">
  <template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="HelloWorld" id="1">
    <string name="String" id="1">
      <default value=""/>
    </string>
  </template>

  <template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="SequenceOfSequences" id="2">
    <sequence name="OuterSequence">
      <length name="NoOuterSequence" id="3"/>
      <uInt32 name="GroupID" id="2"/>
      <sequence name="InnerSequence">
        <length name="NoInnerSequence" id="25"/>
        <string name="Username" id="4"/>
        <uInt32 name="ID" id="32" presence="optional">
          <increment/>
        </uInt32>
      </sequence>
    </sequence>
  </template>

  <template xmlns="http://www.fixprotocol.org/ns/fast/td/1.1" name="SequenceOfSequences" id="3">
    <sequence name="LotsOfDecimals">
      <length name="NoLotsOfDecimals" id="100"/>
      <decimal name="ADecimal" id="101" presence="optional">
        <exponent>
	  <default value="-2"/>
	</exponent>
	<mantissa>
	  <delta/>
	</mantissa>
      </decimal>
    </sequence>
  </template>
</templates>

Dados de Entrada

1100_0000   1000_0011   1000_0011   1000_0000
0010_1010   1010_0010   1100_0000   1000_0000
1100_0000   1111_1100   0000_0111   0100_1111   
1011_1101

Processamento

Os dois primeiros bytes são nossos velhos conhecidos (nesse ponto) Mapa de Presença e Template ID. Eu não vou explicar de novo, porque já foi explicado como estes funcionam, mas uma coisa a ter em mente é que o Mapa de Presença tem apenas o bit do Template ID ligado; é porque o elemento raíz é só uma sequence, que não tem um operador e, portanto, não aparece no mapa.

O próximo byte é 1000_0011, que é o valor de "NoLotsOfDecimals". Nós temos 3 elementos aqui.

Depois nós temos 1000_0000. Esse é o Mapa de Presençá do primeiro elemento da sequência. Como o campo é um Decimal, isso significa que o Expoente (e não o decimal inteiro) não está nos dados de entrada. Como ele não está nos dados de entrada, o valor do Expoente é "-2", que é o seu valor padrão (default). Como o Expoente tem um valor, nós devemos ler a Mantissa.

A Mantissa está nos bytes 0010_1010 e 1010_0010. Nós precisamos destes dois porque o primeiro não tem o bit de parada. Removendo os bits de parada temos 010_1010 010_0010 ou 0001_0101 0010_0010 na base 10 (não queria dizer "em decimal" aqui para não confundir com o tipo de campo). A matemática para o valor de verdade é fazer "mantissa vezes 10 na potência do expoente" ou "m * 10 ^ e", que no nosso caso seria "5410 * 10 ^ -2" ou apenas "54.10". Esse é o nosso primeiro decimal.

Começando o segundo registro, nós lemos 1100_0000 para o Mapa de Presençá. Agora o bit está ligado para o Expoente, então devemos lê-lo. O valor para ele é 1000_0000, que é, na verdade, "0" (quando removemos o bit de parada) mas como o campo é opcional, o Expoente neste caso é Null; campos opcionais são decrementados em 1, a não ser que já sejam 0, e neste caso eles são Null. Mas agora, como o Expoente é Null, a Mantissa também é Null e o Decimal inteiro é Null.

Mapa de Presença do terceiro registro: 1100_0000. Nosso Expoente está presente de novo, no byte 1111_1100. Isto é um "-4", em complemento de 2.

A Mantissa está nos 3 bytes seguintes: 0000_0111, 0100_1111 e 1011_1100. Removendo os bits de parada e juntando tudo, temos "124861". Essa não é a Mantissa, mas um delta do valor anterior para o atual. Assim, nós adicionamos "5410" e "124861" e temos "130271". E fazendo a mágica de "10 na potência", temos "13.0271". Talvez eu devesse ter mencionando isso antes, mas como o valor anterior foi Null -- indicando que não havia um valor -- ele não afetou a forma como nós vemos o valor anterior e usamos o verdadeiro valor anterior do primeiro registro.


Changelog