quinta-feira, 8 de março de 2012

C# - Calculando o CRC de strings e arquivos

O CRC - Cyclic redundancy check, ou verificação de redundância cíclica, é um código detector de erros cujo algoritmo é muito usado em protocolos de comunicação diferentes, empacotamento e desempacotamento de algoritmos para garantir a robustez dos dados.

A ideia por trás do CRC é simples - calcular um checksum original (sequência de verificação de quadros) para cada quadro de dados, baseada em seu conteúdo, e colar o checksum no final de cada mensagem. Uma vez que os dados são recebidos, é possível realizar o mesmo cálculo e comparar os resultados - se os resultados são semelhantes, a mensagem é válida.

O CRC é calculado e anexado na informação a transmitir (ou armazenar), sendo verificado após a recepção ou o acesso, para confirmar se não ocorreram alterações.

O CRC é popular por ser simples de implementar em hardware binário, simples de ser analisado matematicamente, e pela eficiência em detectar erros típicos causados por ruído em canais de transmissão.

A utilidade do CRC advém das seguintes propriedades:
  1. Como todos os bits são usados no cálculo do CRC, a mudança em apenas um bit provoca uma mudança no CRC.
  2. Mesmo mudanças pequenas nos dados levam a CRCs muito diferentes. Experiências com o CRC-32 (usando polinômios de 32 bits) mostram que é muito raro que a introdução de erros nos dados não seja detectada pelo CRC.
  3. A probabilidade de qualquer dos 232 valores possíveis para o CRC é praticamente uniforme.

Existem diferentes tipos de CRC que podem ser calculados: CRC-32, CRC-16, CRC-12, CRC-8 etc.

Para realizarmos cálculos envolvendo o CRC temos que utilizar o namespace System.Security.Cryptography e neste artigo eu vou calcular o CRC de strings e arquivos.
Vamos abrir o Visual Studio 2010 e no menu: File > New Poject, selecione o Template Windows Forms Applicacion com o nome Calculando_CRC.

No menu: Project > Add Class, inclua uma classe com o nome CrcStream com o seguinte código:

using System.IO;

namespace PauloTips
{
    /// <summary>
    /// Encapsula um <see cref="System.IO.Stream" /> para calcular o checksum CRC32
    /// em tempo de execução
    /// </summary>
    public class CrcStream : Stream
    {
        /// <summary>
        /// Encapsula um <see cref="System.IO.Stream" />.
        /// </summary>
        /// <param name="stream">O stream para calcular o checksum.</param>
        public CrcStream(Stream stream)
        {
            this.stream = stream;
        }

        Stream stream;

        /// <summary>
        /// Obtem o stream.
        /// </summary>
        public Stream Stream
        {
            get { return stream; }
        }

        public override bool CanRead
        {
            get { return stream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return stream.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return stream.CanWrite; }
        }

        public override void Flush()
        {
            stream.Flush();
        }

        public override long Length
        {
            get { return stream.Length; }
        }

        public override long Position
        {
            get
            {
                return stream.Position;
            }
            set
            {
                stream.Position = value;
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return stream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            stream.SetLength(value);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            count = stream.Read(buffer, offset, count);
            readCrc = CalculateCrc(readCrc, buffer, offset, count);
            return count;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            stream.Write(buffer, offset, count);

            writeCrc = CalculateCrc(writeCrc, buffer, offset, count);
        }

        uint CalculateCrc(uint crc, byte[] buffer, int offset, int count)
        {
            unchecked
            {
                for (int i = offset, end = offset + count; i < end; i++)
                    crc = (crc >> 8) ^ table[(crc ^ buffer[i]) & 0xFF];
            }
            return crc;
        }

        static private uint[] table = GenerateTable();

        static private uint[] GenerateTable()
        {
            unchecked
            {
                uint[] table = new uint[256];

                uint crc;
                const uint poly = 0xEDB88320;
                for (uint i = 0; i < table.Length; i++)
                {
                    crc = i;
                    for (int j = 8; j > 0; j--)
                    {
                        if ((crc & 1) == 1)
                            crc = (crc >> 1) ^ poly;
                        else
                            crc >>= 1;
                    }
                    table[i] = crc;
                }
                return table;
            }
        }

        uint readCrc = unchecked(0xFFFFFFFF);

        /// <summary>
        /// Obtem o checksum CRC dos dados que foram lidos pelo stream
        /// </summary>
        public uint ReadCrc
        {
            get { return unchecked(readCrc ^ 0xFFFFFFFF); }
        }

        uint writeCrc = unchecked(0xFFFFFFFF);

        /// <summary>
        /// Obtem o checksum CRC dos dados que foram escritos para o stream
        /// </summary>
        public uint WriteCrc
        {
            get { return unchecked(writeCrc ^ 0xFFFFFFFF); }
        }

        /// <summary>
        /// Reseta a leitura e escrita dos checksums.
        /// </summary>
        public void ResetChecksum()
        {
            readCrc = unchecked(0xFFFFFFFF);
            writeCrc = unchecked(0xFFFFFFFF);
        }
    }
}


Agora vamos definir no formulário form1.cs uma interface bem simples, na qual iremos informar o nome do arquivo e calcular o seu CRC.

Abaixo, vemos o formulário que usa os controles TextBox e Button:


No evento Click do botão de comando - Calcula CRC -, vamos incluir o código que usa a classe CrcStream para calcular o CRC do arquivo:

using System;
using System.Windows.Forms;
using System.IO;
using PauloTips;

namespace Calculando_CRC
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        FileStream file = null;
        CrcStream stream = null;

        private void btnCalculaCRC_Click(object sender, EventArgs e)
        {
            if (txtArquivo.Text == string.Empty)
            {
                MessageBox.Show("Informe o nome do arquivo.");
                return;
            }

            string arquivo = txtArquivo.Text;

            //Abre um fluxo de stream e o encapsula em um CrcStream
            try
            {
                file = new FileStream(arquivo, FileMode.Open);
                stream = new CrcStream(file);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Erro ao acessar o arquivo :  " + ex.Message);
                return;
            }

            //Usa o arquivo - neste caso le o arquivo como uma string
            StreamReader reader = new StreamReader(stream);
            string texto = reader.ReadToEnd();

            //Imprime o checksum calculado
            txtCRC.Text = stream.ReadCrc.ToString("X8");
        }
    }
}

Executando o projeto, iremos obter para um determinado arquivo informado:



Pegue o projeto completo aqui: Calculando_CRC.zip 

Créditos: José Macoratti


0 comentários:

Postar um comentário

 
;