30 de dezembro de 2010

Sua rede está bem protegida contra invasões ?

Segurança é - ou ao menos deveria ser - uma grande preocupação em empresas de todos os tamanhos. Invariavelmente, a rede das empresas costuma ser a porta de entrada mais comum para invasões de hackers e ataques de pragas virtuais. A partir de um único computador afetado, toda a rede interna pode acabar comprometida, com consequências que vão desde lentidão em sistemas até vazamento de informações sensíveis das empresas.

Por isso, é imprescindível se valer das ferramentas disponíveis para deixar essa porta menos vulnerável. O uso de firewalls, antivírus, softwares de restrição de acesso à internet, configurações específicas em roteadores e outros equipamentos da rede são medidas amplamente adotadas para aumentar a segurança. No entanto, muitas empresas negligenciam ou subestimam os riscos de uma ameaça muito mais próxima : atitudes dos seus próprias funcionários. Mesmo um funcionário bem intencionado pode infectar a rede com um virus ao abrir um email contaminado ou ao usar um pen-drive em seu computador.

Uma das reportagens mais lidas no site da ComputerWorld em 2010 aborda justamente a questão da vulnerabilidade das redes empresariais frente aos novos avanços tecnológicos. São gadgets e outras ferramentas (inclusive softwares) que no geral trazem melhorias de produtividade mas que se não forem utilizadas com bom senso podem acarretar muitas dores de cabeça. A reportagem enumera 10 brechas frequentes que compromentem a segurança da rede e dá dicas de como enfrentá-las. Reproduzo a lista de brechas e as dicas abaixo abaixo. O conteúdo original pode ser acessado neste link.

1. Flash drives (Pendrives): São, disparados, a maneira mais comum de contaminar um ambiente de rede cercado por firewalls. Baratos, portáteis e capazes de carregar muitos dados, ainda são passíveis de passear por diversas máquinas diferentes. Essas características motivaram o desenvolvimento de vírus especialmente para essa mídia. É o caso do worm Conficker, que se espalha assim que o drive é conectado na máquina. Para piorar, a maioria dos sistemas operacionais monta esses dispositivos assim que são ligados .

O que fazer? Modifique as políticas de acesso automático às novas mídias do seus sistemas operacionais.

2. Laptops, notebooks, netbook e tablets: Discretos, portáteis, munidos de sistemas operacionais completos e capazes de operar usando baterias, os gadgets têm, ainda, conexão Ethernet sem fio (alguns têm portas físicas tradicionais também), e podem se comunicar com o ambiente de rede, de subrede etc. Se estiver contaminado, pode, assim que se conectar à rede, procurar por outros participantes que possam ser infectados.

Mas os dispositivos móveis têm outra fragilidade. Mobilidade. Permitir que essas máquinas passeiem livremente para cima e para baixo enquanto carregam dados de cunho confidencial é para lá de perigoso. Sugere-se que os arquivos estejam criptografados e que sejam munidos de recursos que permitam a deleção do sistema de arquivos à distância no caso de um deles “se perder”.

Logo: limite o tipo de informação armazenada nos dispositivos móveis. Dados de login em redes VPN, DV e Wi-Fi não devem ser salvos nos sistemas. Criptografar os arquivos existentes? Sim. Já.

3. Pontos de acesso de rede: essas interfaces providenciam acesso à rede sem fio para qualquer um que queira se conectar e esteja no raio de alcance da antena. Ataques executados por wardrivers - pessoas que passam o dia em vans circulando por cidades na busca por redes abertas - têm sido cada dia mais comuns. Um rede de lojas teve seu sistema invadido dessa maneira. Os dados de transações de clientes, como números de cartões de crédito, endereços, tipo de operação etc. foram parar na mão dos criminosos. Como resultado, os danos chegaram a meio milhão de dólares.

Criptografadas ou abertas, as redes Wi-Fi são, por natureza, frágeis. Existe uma série de modalidades de ataque, que podem comprometer a rede. Mesmo conexões protegidas (WPA ou WPA2) são pouco eficazes quando uma senha robusta não lhes é atribuída.

O que fazer? Separe as redes abertas das corporativas, não dê acesso aos participantes de um ambiente ao outro. Além disso, é interessante usar recursos de autenticação complementares nas redes Wi-Fi. Permissão para determinadas MACs (identificação física do adaptador de rede e único para cada placa) é uma maneira de fazer isso.

4. Mais USB: Se pensa que os flash drives (pendrives) são os únicos dispositivos USB que podem contaminar sua rede, está redondamente enganado. Máquinas digitais, MP3 players, impressoras e até porta-retratos digitais são todos capazes de armazenar arquivos potencialmente danosos à rede. Quem nunca usou uma máquina fotográfica para transportar um arquivo do Word?

Em 2008, a BestBuy, rede de lojistas, informou que fora encontrado um vírus dentro de uma desses porta-retratos digitais, recém-chegado do fabricante.

A solução: Restringir o acesso e a entrada desses dispositivos em seu ambiente de trabalho corporativo. Deixe evidente, o tempo todo, que essas mídias não devem ser conectadas nas máquinas. Há quase três anos, o Departamento de Defesa dos EUA proíbe terminantemente a entrada de qualquer dispositivo USB em suas dependências.

5. Acesso interno: não raramente, os funcionários de empresas têm acesso a ambientes de rede delicados, cheios de arquivos importantes. Se esse for o caso, todas as opções anteriores de infecção se tornam potencialmente aplicáveis. Já viu um colaborador “emprestar” o terminal de trabalho de outro colega enquanto este saiu para almoçar? E quando um empregado pede que outro libere o acesso à rede? Essas coisas são mais comuns que se pensa.

O antídoto: Troque regularmente as senhas. Funcionários só devem ter acesso às áreas de rede que sejam essenciais para seu trabalho. Qualquer pedido de acesso complementar deve ser feito para uma equipe de TI e não a um único encarregado.

6. O trojan humano: Parecidos com a versão digital inspirada no cavalo de Tróia, esses dispositivos bípedes podem vir até as empresas disfarçados de técnicos de manutenção, de limpeza ou similar. Já foram registrados casos em que esses malfeitores conseguiram acesso às salas superprotegidas de servidores.

Talvez seja uma questão cultural, mas poucos de nós impedem a entrada de alguém devidamente identificado, mesmo que seja estranho ao ambiente de trabalho. Depois de ter a entrada garantida, a infecção do ambiente de rede pode acontecer em questão de um minuto.

Como se prevenir? Expor claramente as condições de acesso de prestadores de serviço no ambiente de trabalho e verificar as informações dadas. Jamais confie no feeling.

7. Mídias óticas: Junho de 2010. Um analista de segurança do Exército dos EUA foi preso depois de roubar e publicar informações confidenciais em círculos públicos. Fontes informam que o sujeito entrou no ambiente trazendo consigo CDs de cantores populares; tudo farsa. Uma vez sentado nas máquinas, o criminoso acessou os dados que interessavam e os gravou no “CD de música” que ouvia enquanto trabalhava. Dizem, ainda, que o rapaz assoviava os sucessos que curtia enquanto roubava os dados.

Essa forma de crime é absolutamente comum. Mídias portáteis são uma maneira de transportar informações de um lugar para outro e, às vezes, isso dá problemas. Além de os drives de CD serem usados para vazar dados, é importante lembrar que também são portas de entrada de vírus, trojans etc.

Como sair dessa? Da mesma forma que lida com os dispositivos USB: restringindo seu acesso e esclarecendo as políticas aos funcionários – constantemente.

