# Última atualização: 2004-01-31 Protomake Versão 0.2.0 por Igor Cananéa, Thiago Souto Maior, Fábio Guerra e Rodrigo Cavalcanti. Copyright 2004 Em poucas palavras, Protomake é um compilador de protocolos que irá evoluir para se tornar um ambiente de desenvolvimento de protocolos. A intenção dessa ferramenta é facilitar a criação de novos protocolos da camada de aplicação (Application Layer) que possam funcionar através de UDP e/ou TCP. Protomake foi criado inicialmente como uma extensão do compilador APC[1] (Austin Protocol Compiler) para suportar TCP. APC usa uma linguagem chamad TAP (Timed Abstract Protocol) e só cria protocolos da camada de aplicação que funcionam com UDP. Á linguagem extendida demos o nome de ETAP (Extended TAP) e criamos o nosso compilador do zero para essa linguagem. Já é possível criar clientes/servidores com o APC, ao contrário do Protomake que ainda não possui todos os recursos mínimos para que isso seja possível. HISTÓRICO: - 2004-01-28: Versão 0.2.0 * Geração do esqueleto dos behaviors usados no protocolo. Apenas em C, por enquanto. Plugin não reconhece campos de string ainda. * Criação de 3 novos tipos para as mensagens: str(N), str(CTE) e str(SEP). Detalhes em ESPECIFICAÇÃO DAS MENSAGENS. Com esses novos campos é possível especificar mensagens que dependam de campos de strings. - 2004-01-10: Versão 0.1.1 * Gramática da linguagem foi alterada. As palavras chaves server e client, usadas para qualificar processos, foram removidas e foram incluidos dois novos tipos de dados: client_address e server_address, usados para especificar conexões TCP. O tipo address agora identifica canais UDP. - 2004-01-05: Versão 0.1.0 * Geração de plugin para visualização das mensagens do protocolo no sniffer ethereal. O plugin gerado é bem simples porém útil o suficiente. * Inclusão de opção na linha de comando. Iniciando protomake com a opção -h mostra todas as opções disponíveis. - 2003-09-20: Versão 0.0.2 * Geração de código em Java. Código gerado funcionalmente igual ao gerado em C. - 2003-08-27: Versão 0.0.1 Primeira versão do Protomake usado como projeto da disciplina de Redes 2 da Universidade Federal de Pernambuco (UFPE). * Geração do código em C * Funções para serialização e deserialização das mensagens. * Reconhecimento de mensagens através de um código inserido no cabeçalho. FUNCIONALIDADES ATUAIS * Geração de código funcionalmente equivalente nas seguintes linguagens: - C - Java * Funções/Métodos para serializção e deserialização das mensagens especificadas. * Geração do esqueletos das funções/métodos usados através de behaviors * Geração de um plugin para o sniffer ethereal para visualização das mensagens. Útil para depurar programas que usem o protocolo. O QUE PRECISA SER FEITO * Gerar a máquina de estados do protocolo especificado. * Mudar a forma de especificar o plugin. * Fazer um plugin mais complexo (melhorar a visualização das mensagens) * Implementar imports * Melhorar o mecanismo de reconhecimento das mensagens. Atualmente não seria possível criar um cliente/servidor para um protocolo conhecido pois o campo a mais inserido para auxiliar a identificação das mensagens a mensagem especificada. Isso implica em mudar a forma como o plugin reconhece as mensagens também. * Melhorar a forma de especificação das mensagens para que protocolos que usam strings como campos diferenciadores de mensagens possam ser usados (HTTP, SMTP, ...). O QUE SERIA INTERESSANTE * Uma interface gráfica para a ferramenta. * Gerar código para outras linguagens (Python é a próxima opção). * Ter uma API para facilitar a criação de geradores de código para outras linguages. * Validação de protocolo. * Um ambiente de desenvolvimento integrado (Especificação do protocolo de forma gráfica, compilação, validação e depuração) USANDO O PROTOMAKE Uso: protomake [-j] [-c] [-h] -i file [-d out] Opções: -j Gera código em Java. -c Gera código em C (default). -h Mostra o help. -i file Lê a descrição do protocolo a partir do arquivo file. -d out Gera a saida na pasta out (default = pasta atual). As opções de geração de código podem ser usadas simultaneamente(-j -c). A ordem dos parâmetros é irrelevante. O protomake é uma ferramenta de linha de comando. Para usá-lo, deve-se especificar o protocolo em um arquivo (normalmente com exensão .pm) e invocar o compilador. Exemplos: - Para ler a especificação do arquivo teste.pm e gerar o código no diretório corrente em C: protomake -i teste.pm protomake -c -i teste.pm - Para gerar código em Java: protomake -j -i teste.pm - Para gerar código em C e em Java: protomake -c -j -i teste.pm - Para gerar código no diretório codigo: protomake -i teste.pm -d codigo Se o protocolo for compilado, protomake irá gerar os seguintes arquivos: C: teste_messages.c teste_messages.h behavior_BEHAVIOR1.c ** behavior_BEHAVIOR1.h ** behavior_BEHAVIOR2.c ** behavior_BEHAVIOR2.h ** . . . ** Para cada behavior declarado no protocolo, arquivos desses tipos serão criados. Ver ESPECIFICAÇÂO DE BEHAVIORS. Java: Teste.java Geral: ** plugin/packet-protocol.c *** plugin/Makefile.nmake plugin/Makefile.am ** O diretório plugin é sempre gerado mas os arquivos apenas se o plugin for especificado. Ver ESPECIFICAÇÃO DO PLUGIN *** protocol é especificado no arquivo. Ver ESPECIFICAÇÃO DO PLUGIN O código pode ser compilado diretamente enquanto mas o código C deve ser linkado com a biblioteca libcpm que está no diretório lib. Essa biblioteca possui funções de mais alto-nível para comunicação através de socket e para pthreads (POSIX Threads) em C. LINGUAGEM ETPA é uma linguagem simples baseada em guardas. Uma guarda possui uma condição (expressão booleana, recepção de mensagens ou timeout) e um conjunto de ações que serão executadas caso a condição seja satisfeita. As mensagens são descritas por uma estrutura de campos que podem ter os tipos string, bits ou bytes. O arquivo de especificação possui 5 seções, que devem ser especifcadas nessa ordem: PLUGIN, IMPORTS, MENSAGENS, BEHAVIORS e PROCESSOS, dos quais apenas MENSAGENS E PROCESSOS são obrigatórios. PALAVRAS CHAVES: !@Author !@Email !@Name !@Acrynom !@Port !@Transport act address array begin behavior bits bytes client_address const do end external fi from if import in integer message od of process rcv send skip server_address start stop str tcp timeout to udp OPERADORES: + - * / & | ~ <> <= >= = < > [] [ ] ( ) , : -> . .. := ESPECIFICAÇÃO DO PLUGIN (opcional) O plugin so é gerado se essa descrição estiver no arquivo. Para especificar o plugin, a seguinte construção é usado: !@PALVRA-CHAVE VALOR As palavras chaves válidas são: Author, Email, Name, Acrynom, Port, Transport. A ordem é indiferente. Exemplo: !@Author "Igor Chaves Cananea" !@Email "icc@cin.ufpe.br" !@Name "Protomake Temporary Test Protocol" !@Acrynom "PTTP" !@Port 9986 !@Transport tcp Com exceção de Port (que deve ser um inteiro entre 0-65535) e Transport (que deve ser tcp ou udp), os valores das palvras chaves devem ser strings. Essa seção não é obrigatória mas, caso exista, ao menos as palavras chaves Name, Abbrev, Port e Transport devem ser definidas. Author -> Autor(es) do protcolo. Email -> email do(s) autor(es). Name -> Nome do protocolo. Acrynom -> Abreviatura do protocolo. É usado para dar nome ao arquivo do plugin (packet-protocolo.c; no exemplo, packet-pttp.c). Port -> A porta em que o protocolo ira operar. Transport -> O protocolo de transporte que será usado. O Protomake usa essa informação para gerar o plugin de visualização para o ethereal. Esse formato foi definido antes da criação dos tipos client_address e server_address, quando apenas um protocolo de transporte era possível por protocolo. Portanto, essa forma irá mudar. IMPORTS (opcional) Imports são outros arquivos de especificação que terão suas definições incluídas no arquivo atual. Dever ser especificada da seguinte forma: import "arquivo" onde "arquivo" deve ser um arquivo válido, podendo especificar o caminho relativo ou absoluto do mesmo ("/home/icc/t.pm"; "../f.pm"; "http.pm"). Essa construção não foi implementada ainda, fazendo com que qualquer import seja silenciosamente ignorado pelo compilador. ESPECIFICAÇÃO DAS MENSAGENS Mensagens são defindas como registros onde os tipos de cada campo podem ser bits ou bytes. Ao menos uma deve ser definida. Para especificar uma mensagem: message msg [(in, out)] begin campo1 : n bits [= x], campo2 : n bytes [= "xxxxx"], campo3 : campo1 bytes, campo4 : str(10), campo5 : str("teste"), campo6 : str(/\n/), . . . campoM-1 : n bits [= y], campoM : n bits [= z] end O que estiver entre colchetes é opcional. A mensagem começa com a palavra chave message (que pode ser precedida por external), seguida pelo nome da mensagem (nesse caso msg) e opcionalmente funções para tratamento da mensagem (in, out). Em seguida, a palavra chave begin e os campos das mensagens. A mensagem pode conter quantos campos forem necessários, definidos como: campo : tamanho bits|bytes [ = valor] ou campo : str(N)|str(CTE)|str(SEP), Na primeira forma, caso o tipo seja bits, tamanho deve ser um inteiro entre 1 e 32 e o valor opcional deve ser um inteiro que esteja entre 0 e 2^tamanho-1; caso seja byte, tamanho pode ser um inteiro entre 1 e 2^32-1 ou um campo que foi previamente especificado na mesma mensagem e que tem tipo bit (no caso acima, o tamanho de campo3 será varíavel, dependendo do valor que estiver armazendo em campo1). Se for um inteiro, pode ter um valor opcional que deverá ser uma string. Com exceção do último campo, todos os campos devem terminar com uma vírgula (,). Caso o campo possua um valor, cada vez que a mensagem for recebida, esse campo será checado para assegurar que o valor foi enviado. Caso contrário, a mensagem é descartada. Na segunda forma, todos os tipos representam strings. str(N) é uma string de tamanho N (campo4, no caso acima). N pode ser qualquer expressão válida que resulto em um inteiro. str(CTE) é uma string constante (campo5, no caso acima). CTE deve ser uma string válida (qualquer coisa entre aspas duplas) e, assim como nos tipos bits e bytes que possuem um valor inicial, cada vez que a mensagem for recebida, esse campo será checado para assegurar que a string foi enviado. Caso contrário, a mensagem é descartada. str(SEP) é uma string de tamanho variável terminado por sep (campo6, nesse caso). SEP é um conjunto de caracters entre duas barras inclinidas (/abc/, por exemplo). No exemplo acima, campo6 é uma string terminada por \n (código que indica nova linha). Se o SEP de campo6 fosse /ab./, ele seria uma string terminada por ab. Para reconhecer que mensagem foi recebida, o Protomake acrescenta à cada mensagem um campo de identificação de mensagem. Esse campo é checado quando uma mensagem chega e é utilizado pelo plugin para poder mostrar a mensagem correta no ethereal. Esse campo extra deturpa a mensagem do protocolo e impede que protocolos já estabelecidos possam ser implementados usando o compilador. No entanto, não impede que novos protocolos sejam desenvolvidos. As funções opcionais (in, out) são utilizadas nos buffers de serialização, antes de enviar a mensagem para o socket (out) e quando o buffer de recepção conter alguma mensagem (in). Essas funções são definidas pelo programdor, caso especificadas. O protomake gera apenas o esqueleto delas. ESPECIFICAÇÃO DOS BEHAVIORS (opcional) Behaviors são construções que podem ser usadas como ações de uma guarda do protocolo (ver AÇÕES VÁLIDAS em ESPECIFICAÇÃO DE PROCESSOS). São definidos da seguinte forma: behavior a1, a2, ..., aN behavior b1 behavior c1, c2, ..., cM Mais de um behavior pode ser definido na mesma linha, separados por virgulas. Ele pode ser usado no protocolo, como uma ação, na forma a1.xxxx(expressao), onde a1 é o nome do behavior, xxxx é o nome de uma função pertencente ao behavior e expressao é qualquer expressão válida. Exemplo: a1.processa(msg.campo1 * 100 / 2) a1.troca(a, b) a2.inicializa() . . . Ao declarar um behavior X, qualquer ação do tipo X.xxxx(expressao) passa a ser válido. Protomake irá gerar os arquivos com os esqueletos de todas as funções encontradas no protocolo. Cabe ao programador definir o que as funções fazem. Os tipos dos parâemtros são identificados pelo compilador e a passagem de parâmetros e feita sempre por referência (modificações nos parâmetros passados são visíveis ao processo). Os behaviors não retornam valor. Caso seja necessário retornar um valor, passe uma variável como parâmetro. ESPECIFICAÇÃO DOS PROCESSOS O protocolo é definido por suas mensagens e suas sequências de ações. Para especificar a sequência de ações de um determinado protocolo, utiliza-se a seguinte contrução: processs nome [var|const a1, a2, ..., aN : tipo [= init]] [var|const b1, b2, ..., bM : tipo [= init]] . . . begin guarda -> açoes [guarda -> ações] . . . end A sequência de ações (ou máquina de estados) do protocolo é chamada de process e cada process possui um nome. Todo arquivo de especificação deve conter ao menos um process chamado main (quando import for implementado, isso deixará de ser necessário. Ele deverá existir em ao menos um dos arquivos usados para especificar o protocolo). Esse é o processo principal do protocolo. Qualquer outro processo no mesmo arquivo deve ser explicitamente iniciado com a ação start. Isso permite modelar protocolos que, ao serem compilados, possam tratar mais de um cliente ao mesmo tempo. A especificação de um processo começa com a palavra-chave process seguida de um nome. Em seguida, pode-se especificar variáveis ou constantes que serão usadas no processo. Variáveis de mesmo nome definidos em processos diferentes são canais de comunicação entre eles. O comportamento do canal depende de como ele foi declarado. Por exemplo, supondo q x tenha sido declarado no processo chamador (p1) e no processo chamado (p2). O comportamento do canal x será o seguinte: - Se x é variável em p1 e variável em p2: modificações feitas em qualquer processo serão visíveis a ambos. - Se x é constante em p1 ou p2: modificações em quem o tiver declarado como variável não será visível em quem o tiver declarado como constante. Em qualquer caso, o x é inicializado no processo chamado com o valor que ele possui no processo chamador no momento em que o processo chamado é iniciado. Os seguintes tipos são válidos para variáveis e constantes: - integer - x..y (range, onde x e y devem ser inteiros) - address - client_address - server_address - array[X] of Type (array, onde X deve ser um inteiro e Type um tipo válido) Se declarados como constantes, os tipos integer, range e array devem ser inicializados. Os tipos address, client_address e server_address (daqui pra frente denominados address) não podem ser inicializados. A semântica dada a variável e constante para o tipo address é a seguinte: se um address é variável, ao ser usado numa cláusula rcv, o endereço de quem enviou o dado é colocado no address. Isso é útil para definir protocolos de servidores, onde não se sabe quem vai enviar o dado. Se for constante, o endereço de quem enviou é comparado com o valor que ele possui. Se for o mesmo, o dado é recebido. Caso contrário, ele é descartado. Os valores dos address são preenchidos quando o programa gerado pelo compilador é compilado. Ver COMPILANDO O CÓDIGO GERADO. ----- TERMINAR ----- COMPILANDO O CÓDIGO GERADO Atualmente, o código gerado não é suficiente para gerar um executável. É possível, no entanto, criar uma biblioteca (.a, .so, .lib, .dll, etc...) com as funções de serialização de mensagens e com os behaviors que foram criados. ----- FALAR SOBRE ADDRESS'S!!! ----- O código do plugin deve ser integrado ao ethereal para que ele possa ser usável. Para saber como fazer isso, consulte a documentção do ethereal (o arquivo README.plugins explica como integrar um plugin novo ao ehtereal). É preciso baixar o código fonte do ethereal para que a integração possa ser feita. FALE CONOSCO Dúvidas, críticas, sugestões, código, enviem e-mail para: Fábio Guerra (Yzmurph) (fga@cin.ufpe.br) Igor Cananéa (icc) (icc@cin.ufpe.br, igorcananea@hotmail.com) Thiago Souto Maior (Mouse) (tsmcf@cin.ufpe.br) Rodrigo Araújo (Salsicha) (rca4@cin.ufpe.br) REFERÊNCIAS [1] Austin Protocol Compiler: http://www.cs.utexas.edu/users/mcguire/software/apc/