Przygody z BitConverter i StreamReader

Napotkałem problem z klasą StreamReader, która nie radzi sobie z odczytaniem plików w formie tablicy bajtów. Różne kombinację z odczytaniem czy to całej linii czy też całego pliku – ReadToEnd(), nie przyniosły właściwego skutku. Poniżej prezentuje jak można odczytać sekwencję bajtów  bezpośrednio z pliku.

Wyobraźmy sobie sytuację gdzie chcemy zapisać do pliku sekwencję bajtów. Miałem taką potrzebę przy zapisywaniu wiadomości szyfrowanej asymetrycznym algorytmem RSA, używając do tego klasy RSACryptoServiceProvider. Jej metoda Encrypt zwraca zaszyfrowaną wiadomość w postaci tablicy bajtów byte[].

Ostatnim razem radziłem sobie tak, że zamieniałem tablice bajtów na postać szesnastkową i zapisywałem ją pliku. Dzięki temu wiadomość była miła dla oka i można było ją łatwo transportować.

Tym razem chciałem w pliku trzymać postać bajtową. Kombinacje ze StreamReader-em nie udały się więc postanowiłem zrobić to w trudny sposób.

Najpierw jednak łatwy sposób. Można to uzyskać poprzez dwie metody w klasie statycznej File.

public void ByteConverterUsingFile()
{
string text = "Tajny Tekst";

string fileName = Path.GetRandomFileName();

var encoding = new UnicodeEncoding();

File.WriteAllBytes(fileName, encoding.GetBytes(text));

var result = encoding.GetString(File.ReadAllBytes(fileName));

Assert.AreEqual(text, result);

File.Delete(fileName);

}

Jest to najprostszy sposób zapisu danych do pliku w formie tablicy bajtów. Polecam!

Jednak ja wybrałem inny sposób.

public byte[] GetByteFromFile(string fileName)
{
    var result = new List<byte>();

    using (var sr = new StreamReader(fileName))
    {
        int byteStream = 0;

        while ((byteStream = sr.BaseStream.ReadByte()) != -1)
        {
            var intByteList = BitConverter.GetBytes(byteStream);

            if (BitConverter.IsLittleEndian)
            {
              Array.Reverse(intByteList);
            }

            if (intByteList.All(b => b == 0))
            {
              result.Add(0);
              continue;
            }

            var LeadingZero = true;

            foreach (var byteValue in intByteList)
            {
				if (byteValue == 0 && LeadingZero)
				{
					 LeadingZero = true;
				}
				else
				{
					result.Add(byteValue);
 					LeadingZero = false;
				}
            }
		}
    }
        return result.ToArray();
}

Na początek w linijce 9 korzystamy z bazowej klasy BaseStream. Dzięki temu możemy użyć metody ReadByte(), która to zwraca jeden bajt w postaci 32 bitowego integera lub -1 gdy strumień się skończył.

W linijce 13 mamy ciekawostkę. Dla większość procesorów x86 i aplikacji .NET – IsLittleEndian jest prawdziwa. Co oznacza, że układ zer i jedynek dla konwersji jest tak skonstruowany, że zera wiodące są na końcu.

Szegóły dostępne tutaj : http://pl.wikipedia.org/wiki/Kolejno%C5%9B%C4%87_bajt%C3%B3w

W tej linijce odwracam kolejność aby zaczytać właściwe dane.

W linijce 18 sprawdzam czy wartość dla każdego bajtu jest 0. Jeśli tak to wpisujemy 0.

Ostatnia ciekawa linijka to 28. Ponieważ int32 składa się z 4 bajtów to gdy liczba nie zajmuje wszystkich 4 bajtów, wtedy nie używane bajty są zastępowane zerami. Jednak zera dzielą się na te wiodące i te, które można pominąć.

Dobry przykład to agent James Bond 007. 00 – to zera, które można pominąć. Jednak już zapis 0070 dzieli się na zera, które można pominąć (00) i jedno zero na końcu, które jest ważne, bo między 7 a 70 jest różnica(przykład był w postaci dziesiętnej ale cały czas chodzi o postać binarną)

Dlatego iterujemy po wszystkich bajtach i jeśli trafimy już na właściwą wartość (!=0) to każde następne zero będzie już ważne i należy te przepisać.

Rozwiązanie to działa i jest przetestowane. Oczywiście nie jest to forma w jakiej należy korzystać z konwersji bajtowej, jednak pozwala sporo zrozumieć jak dokładnie działa kodowanie.