8. A mente humana: Se muitas das opções descritas até agora têm por objetivo mitigar as ameaças que pairam sobre as redes corporativas, é absolutamente necessário lembrar que a criatividade e a capacidade da mente humana em guardar informações é quase ilimitada. Será que tem alguém de olho no que você digita quando vai realizar o login em uma rede? Onde você costuma armazenar os dados importantes? É de seu costume ler documentos confidenciais quando está esperando em aeroportos ou em um café?

Como se prevenir? A melhor saída é a prevenção. Levante a cabeça enquanto visualiza conteúdo classificado como confidencial e seja cauteloso ao entrar em redes públicas.

9. Smartphones e outros gadgets: Os celulares já não são mais como eram antigamente. Hoje em dia, carregam câmeras de alta resolução e portas de conectividade de todas as espécies e tamanhos. Os sistemas operacionais também podem dar conta de vários processos ao mesmo tempo e suportam uma vasta gama de aplicativos.

Além disso, esses dispositivos têm entrada permitida em vários ambientes corporativos em que são tratados assuntos delicados, muitas vezes estratégicos. As ameaças representadas por esses gadgets são as mesmas que se observa em laptops e notebooks.

O que impede um usuário de smartphone capturar uma imagem que revele informações importantes e transmiti-la via rede 3G?

A solução para os smartphones é mesma que serve para os dispositivos USB e às mídias óticas. Esclareça aos funcionários em que condições esses dispositivos podem entrar no ambiente corporativo.

10. E-mail: Originalmente ele serve para otimizar o fluxo de trabalho e realizar a comunicação entre pares envolvidas em um processo. Mas isso é teoria. O e-mail é muito usado para enviar e receber dados. Dados estes que podem ser anexados em uma mensagem sem que se tenha certeza da idoneidade de quem os recebe.

As caixas de entrada são, ainda , poderosas portas de entrada para viroses binárias. Se conseguirem entrar na conta de e-mail, podem roubar dados de acesso e levar a um segundo ataque, de proporções hercúleas.

Tem jeito de evitar? O nome da solução é “identificação de origem”. Atribuir uma identidade a quem enviou a mensagem, usando soluções como PGP, ou uma simples rotina de perguntas antes de enviar informações confidenciais deve dar conta da questão.

Também vale restringir o banco de dados de e-mails que podem receber mensagens a partir do serviço interno. Como sempre, educação é palavra-chave: deixe claro na organização que o uso de email deve ser feito com olhos voltados à segurança.


22 de dezembro de 2010

Detectando memory leaks em seus programas Delphi/C++ Builder

Umas das grandes discussões entre defensores das linguagens de código gerenciado (como C# e Java) e defensores das linguagens puras, isto é, não gerenciadas (como C, C++ e Delphi para Win32) é justamente a diferença mais marcante entre os dois conjuntos de linguagens : o gerenciamento da memória usada pelas variáveis e objetos nos programas.

Nas linguagens gerenciadas, o programador não precisa se preocupar em comandar a liberação da memória usada por objetos que ele tenha criado num programa pois o Garbage Collector faz isso de forma automática quando o objeto sai de escopo, ou seja, quando ele não está mais sendo utilizado. A vantagem é que sempre toda memória alocada pelo programa será devolvida ao sistema operacional quando ele terminar. A contrapartida é que haverá uma thread monitorando continuamente a execução do programa (com possível impacto em performance) além do fato de não se ter controle sobre o momento em que o destrutor do objeto será de fato chamado.

Por outro lado, nas linguagens não gerenciadas é responsabilidade do programador desalocar qualquer memória que ele tenha alocado em seu programa. Como esse é um procedimento manual, eventualmente pode escapar alguma memória sem ser devolvida, o que, em situações extremas, pode resultar até na queda do sistema operacional. Mas, este método produz programas mais rápidos e o programador tem total controle sobre o instante em que cada destrutor é invocado.

À parte a discussão sobre qual das abordagens é a melhor, os desenvolvedores C++ Builder e Delphi Win32 têm uma ferramenta para ajudá-los a depurar seus programas e encontrar eventuais falhas no gerenciamento manual de memória. O FastMM é uma biblioteca que substitui o BorlndMM.DLL, que é o gerenciador padrão dos programas feitos nessas duas linguagens. O MM no nome de ambas as bibliotecas é abreviação para Memory Manager, ou Gerenciador de Memória.

Diferente do Code Guard, o FastMM detecta apenas os memory leaks, apresentando os resultados da verificação num relatório gravado ao final da execução do seu programa. Projetos feitas em ambas as linguagens extraem melhores benefícios do FastMM se estiverem compilados para depuração. Com essa opção ligada, o gerenciador consegue apontar com precisão o local onde foi alocada uma variável que não tenha sido removida da memória.

Em C++, o processo de substituição do gerenciador é mais simples pois implica em simplesmente trocar a biblioteca BorlndMM.DLL original por aquela distribuída junto com o FastMM (ou que tenha sido compilada por você mesmo com as configurações personalizadas). A única ressalva a ser feita é que há uma opção de linkagem nos projetos C++ Builder que permitem embutir no binário final o conteúdo do gerenciador de memória, eliminando a necessidade da biblioteca externa. Por isso, substituir a biblioteca só vai funcionar se o projeto estiver compilado para usar o gerenciador dinamicamente. Na versão que eu tenho do C++ Builder, esta opção aparece na página Linker das opções de projeto e tem o nome de Use Dynamic RTL.

Em projetos do Delphi para Win32, as funcionalidades providas em BorlndMM.DLL são sempre linkadas juntas, produzindo um executável ou biblioteca sem essa dependência externa, resultando num processo de substituição um pouco mais elaborado. Neste ambiente, você terá que incluir em seu projeto o fonte do FastMM e garantir que esta unit seja a primeira a aparecer na cláusula Uses do fonte do seu projeto.

Normalmente, não é necessário fazer quaisquer alterações na configuração do FastMM para depurar um projeto. No entanto, se alguma situação exigir, as configurações dele podem ser encontradas no fonte FastMM4Options.Inc, onde há uma lista de opções ricamente comentadas (em inglês) que podem ser ligadas ou desligadas de acordo com suas necessidades. As opções estão espalhadas em seções organizadas pelo tipo de efeito abordado, como por exemplo as vinculadas à melhoria do tratamento do gerenciador em projetos multi threaded (concorrência), as que controlam a forma com que os memory leaks serão reportados, as que compatibilizam o gerenciador com opções específicas do projeto e as relacionadas à depuração do seu programa quando o gerenciador está presente. Apenas lembro que o FastMM4Options.Inc é parte dos fontes do FastMM e que as alterações introduzidas nele exigem que se recompile o projeto para que surtam efetivamente os efeitos desejados.

O FastMM é um projeto open source hospedado no Source Forge, a partir de onde você pode fazer o download tanto das bibliotecas prontas para diversas versões do Delphi e do C++ Builder quanto os códigos fonte para que se possa compilar sua própria versão com um conjunto específico de opções ligadas.

Mais Informações
Site do projeto FastMM

17 de dezembro de 2010

Design Patterns em Delphi - Mediator - parte II

Dada a natureza do problema abordado pelo Design Pattern comportamental Mediator, normalmente a implementação de uma solução com ele resulta numa hierarquia de classes bastante extensa. Basta lembrar que o conceito é aplicável ao conjunto de telas (Forms) de um sistema, onde cada nova tela herdada é um mediador e os componentes inseridos nela podem conversar entre si de maneiras específicas, sem que as classes de componentes necessitem ter conhecimneto umas das outras. O conceito do Mediator foi introduzido num post em novembro, acessível neste link. Apenas para relembrar o básico: o Mediator propõe centralizar em uma única classe base a responsabilidade pela comunicação de todas as outras envolvidas na solução de um problema.

Isto posto, pretendo mostrar aqui a implementação de um exemplo simples, proposto naquele post inicial. É o exemplo da torre de controle de tráfego aéreo, onde a comunicação entre os participantes pode ser implementada de uma forma mais genérica, fazendo com que seja necessária apenas uma herança do mediador. Segue uma reprodução do diagrama UML com o exemplo proposto:
Diagrama UML para o padrão Mediator
Começo a codificação pela interface que estabelece as regras de mediação, isto é, o próprio Mediator. Ela é normalmente assentada como uma classe abstrata a partir da qual todas as possibilidades de mediação são herdadas.
TWFila = class
public
procedure push (obj: TObject);
function pop : TObject;
end;

TWAeronaveAbstrata = class;

TWTorreAbstrata = class
public
procedure concedePermissao(Aeron: TWAeronaveAbstrata); virtual;abstract;
procedure solicitaPouso (Aeron: TWAeronaveAbstrata); virtual;abstract;
procedure solicitaDecolagem (Aeron: TWAeronaveAbstrata); virtual;abstract;
end;

TWTorreAeroportoCGN = class(TWTorreAbstrata)
protected
_FilaPista : TWFila;

public
procedure concedePermissao(Aeron: TWAeronaveAbstrata); override;
procedure solicitaPouso (Aeron: TWAeronaveAbstrata); override;
procedure solicitaDecolagem (Aeron: TWAeronaveAbstrata); override;
end;

No exemplo, há apenas uma herança mas o mais comum é ter que trabalhar com diversas delas, como no caso de se usar telas como mediadores para gerenciar a comunicação entre os elementos de interface visual presentes (os componentes da tela). A classe TWFila é apenas auxiliar, sendo usada aqui para controlar a fila de aeronaves que solicitarem algum procedimento à torre. Num projeto real, é praticamente regra que tenhamos neste ponto instâncias de classes específicas cuja comunicação será mediada, implementando regras de negócio únicas. Por causa disso, cada herança do mediador é normalmente aplicada a um único cenário predeterminado.

A declaração dos Colleagues (objetos cuja comunicação é mediada) também é bem direta:
TWAeronaveAbstrata = class
protected
_Aguardando: boolean;
_Torre: TWTorreAbstrata;
public
constructor Create (ATorre: TWTorreAbstrata);

function aguardarPermissao : boolean; virtual;abstract;
procedure pousar; virtual;abstract;
procedure decolar; virtual;abstract;
procedure liberarPista; virtual;abstract;
end;

TWJatoExecutivo = class(TWAeronaveAbstrata)
public
constructor Create (ATorre: TWTorreAbstrata);

function aguardarPermissao : boolean; override;
procedure pousar; override;
procedure decolar; override;
procedure liberarPista; override;
end;

O único ponto importante a ressaltar é a existência do membro _Torre, que é uma instância da classe mediadora. Isso reforça o conceito de que a comunicação entre os diversos tipos de Colleagues deve ser levada a cabo exclusivamente através do mediador, isto é, nenhum Colleague deve ter conhecimento da existência dos demais. Como estas classes são fracamente acopladas, podemos introduzir novos Colleagues sem prejuízo para as heranças já estabelecidas na solução.

O trecho abaixo mostra um exemplo da comunicação usando o mediador.
procedure TWJatoExecutivo.pousar;
begin
{ Solicita autorização à torre e aguarda permissão }
_Aguardando := true;
_Torre.solicitarPouso ();

While (aguardarPermissao() ) do
Sleep (1000);

{ Com a permissão concedida, a pista é liberada para o procedimento da aeronave seguinte }
liberarPista;
end;

Este é um exemplo bastante simples, com as instâncias das classes do tipo Colleague inseridas numa fila. De novo : no mundo real, teríamos instâncias específicas dessas classes enviando mensagens umas às outras. Por fim, as classes representando aeronaves são muito parecidas entre si e, por isso, inclui neste post a declaração de apenas uma delas, omitindo as demais.

15 de dezembro de 2010

Criando Esquema para validar um XML - parte III

A sintaxe para criação de esquemas XSD é bem mais flexível e poderosa do que os exemplos que mostrei no último post sobre esse assunto. Para tornar mais realista o exemplo da carga de produtos mostrado lá, podemos melhorá-lo agregando novas regras.

O defeito mais patente naquele exemplo é o fato de que apenas um produto por vez pode ser incluído no XML. Numa carga, faz mais sentido aceitar que se informem diversos itens de uma só vez. Como também não é permitido a um XML ter mais de um elemento como raiz, vou acrescentar uma nova raiz - produtos - e reconfigurar a tag de produto:
<xs:element name="produtos">
<xs:complexType>
<xs:sequence>
<xs:element name="produto" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="codigo" type="xs:string"/>
.
.
.
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>

Os atributos minOccurs e maxOccus aplicados ao elemento produto determinam as quantidades mínima e máxima que esse elemento pode aparecer no XML. Caso esses atributos sejam omitidos, eles assumem o valor 1 (um); por isso, omití-los obriga sempre uma ocorrência do elemento. Valores válidos para esses atributos são números inteiros mas no caso do maxOccus podemos informar ainda o texto "unbounded" para indicar que não há limite máximo de ocorrências para o elemento. O trecho de XSD acima exemplifica ainda elementos complexos aninhados uns dentro dos outros, isto é, eles não precisam ser formados apenas por elementos simples.

O minOccurs também aceita o valor 0 (zero), o que na prática significa que o elemento em questão poderá ser omitido do XML - e, ainda assim, ele permanecerá válido. Isso abre a possibilidade de atribuirmos valores padronizados para elementos que não apareçam no XML.
<xs:element name="saldo" type="xs:decimal" minOccurs="0" default="0"/>

No caso acima, se o elemento com o saldo do item não estiver presente no XML, devemos assumir que seu valor é zero.

Os tipos de dados com os quais definimos elementos no XSD constituem um tipo de restrição aos valores que esses elementos podem assumir no XML. No entanto, há situações em que só o tipo não é suficiente para determinar a validade de um dado. Para atender essa necessidade, um esquema XSD dispõe da tag xs:restriction, que, como o nome diz, serve para criar outras restrições aos valores permitidos em elementos simples num XML. A forma mais básica de restrição é a que estipula uma faixa de valores válidos:

<xs:element name="deposito" >
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="99"/>
</xs:restriction>
</xs:simpleType>
</xs:element>

O trecho acima reformula o elemento deposito para que ele aceite apenas códigos entre 1 e 99, sendo que esses valores-limites também são válidos. Repare que, apesar de continuar a ser um elemento simples (não são definidos elementos internos nele), o tipo do deposito agora foi passado para uma estrutura interna, delimitada por uma tag xs:simpleType, dentro da qual são explicitadas as novas restrições. Outros parâmetros válidos para configurar restrições podem ser encontrados neste link.

Outro tipo de restrição bastante comum é a lista de valores permitidos. Ou seja, para o XML ser válido, o valor do elemento nele tem que ser um daqueles listados na tag de restrição do XSD:
<xs:element name="unidade" maxOccurs="5">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="UN"/>
<xs:enumeration value="CX"/>
<xs:enumeration value="KG"/>
<xs:enumeration value="PC"/>
</xs:restriction>
</xs:simpleType>
</xs:element>

Como está exemplificado acima, cada item da lista de possíveis valores é definido através de uma tag xs:enumeration. Obviamente, os valores especificados na lista devem estar de acordo com o tipo base atribuído ao elemento - no caso apresentado acima, devem ser cadeias de caracteres (strings). Vale a pena ressaltar também que letras maiúsculas e minúsculas são consideradas diferentes e isso deve ser respeitado na hora de criar as declarações. Por exemplo, de acordo com os critérios do XSD acima, um valor un ou kg no elemento unidade do XML torna-o inválido visto que está prevista apenas a ocorrência desses valores em letras maiúsculas.

Alguns campos podem apresentar restrição de tamanho para serem válidos. No ERP da ABC71, por exemplo, a descrição de um produto deve ter no máximo 50 caracteres. Uma declaração para contemplar esse tipo de restrição pode ser feita como segue:
<xs:element name="descricao">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>

Além de maxLength e minLength - que definem uma faixa válida de quantidade de caracteres - há o elemento length, que serve para forçar uma quantidade exata de caracteres. Não a usei no exemplo porque isso obrigaria a colocar espaços em branco após as descrições de produto até que todos eles tivessem a quantidade exata exigida.

Há uma outra forma de restrição onde podemos estipular padrões de preenchimento dos campos, padrões estes baseados em máscaras mais ou menos como nas expressões regulares. Volto em outro post para falar sobre isso.

Mais Informações
Criando esquema para validar um xml - parte I e parte II, Elemento xs:restriction

6 de dezembro de 2010

Criando Esquema para validar um XML - parte II

Depois de montar o cabeçalho do Esquema XSD, onde definimos um namespace e outros parâmetros para evitar choque de nomes com outros esquemas, podemos abordar a criação das regras de validação que orientarão como os elementos do XML devem ser criados.

Elementos são definidos num XSD usando-se a tag xs:element, que deve ser inserida entre a abertura e o encerramento da tag de cabeçalho. Um XSD pode definir basicamente dois tipos de elementos para o XML: os que contêm outros elementos aninhados são chamados de Complexos; os demais são chamados de Simples. O nó raiz do XML necessariamente será do tipo Complexo já que sempre haverão outros elementos aninhados nele.

Fica mais fácil trabalhando com um exemplo. Suponha, então, que estamos estabelecendo a estrutura de um XML a ser usado para carga do cadastro de produtos. Neste XML, as informações básicas do produto podem ser definidas no XSD como elementos simples. Segue um trecho extraído do XSD com essas definições:
<xs:element name="codigo" type="xs:string"/>
<xs:element name="deposito" type="xs:integer"/>
<xs:element name="saldo" type="xs:decimal"/>
<xs:element name="data_criacao" type="xs:date"/>

As linhas acima definem 4 elementos simples que devem aparecer no XML de produtos. A cada elemento é atribuído um nome e um tipo de dado - por enquanto, usei apenas tipos preestabelecidos pela W3C. Assim, o XML será considerado inválido se um elemento com qualquer outro nome estiver inserido nele ou se o tipo de dado esperado para cada elemento não corresponder à definição. O trecho de XML reproduzido abaixo está em conformidade com o XSD anterior:
<abc:codigo>000.001-8</abc:codigo>
<abc:deposito>1</abc:deposito>
<abc:saldo>220.34</abc:saldo>
<abc:data_criacao>2010-01-20</abc:data_criacao>

Os tipos complexos do XSD encapsulam declarações como as que fizemos no início deste post. Ao fazer isso, estabelecem também qual a sequência em que os elementos devem estar inseridos no XML. Declarar um elemento do tipo complexo exige o uso de duas novas tags: o xs:complexType - que substitui o atributo "type" usado para declarar os tipos simples - e o xs:sequence - que vai embutido no complexType e que encorpora as declarações dos demais elementos que compõem esse tipo complexo. Veja como fica a declaração do XSD para o exemplo do produto:
<xs:element name="produto">
<xs:complexType>
<xs:sequence>
<xs:element name="codigo" type="xs:string"/>
<xs:element name="deposito" type="xs:integer"/>
<xs:element name="saldo" type="xs:decimal"/>
<xs:element name="data_criacao" type="xs:date"/>
</xs:sequence>
</xs:complexType>
</xs:element>

Repare que a tag inicial é do tipo xs:element pois estamos definindo um novo elemento, ainda que do tipo complexo. O atributo name também é exigido aqui, enquanto o type foi eliminado para permitir que a declaração dos elementos seja feita de forma aninhada. O exemplo mostra apenas elementos de tipo simples aninhados no tipo complexo mas isso não é uma obrigação. Se for necessário, podemos aninhar outros tipos complexos também.

Como não estabelecemos nenhuma regra extra, todos os elementos definidos no nosso XSD tem que aparecer uma e somente uma vez no XML, isto é, esses elementos não podem ser omitidos nem aparecer duplicados. Caso contrário, o XML será considerado inválido.

Além disso, da forma como está declarado neste post, o XSD permite que apenas um produto seja incluído no XML. Num post futuro eu avanço no assunto, introduzindo novos conceitos para melhorar a declaração de tipos e enriquecer as validações, tornando-as mais precisas.

30 de novembro de 2010

Criando Esquema para validar um XML - parte I

Há dois aspectos que devemos observar num XML para podermos considerá-lo válido. O primeiro aspecto é obrigatório e está relacionado com a forma com a qual a estrutura básica do XML está montada, isto é, se os elementos (tags) existentes estão contidos entre um par < e >, se os elementos de abertura estão devidamente fechados, se a abertura e o fechamento dos elementos ocorrem na ordem correta, respeitando a hierarquia interna, etc. Em suma, há uma sintaxe bem definida para a construção de XMLs e eles só serão válidos se forem construídos de acordo com ela. Quando o XML atende este requisito, diz-se que ele está bem formado.

O segundo aspecto é opcional mas constitui uma ferramenta poderosa para garantir que um XML bem formado que tenhamos em mãos atenda um propósito específico. Estamos falando aqui da semântica do XML, ou seja, do significado que o conteúdo dele tem em um determinado contexto. Tendo a certeza de que o XML contém os elementos que esperamos e que os valores inseridos nestes elementos estão condizentes com regras estabelecidas por nós mesmos, o XML poderá ser utilizado sem sustos por uma aplicação que dependa de tais regras para funcionar adequadamente. É, portanto, também uma forma de padronizar a distribuição das informações. A Nota Fiscal Eletrônica é um bom exemplo da utilidade dos esquemas já que todas as notas enviadas à Receita Federal devem estar no formato XML apropriado, conforme a definição contida nos esquemas publicados pela própria Receita.

A validação sintática pode ser feita carregando o XML como uma estrutura DOM - Document Object Model - presente nas linguagens de programação mais usadas atualmente. Já a validação semântica depende da criação de um arquivo externo, construído de acordo com uma estrutura chamada XSD - XML Schema Definition - que serve para descrever a estrutura de um documento XML. Neste arquivo são colocadas regras tais como os nomes dos elementos esperados para o XML, a hierarquia entre esses elementos (isto é, quais nós devem estar inseridos em outro), presença de atributos nos elementos, o tipo de valor aceito pelos elementos, etc.

Um XSD é também um tipo de XML mas, assim como o HTML, tem uma semântica própria que deve ser respeitada. Isso significa que há um conjunto de tags específicas para a construção do XSD, a começar pelo cabeçalho do documento. Segue um exemplo típico de declaração do cabeçalho:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.abc71.com.br"
xmlns="http://www.abc71.com.br"
elementFormDefault="qualified">
.
.
.
</xs:schema>

A tag schema é o nó raiz onde toda a descrição de estrutura de um XML será feita. Mas qual o significado dos atributos que estão acompanhando a declaração da tag ? O primeiro deles - xmlns:xs - indica que a declaração do esquema seguirá as regras definidas no namespace do consórcio W3C, cujo nome é "http://www.w3.org/2001/XMLSchema". Também define um apelido para esse namespace, que passa a ser identificado internamente no XSD como xs. Assim, o prefixo xs é usado para indicar tags pertencentes ao domínio desse namespace, como em xs:schema.

Os dois próximos atributos estão relacionados a nosso próprio namespace. O targetNamespace define o namespace ao qual os elementos de nosso XML estão vinculados, enquanto o xmlns sinaliza o namepace padrão para o XML, isto é, o que deve ser usado quando um não for explicitamente apontado. O conceito de namespace é relativamente simples: a grosso modo, ele isola os nomes usados por nós daqueles usados por outros namespaces. Assim, mesmo que algum dos nomes usados por nós também exista em outro namespace, ambos são considerados diferentes por estarem inseridos em contextos (namespaces) distintos.

O atributo elementFormDefault indica se, quando construirmos um XML segundo esse XSD, os elementos do namespace devem ser qualificados ou não. Isto é, se devemos obrigatoriamente especificar o namespace na declaração de um elemento ou se isso é subentendido.

O XML deve, no entanto, referenciar o namespace no elemento a partir do qual as regras do XSD são aplicadas. Em outras palavras, o XML deve indicar o namespace com o qual está trabalhando de modo que uma validação contra o XSD é possível. Supondo que o nó raiz determinado por nosso XSD tenha o nome de items, a declaração do XML seria:
<?xml version="1.0"?>
<items xmlns="http://www.abc71.com.br">
.
.
.
</items>

Há outros atributos permitidos na tag schema e eles serão apresentados conforme for surgindo a necessidade. Nos próximos posts, tratarei da declaração da sequência de tags e da definição de tipos dentro de um XSD.

20 de novembro de 2010

A Oracle deixará a Sun morrer ?

Desde que a Oracle anunciou a compra da Sun, a imprensa especializada tem publicado várias notas a respeito - muitas negativas - especulando sobre o destino a ser dado às várias tecnologias da empresa, notadamente o Java. Algumas organizações (e a própria comunidade de desenvolvedores) estão incomodadas com as posições da Oracle, que está planejando impor licenciamentos mais restritivos para o uso do Java de código aberto. A Apache está ameaçando até bloquear a aprovação do Java 7 se essas restrições forem levadas a cabo. A saída da empresa de James Gosling, o criador da linguagem Java, levantou especulações sobre as reais intenções da Oracle para a linguagem, pondo mais lenha na fogueira.

Enquanto isso, a Oracle tenta acalmar os ânimos anunciando especificações de novas versões para a linguagem Java. Nesta mesma direção, o projeto do OpenJDK (o SDK de código aberto para o Java) ganhou o apoio de nomes de peso na área de TI, com anúncios de colaboração partindo primeiro da IBM e, depois, da Apple.

Mas muitos autores de blogs também tem expressado seus temores quanto ao futuro de tudo que está relacionado à Sun - não só com o Java. Abaixo segue uma tradução do post publicado por Paul Venezia em seu blog na InfoWorld:
Meu receio de que a Oracle comprasse a Sun apenas para deixá-la morrer está se tornando realidade. Não posso evitar a visão do corpo estendido da Sun inerte enquanto um Larry Ellison com pés de bode fendidos dança ao redor dele, gargalhando. Que tragédia.

Questões óbvias surgem. A primeira é bem simples: O que acontecerá aos bilhões de dólares em hardware Sun espalhados pelo mundo neste momento ? Por experiência própria, a qualidade do suporte técnico despencou. Mais importante, a Oracle não parece preocupada em reter engenherios de hardware e de software da Sun nem seus visionários, de modo que o prognóstico não pode ser bom para a manutenção e o desenvolvimento das linhas de produto de hardware da Sun.

Quando tecnólogos como Adam Leventhal, Bryan Cantrill, Mike Shapiro e Brendan Gregg estão abandonando o barco, há algo terrivelmente errado. Demissões são comuns em qualquer aquisição, mas não há pessoal com as características desses caras na Oracle. Eles são os únicos dessa estirpe e estão deixando um grande vácuo no que antes costumava ser a Sun Microsystems.

Já fui advogado do hardware Sun no passado e até hoje executo e mantenho muitas aplicações em diferentes servidores da Sun. Mas está ficando difícil pensar em comprar algum hardware novo da Sun, dada a apatia mostrada pela Oracle em relação a este lado do negócio.

Pior, a Oracle parece estar arrancando partes vitais da Sun e atirando-as fora. Ela está se livrando da SunSolve e do Member Support Center para trazer tudo para dentro do My Oracle Support. (Entre outros desastres associados ao My Oracle Support, ele aparentemente exige o Adobe Flash, apesar de outras seções do FAQ afirmarem que há um versão em HTML puro também. Que tal se livrar completamente do Flash ?)

Além disso, pelo que vale, eu tentei um miríade de formas para entrar no novo sistema de suporte com as credenciais já existentes. O FAQ afirma que é possível. Infelizmente pra mim, fiquei emperrado, andando em círculos nos termos de aceitação sem conseguir chegar a lugar algum, o que aparentemente tornou inválido meu acesso existente no MSC. Brilhante !

Pergunte a qualquer cliente de longa data da Sun; eles sabem que esses mini-desastres são muito comuns. Dado o que aconteceu até agora, não há razão para esperar que algo mude.

Ao invés de me enclausurar num estado de tristeza, entretanto, eu prefiro especular sobre para onde todos os talentos da Sun estão indo. Meu desejo ardente é que essas pessoas dotadas estejam indo para suas garagens e dando início a novas companhias com um pouco do espírito que antes fez a Sun brilhar.

Os tipos de mentes que produziram o DTrace e o ZFS não são prontamente adaptadas para outro tipo de trabalho. Não as vejo indo para as altas finanças ou para a política - eles vão acabar dando início a suas próprias companhias ou mudando para algumas já existentes, enriquecendo-as com seus profundos conhecimentos. A outra ponta da drenagem de cérebros da Oracle/Sun leva diretamente a empresas iniciantes do Vale do Silício que evocam muitas das ferramentas mais importantes e tecnologicamente mais avançadas que nós temos. Não duvido que um bom número também irá para o Google ou outra grande corporação.

Podemos ver que esse tratamento que a Oracle dá para a Sun atualmente traz avanços inesperados em uma variedade de áreas. Obviamente, é muito mais fácil dividir no software do que no hardware, como já vem acontecendo com a formação do LibreOffice e projetos como o SkySQL e o MariaDB, junto com o Illumos Project que está tentando a ressurreição do OpenSolaris. Diversos desses empreendimentos devem ter sucesso. Então, de novo eles enfrentarão uma batalha pelo topo, competindo com os fantasmas que são suas próprias versões anteriores. Só o tempo dirá.

Não devia ser desse jeito. A Sun não tinha que ser desconstruída assim. Sim, o voo de mentes brilhantes pode espalhar mais e melhor desenvolvimento tecnológico já que a Sun, sob o comando da Oracle, poderia desistir disso - mas ainda não consigo aceitar a desconstrução de uma das mais avançadas companhias de tecnologia criativa que jamais existiu.

O texto original em inglês está disponível neste endereço.

17 de novembro de 2010

Trabalhando com Exceções em Delphi

Construir um programa que implemente regras de negócio previamente projetadas é um processo relativamente simples. As coisas começam a mudar de figura quando percebemos que não basta ir encadeando as regras pois há muitos pontos em que as operações podem falhar, seja porque um valor inválido foi informado pelo usuário, ou porque o banco de dados ficou de repente inacessível, ou um outro motivo qualquer. É natural nos preocuparmos em como o sistema deve funcionar e deixarmos de lado as exceções, isto é, aquelas situações imprevistas que não fazem parte do processo de negócio que estamos modelando para uso no computador.

Por outro lado, incluir tratamento dessas exceções diretamente no código das regras de negócio pode deixar o código complicado, abarrotado de IFs aninhados e outros desvios. Isso torna o código difícil de ser lido e de dar manutenção. Portanto, planejar desde o início de um projeto como é que o sistema deve se portar diante das situações de erro o tornará mais robusto, com um código fonte mais limpo e mais fácil de manter.

As linguagens de programação de hoje têm um mecanismo especial para tratar tais situações: as Exceptions. Com elas, podemos proteger trechos do nosso código de quaisquer tipos de erro. Se um erro for ocasionado dentro do trecho protegido, a execução do código será automaticamente desviada para um ponto que determinarmos, dando a oportunidade de tomar as providências necessárias, tais como fechar arquivos, encerrar uma transação com o banco de dados, devolver memória em uso ou notificar o usuário.

O quadro abaixo mostra a abordagem sem o uso de Exceptions para uma situação hipotética: ler um arquivo XML e importá-lo no banco de dados:
procedure TWNotaFiscal.ImportaXML (pNomeArq: String);
var lXML: TXMLDocument;
lCodCli: TCodCliente;
begin
lXML := TXMLDocument.Create(Self);

if FileExists (pNomeArq) then
begin
lXML.LoadFromFile (pNomeArq);
lCodCli := ExtrairCodCliente (lXML);

if ClienteExiste (lCodCli) then
begin
DB.StartTransaction;
CriaVinculoDbXML (lCodCli, lXML);
DB.Commit;
end
else
MostraErro ('Cliente não existe: ' + lCodCli.AsString);
end
else
MostraErro ('Arquivo não encontrado: ' + pNomeArq);

lXML.Free;
end;

O trecho de exemplo é simples mas ilustra bem como é fácil aumentar a complexidade do código ao encadear o tratamento de erros. Mesmo assim, muitas situações ainda estão desprotegidas. Por exemplo, o que aconteceria se existir um erro de formação no XML ou se o banco de dados estiver fora do ar ? No mínimo, o programa será interrompido sem que tenha chance de aplicar o Free presente na última linha da função, deixando memória sem desalocar.

Em Delphi, podemos interceptar uma exceção de 2 maneiras: usando um bloco try..except..end ou um bloco try..finally..end. Basicamente, no primeiro formato, a linha seguinte ao except é executada somente se uma exceção ocorrer no código entre o try e o except. No outro caso, o código após o finally sempre é executado, mesmo que não tenha ocorrido uma exceção entre o try e o finally.

Reescrevendo o código anterior para usar exceções:
procedure TWNotaFiscal.ImportaXML (pNomeArq: String);
var lXML: TXMLDocument;
lCodCli: TCodCliente;
begin
lXML := TXMLDocument.Create(Self);

try
lXML.LoadFromFile (pNomeArq);
lCodCli := ExtrairCodCliente (lXML);

DB.StartTransaction;
CriaVinculoDbXML (lCodCli, lXML);
DB.Commit;
except
MostraErro ('Erro na importação do XML.');
end;
lXML.Free;
end;

Agora, qualquer erro que ocorra no código direcionará o fluxo do programa para a cláusula except, que então mostrará uma mensagem. Nesta solução, a memória usada pelo XML é sempre liberada e o código ficou mais fácil de se ler.

O tipo básico de exceção possui apenas uma propriedade, que é uma descrição da situação encontrada. Podemos extrair o valor desta propriedade e exibí-lo para o usuário, melhorando o tratamento da exceção. Obter a descrição exige uma nova sintaxe:
try
{ ... }
except
on Exc : Exception do
MostraErro ('Erro na importação do XML.' + exc.Message);
end;

Essa sintaxe permite-nos recuperar a instância da classe Exception com os dados sobre a exceção ocorrida. Com ela, diferentes tipos de exceção que ocorram podem ter tratamento individualizado. Por exemplo, um erro associado ao banco de dados pode exigir a chamada a um Rollback para desfazer a transação enquanto para um erro de entrada/saída bastaria limpar algumas variáveis.
try
{ ... }
except
on ExcDB : EDatabaseError do begin
DB.RollBack;
MostraErro ('Erro no banco de dados.' + excDB.Message);
end;
on ExcIO : EInOutError do begin
_Salvou := false;;
MostraErro ('Erro no arquivo.' + excIO.Message);
end;
on Exc : Exception do
MostraErro ('Erro na importação do XML.' + exc.Message);
end;

De acordo com o tipo da exceção ocorrida, o programa é desviado para o bloco ON correspondente. Esses blocos são varridos de cima para baixo até que a classe indicada no ON seja compatível com a classe da exceção ocorrida - mais ou menos como num bloco Case..Of. Como Exception é a classe base para todas as outras exceções, um bloco ON que faça referência a este tipo deve ser colocado no final; caso contrário todas as exceções geradas serão desviadas para este bloco, sempre ignorando os demais.

E o que acontece se nenhum dos tipos tratados combinar com a exceção gerada ? A exceção é relançada como se tivesse ocorrida fora do bloco protegido; se este bloco estiver aninhado em outro bloco protegido capaz de tratar a exceção, ela será considerada normalmente; se não o programa será interrompido. Em algumas situações, mesmo após o tratamento da exceção o programa não deve mais seguir seu fluxo natural - por exemplo, não pode ter continuidade uma carga de arquivo onde o arquivo informado não existe. Consegue-se esse efeito de desvio com o comando Raise num bloco Except ou Finally, que apenas lança de novo a mesma exceção. Isso exige que esse bloco também esteja protegido e que resulte num desvio para um ponto no qual o programa possa continuar sua execução sem problemas.

Exceções podem ser geradas tanto pela VCL quanto pelo sistema operacional mas há também uma forma de as levantarmos manualmente sempre que for necessário. Podemos, então, criar nossas próprias hierarquias de exceções para representar as situações de erro do nosso sistema. Novamente, usamos a palavra chave Raise, agora acompanhada da construção de uma instância da classe de exceção, como ilustrado abaixo:
{ ... }
lCodCli := ExtrairCodCliente (lXML);
if (not ExisteCliente (lCodCli)) then
Raise EClienteNaoExiste.Create (lCodCli);

Assim, um código protegido pode testar pela ocorrência da exceção EClienteNaoExiste e tomar providências condizentes com o cenário onde ela está inserida. Veja que o constructor dela aceita um código como parâmetro ao invés do texto. Isso permite criar notificações mais completas, incluindo outras informações relevantes para auxiliar na detecção e solução de problemas inesperados dentro do sistema.

11 de novembro de 2010

Design Patterns em Delphi – Mediator

Na modelagem de sistemas computacionais encontramos às vezes situações em que diversas classes de objetos podem ter que interagir entre si para que uma determinada ação seja concluída com sucesso. São cenários onde as classes têm relacionamento de muitos para muitos, isto é, as classes envolvidas podem se comunicar com qualquer uma das outras. O impulso inicial é o de incluir no modelo relacionamentos diretos entre todas as classes, tornando-as fortemente acopladas. Portanto, ter que adicionar novas classes a esse tipo de interação implicará a existência de uma profusão de relacionamentos, tornando complexa a manutenção da funcionalidade no sistema.

Esse tipo de situação ocorre, por exemplo, com as relações entre componentes visuais numa tela. Imagine que você queira fazer com que alterações no conteúdo de um componente reflitam em outros. Ao informar um código numa caixa de edição, por exemplo, a tela deve trazer dados sobre um Cliente, um Produto ou uma Conta a Pagar e esses dados devem alimentar outras caixas de edição, combos, checkboxes, etc. Ou, ao apertar um botão, a tela usar filtros informados em outros componentes, submeter uma query ao banco de dados e popular um grid. Qualquer componente pode se comunicar com todos os outros ! Criar novos componentes ampliaria a quantidade de relacionamentos possíveis, alavancando junto a complexidade de manutenção do sistema.

Linguagens de programação visuais modernas resolvem essa situação particular com eventos. O Design Pattern comportamental Mediator propõe resolver esta questão centralizando numa única classe a responsabilidade pela comunicação de todas as outras envolvidas na solução de um problema. Em outras palavras, o Mediator encapsula o modo como as classes interagem, desacoplando-as ao evitar que elas tenham que manter referências explícitas entre si. A ideia é a mesma de uma torre de controle de tráfego aéreo. Qualquer aeronave que queira decolar ou pousar tem antes que se comunicar com a torre para obter permissão. Esta comunicação mediada é muito mais simples e eficiente do que se cada aeronave tivesse que conversar diretamente com todas as outras para determinar o melhor momento de realizar sua manobra. Além disso, estabelecer que a comunicação deva ser mediada pela torre permite que um número indefinido de aeronaves seja controlado sem que haja necessidade de umas terem conhecimento da existência das outras.

Veja abaixo um diagrama UML retratando a solução proposta pelo Mediator para o exemplo do tráfego aéreo:
Diagrama UML para o padrão Mediator

Mesmo tendo apenas 3 classes de aeronaves modeladas, a torre é planejada para comportar um número indefinido de instâncias ao mesmo tempo. Adicionar novos tipos de aeronaves fica simplificado pois a nova classe só tem que aprender a conversar com o mediador (a torre), sem precisar saber quantas ou quais são as outras aeronaves presentes num determinado instante.

A nomenclatura formal para as classes participantes desse tipo de solução está retratada no quadro a seguir:
O Mediator é uma classe abstrata que define o padrão de comunicação que estará disponível para os objetos que serão administrados por ele. No exemplo do diagrama acima este papel cabe à classe TWTorreAbstrata.
Dá-se o nome de ConcreteMediator a toda herança que implementa as regras de comunicação estabelecidas pela classe Mediator. Estas heranças são, portanto, efetivamente responsáveis por coordenar a comunicação entre os objetos associados a elas. No diagrama de exemplo há apenas uma classe desse tipo: a TWTorreAeroportoCGH.

Cenários distintos exigirão a implementação de heranças distintas capazes de prover a comunicação apropriada entre os seus membros. No caso dos componentes de tela, por exemplo, podemos criar novas telas (mediadores) com seu próprio conjunto de componentes e codificarmos nelas as relações específicas entre esses componentes.
O AbstractColeague determina o comportamento padrão esperado para todos os objetos que desejam se comunicar entre si através do mediador. Por isso, tal classe terá que armazenar uma referência ao objeto mediador e acessá-lo sempre que necessitar se comunicar com as demais instâncias. Esse é o papel da classe TWAeronaveAbstrata no diagrama acima.
Os ConcreteColeagues implementam o comportamento determinado pela classe Coleague, enquanto realizam suas tarefas particulares. Se uma classe desse tipo precisar se comunicar com os demais Coleagues de uma forma nova, terá que fazê-lo também através do mediador, ainda que isto implique o uso de um ConcreteMediator específico. As classes TWBoing, TWTecoTeco e TWJatoExecutivo pertencem a esta categoria.

Num próximo post eu publico a codificação em Delphi do exemplo usado aqui para ilustrar esse Design Pattern.

Mais Informações
Posts sobre Design Patterns

5 de novembro de 2010

Trabalhando com campos de bits

Nos primórdios da computação não havia como fugir da obrigação de se trabalhar diretamente com bits e bytes. Naquele tempo, um especialista tinha apenas o assembly para instruir o computador. Hoje, as linguagens de programação são de alto nível, liberando o programador para se ocupar com as regras de negócio de um sistema ao invés de mergulhar nos detalhes de como a máquina funciona.

Mas, como os computadores continuam funcionando do mesmo jeito, há momentos em que a manipulação direta de bits volta pra nos assombrar. Uma situação típica em que saber trabalhar com bits é imprescindível aparece quando precisamos que nosso programa se comunique com outros tipos de hardware. Isto aconteceu aqui na ABC71 em outubro, quando um de nossos Clientes precisou de uma integração de nosso ERP com balanças instaladas em seu chão de fábrica. A balança implementa um protocolo específico, transmitindo através da porta serial os bytes que representam seu estado atual e o peso que está sobre ela. A imagem abaixo ilustra o significado de cada bit presente em um dos bytes, trafegando com parte do status:
Status 1

Em C++, extrair esses bits isoladamente é muito simples já que a linguagem comporta o conceito de "mapa de bits". Trata-se de uma estrutura onde conseguimos especificar os limites de cada campo, informando quantos bits cada um deles ocupará na estrutura. O quadro a seguir mostra a declaração de um mapa de bits que corresponde ao byte de status exibido na imagem anterior.
typedef struct
{
UINT flagDecimal : 3; /* Ocupa 3 bits */
UINT pesoNegativo : 1; /* Ocupa 1 bit */
UINT pesoEmMovto : 1;
UINT saturacao : 1;
UINT sobrecarga : 1;
UINT bit7 : 1;
} TWStatus1;
Repare que a ordem de inclusão dos campos na estrutura respeita a ordem que os bits ocupam no byte em questão. Isto é, o campo para o bit de número 1 é incluído primeiro, enquanto o oitavo bit é o último campo da estrutura. O tipo de dado selecionado para cada campo é importante por uma questão semântica, sendo que a escolha deve levar em conta o uso que faremos deles. À exceção do indicador de casas decimais, os outros campos do exemplo poderiam ser do tipo boolean sem problemas.

E onde isso se encaixa na solução do problema de comunicação direta com os bytes recebidos da porta serial ? Ao receber da porta serial um buffer (sequência de bytes) com os dados esperados, podemos fazer com que uma variável com o tipo da estrutura que definimos aponte o mesmo endereço de memória do buffer, compartilhando com ele o conteúdo. A diferença é que nossa estrutura tem os campos bem definidos e assim podemos usá-los diretamente em nosso programa, sem termos que nos preocupar com o significado dos bits contidos no buffer. Fica mais fácil compreender vendo o corpo da função que trata os bytes recebidos da serial :
void __fastcall TWSerialComm::DoOnReceiveData(TObject* Sender, void* DataPtr, unsigned int DataSize)
{
TWStatus1 *lStatus = (TWStatus1 *)DataPtr;
if (lStatus->pesoEmMovto == 0)
{
/* ... */
}
}

Embora não seja o caso no exemplo apresentado, há um outro detalhe ao qual devemos prestar atenção: o alinhamento de bytes. Em estruturas mais complexas, o alinhamento dos campos na memória, determinado pelo compilador, pode fazer com que a estrutura ocupe mais bytes do que o buffer recebido. Como resultado, as informações contidas no buffer deixam de se encaixar perfeitamente na estrutura, que passa a conter apenas sujeira. Por isso, é importante forçar o alinhamento correto da estrutura, evitando a existência dos espaços extras no interior dela. Em C++ Builder, há uma diretiva de compilação para controlar o alinhamento no nível de estrutura, chamada pragma pack:
/* Força em 1 o alinhamento da estrutura */
#pragma pack(push)
#pragma pack(1)
typedef struct
{
char STX;
double peso;
double tara;
TWStatus1 status;
char ETX;
} TWDadosBalanca;
#pragma pack(pop)

Em Delphi, manipular bits é um pouco mais complexo devido à falta do conceito de campo de bits na sintaxe da linguagem. Neste caso, será preciso apelar para a aritmética binária, operando com máscaras de bits e operadores lógicos. Há uma solução bastante elegante no site Delphi's Corner onde é construída uma classe com propriedades indexadas para encapsular o tratamento de mais baixo nível envolvido na aritmética binária.

29 de outubro de 2010

Levantando os recursos existentes na CPU

Há um post aqui no blog onde mostrei como obter informações sobre a CPU em Delphi usando a instrução assembly CPUID. O foco lá era obter apenas o modelo da CPU, informação usada para fazer um licenciamento de software. Aqui eu vou mostrar como fazer um levantamento sobre os recursos implementados numa CPU dos dois principais fabricantes - Intel e AMD.

Em ambos os fabricantes, o levantamento proposto é executado pela função 1 da CPUID. Isto signfica que teremos que mover o valor 1 para o registrador EAX, que armazena o código da função a ser executada. Os valores que buscamos são armazenados pela instrução CPUID no registrador EDX.
var REGEDX : LongWord;
begin
asm
MOV EAX, 1
CPUID
MOV [REGEDX], EDX
end;
{ ... }

Então, após a execução do código acima, teremos na variável REGEDX o mapa de bits para as características presentes no processador. São 32 bits ao todo, cada um sinalizando se um determinado recurso está presente na CPU do seu computador. Portanto, para ter acesso a cada bit isoladamente, teremos que trabalhar com aritmética binária no Delphi - principalmente com o operador AND aplicado bit a bit.

A ideia é preparar outra variável com 32 bits mas apenas um dos bits estará ligado de cada vez. Ao aplicar o AND binário entre essa variável e o conteúdo de REGEDX nós obtemos a sinalização se o recurso representado nesse bit específico está presente ou não. A imagem abaixo ilustra a extração do recurso representado pelo primeiro bit:
Extração do primeiro bit

Repare que, na variável preparada, apenas o primeiro bit está ligado, isto é, possui valor 1. Comos os demais bits estão em zero, o AND binário com o REGEDX retornará um valor diferente de zero caso seu primeiro bit também esteja ligado.

Para ligar um bit de cada vez na variável, podemos montar um laço iniciando em 1 (liga o primeiro bit) e ir multiplicando-a por 2 para fazer o bit ir "avançando" :
var REGEDX, Mascara : LongWord;
Suportado : array[0..31] of byte;
i : integer;
begin
{ ... }
Mascara := 1;
for i := 0 to 31 do begin
if (REGEDX and Mascara) <> 0 then
Suportado[i] := 1 { Suportado }
else
Suportado[i] := 0; { Não suportado }
Mascara := Mascara * 2;
end;
{ ... }

No código acima, a variável preparada para auxiliar na extração dos bits chama-se Mascara. Ao final da execução desse trecho, teremos em cada posição do array Suportado um sinal indicando se o recurso existe ou não na CPU. Mas qual recurso ?

O significado de cada posição varia de acordo com o fabricante da CPU, sendo que até mesmo o conjunto de recursos incluídos em um e outro é diferente. Para facilitar a exibição das informações sobre os recursos podemos montar um array com 32 posições para texto e alimentar cada uma com um lembrete do significado do recurso. O quadro abaixo mostra os recursos disponíveis em processadores Intel:
if(StrComp(VendorID, 'GenuineIntel') = 0)
then begin
Recurso[0]:='FPU'; { Floating Point Unit }
Recurso[1]:='VME'; { Virtual Mode Extension }
Recurso[2]:='DE'; { Debugging Extension }
Recurso[3]:='PSE'; { Page Size Extension }
Recurso[4]:='TSC'; { Time Stamp Counter }
Recurso[5]:='MSR'; { Model Specific Registers }
Recurso[6]:='PAE'; { Physical Address Extension }
Recurso[7]:='MCE'; { Machine Check Extension }
Recurso[8]:='CX8'; { CMPXCHG8 Instruction}
Recurso[9]:='APIC'; { On-chip APIC Hardware }
Recurso[10]:=''; { Reserved }
Recurso[11]:='SEP'; { Fast system call }
Recurso[12]:='MTRR'; { Machine Type Range Registers }
Recurso[13]:='PGE'; { Global Paging Extension }
Recurso[14]:='MCA'; { Machine Check Architecture }
Recurso[15]:='CMOV'; { Conditional Move Instruction }
Recurso[16]:='PAT'; { Page Attribute Table }
Recurso[17]:='PSE-36'; { 36-bit Page Size Extension }
Recurso[18]:='PSN'; { 96-bit Processor Serial Number }
Recurso[19]:='CLFSH'; { CLFLUSH Instruction }
Recurso[20]:=''; { Reserved }
Recurso[21]:='DS'; { Debug Trace Store }
Recurso[22]:='ACPI'; { ACPI Support }
Recurso[23]:='MMX'; { MMX Technology }
Recurso[24]:='FXSR'; { FXSAVE FXRSTOR (Fast save and restore) }
Recurso[25]:='SSE'; { Streaming SIMD Extensions }
Recurso[26]:='SSE2'; { Streaming SIMD Extensions 2 }
Recurso[27]:='SS'; { Self-Snoop }
Recurso[28]:='HTT'; { Hyper-Threading Technology }
Recurso[29]:='TM'; { Thermal Monitor Supported }
Recurso[30]:=''; { Reserved }
Recurso[31]:='PBE'; { Pending Break Enable }
end;

Veja este link para saber como obter o nome do fabricante. A documentação sobre o uso do CPUID em processadores Intel pode ser acessada aqui.

O quadro abaixo mostra os recursos disponíveis em processadores AMD:
if(StrComp(VendorID, 'AuthenticAMD') = 0)
then begin
Recurso[0]:='FPU'; { Floating Point Unit }
Recurso[1]:='VME'; { Virtual Mode Extension }
Recurso[2]:='DE'; { Debugging Extension }
Recurso[3]:='PSE'; { Page Size Extension }
Recurso[4]:='TSC'; { Time Stamp Counter }
Recurso[5]:='MSR'; { Model Specific Registers }
Recurso[6]:='PAE'; { Physical Address Extesnion }
Recurso[7]:='MCE'; { Machine Check Extension }
Recurso[8]:='CX8'; { CMPXCHG8 Instruction }
Recurso[9]:='APIC'; { On-chip APIC Hardware }
Recurso[10]:=''; { Reserved }
Recurso[11]:='SEP'; { SYSENTER and SYSEXIT instructions }
Recurso[12]:='MTRR'; { Machine Type Range Registers }
Recurso[13]:='PGE'; { Global Paging Extension }
Recurso[14]:='MCA'; { Machine Check Architecture }
Recurso[15]:='CMOV'; { Conditional Move Instruction }
Recurso[16]:='PAT'; { Page Attribute Table }
Recurso[17]:='PSE-36'; { 36-bit Page Size Extension }
Recurso[18]:=''; { Reserved }
Recurso[19]:='CLFSH'; { CLFLUSH instruction support }
Recurso[20]:=''; { Reserved }
Recurso[21]:=''; { Reserved }
Recurso[22]:=''; { Reserved }
Recurso[23]:='MMX'; { MMX Technology }
Recurso[24]:='FXSR'; { FXSAVE FXRSTOR (Fast save and restore) }
Recurso[25]:='SSE'; { Streaming SIMD Extensions }
Recurso[26]:='SSE2'; { SSE2 instruction support }
Recurso[27]:=''; { Reserved }
Recurso[28]:='HTT'; { Hyper-Threading Technology }
Recurso[29]:=''; { Reserved }
Recurso[30]:=''; { Reserved }
Recurso[31]:=''; { Reserved }
end;

A documentação sobre o uso do CPUID em processadores AMD pode ser acessada aqui.

Ambos os arrays montados neste post - Suportado e Recurso - estão sincronizados. Então, para mostrar se um recurso está ou não presente basta percorrer os arrays usando o mesmo índice e mostrando lado a lado o nome do recurso e o flag indicativo de suporte a este recurso.