//////////////////////////////////////////////////////
return w.ToArray();
/*
//////////////////////////////////////////////////////
// Вариант 2: данные сжимаются и защищаются
// контрольной суммой Адлера.
//////////////////////////////////////////////////////
return w.ToArray(AcedCompressionMode.Fast);
*/
/*
//////////////////////////////////////////////////////
// Вариант 3: данные сжимаются, шифруются и защищаются
// цифровой сигнатурой RipeMD-160.
//////////////////////////////////////////////////////
return w.ToArray(AcedCompressionMode.Fast,
new Guid("CA761232-ED42-11CE-BACD-00AA0057B223"));
*/
}
В данном примере функция PutData() помещает в бинарный поток массив байт как целый объект, потом значение типа Int16, затем фрагмент массива элементов типа Int32, а в конце – строку символов. Результатом функции может быть просто массив байт, содержащий данные, записанные в поток, защищенные контрольной суммой Адлера. Размер этого массива составляет 443 байта. Если передать в функцию AcedMemoryWriter.ToArray() параметр compressionMode со значением AcedCompression.Fast, данные бинарного потока будут упакованы и размер полученного массива составит 51 байт. Если, кроме того, передать некоторое непустое значение типа Guid в параметре keyGuid, сжатые данные будут защищены цифровой сигнатурой RipeMD-160 и зашифрованы методом CAST-128. За счет добавления сигнатуры размер выходного массива увеличится при этом на 20 байт и составит 71 байт.
Класс AcedMemoryReader
Предназначен для чтения данных из массива байт, созданного экземпляром класса AcedMemoryWriter. В конструктор класса AcedMemoryReader передается ссылка на массив байт с указанием фрагмента, содержащего данные бинарного потока. Если данные зашифрованы, в последнем параметре конструктора необходимо передать значение типа System.Guid, соответствующее ключу шифра, который использовался при вызове метода ToArray() класса AcedMemoryWriter. Отдельные значения могут быть прочитаны из потока методами, названия которых состоят из префикса "Read" и наименования типа читаемого значения. Фрагменты массивов, состоящих из элементов стандартных value-типов, считываются методом Read(). Для возвращения текущей позиции на начало потока, чтобы заново прочитать данные, используется метод Reset(). Чтобы пропустить некоторое количество байт во входном потоке вызывается метод Skip(). При попытке чтения данных за пределами потока возникает исключение типа AcedReadBeyondTheEndException.
Свойство Position класса AcedMemoryReader возвращает индекс следующего считываемого байта во внутреннем массиве, ссылка на который возвращается функцией GetBuffer(). Размер внутреннего массива определяется свойством Size. Смещение во внутреннем массиве, с которого начинаются данные потока – свойством Offset. Если исходный массив байт, переданный в конструктор класса, является упакованным, в памяти создается новый массив для распакованных данных. Тогда функция GetBuffer() возвращает ссылку на этот временный массив, а свойство Offset всегда равно нулю. Если же исходный массив не является упакованным, функция GetBuffer() возвращает ссылку на массив, переданный параметром bytes в конструктор класса AcedMemoryReader. Если данные потока зашифрованы, массив байт, передаваемый в конструктор этого класса, расшифровывается на месте. Это означает, что один и тот же зашифрованный массив байт нельзя использовать для инициализации нескольких экземпляров класса AcedMemoryReader.
Если при создании экземпляра класса AcedMemoryReader в конструктор передан массив недостаточной длины или рассчитанная для него контрольная сумма Адлера не совпадает с сохраненным в потоке значением контрольной суммы, возникает исключение AcedDataCorruptedException. Если после дешифрования данных оказывается, что рассчитанное значение цифровой сигнатуры RipeMD-160 для данных потока не совпадает со значением сигнатуры, сохраненным в начале массива данных, возникает исключение AcedWrongDecryptionKeyException, которое является потомком от класса AcedDataCorruptedException.
Пример использования класса AcedMemoryReader:
private void GetData(byte[] dataBytes, out byte[] bytes,
out short n, out string s, out int[] otherValues)
{
AcedMemoryReader r = new AcedMemoryReader(dataBytes,
0, dataBytes.Length);
/*
AcedMemoryReader r = new AcedMemoryReader(dataBytes,
0, dataBytes.Length,
new Guid("CA761232-ED42-11CE-BACD-00AA0057B223"));
*/
bytes = r.ReadByteArray();
n = r.ReadInt16();
otherValues = new int[120];
r.Read(otherValues, 10, 100);
s = r.ReadString();
}
Предполагается, что массив байт, передаваемый параметром dataBytes в функцию GetData(), получен как результат функции PutData(), код которой приведен выше в разделе, описывающем класс AcedMemoryWriter. Используемый здесь конструктор класса AcedMemoryReader предполагает, что данные в бинарном потоке не зашифрованы. Закомментированный фрагмент кода содержит вызов конструктора с передачей в него ключа шифра, соответствующего варианту 3 функции PutData().
Классы AcedStreamWriter, AcedStreamReader
Эти классы аналогичны описанным выше классам AcedMemoryWriter, AcedMemoryReader. При их использовании, однако, данные помещаются не в массив байт, а в поток типа System.IO.Stream, ассоциированный с экземпляром класса AcedStreamWriter, и читаются не из массива байт, а из потока типа System.IO.Stream, ассоциированного с классом AcedStreamReader.
При работе с классом AcedStreamWriter в памяти создается буфер размером 2МБ, который постепенно заполняется данными. При достижении конца буфера, вызове методов Flush() или Close() класса AcedStreamWriter содержимое буфера упаковывается методом Compress() класса AcedDeflator. Сжатые данные сохраняются в другом буфере, размер которого также составляет 2МБ. Для упакованных данных вычисляется цифровая сигнатура RipeMD-160, после чего данные шифруются методом CAST-128. Длина фрагмента данных, контрольная сумма Адлера, цифровая сигнатура RipeMD-160 и сами сжатые и зашифрованные данные записываются в выходной поток типа System.IO.Stream. После этого содержимое буфера очищается и в него можно записывать следующие данные. При вызове метода Close() класса AcedStreamWriter, если ассоциированный с ним поток поддерживает операцию Seek, поток позиционируется на начало записанных данных и в потоке сохраняется общая длина (в байтах) данных, помещенных в поток классом AcedStreamWriter. Этот размер представляется значением типа System.Int64. Если операция Seek не поддерживается потоком типа System.IO.Stream, длина остается равной значению -1, записанному в поток при его ассоциации с классом AcedStreamWriter. Метод AssignStream класса AcedStreamWriter используется, чтобы связать данный экземпляр класса с потоком System.IO.Stream. Кроме ссылки на поток в этот метод передается константа, выбирающая режим сжатия данных, а также значение типа System.Guid, которое, если оно отлично от Guid.Empty, задает ключ для шифрования данных. Таким образом, в зависимости от параметров, переданных в метод AssignStream, этапы сжатия данных, расчета цифровой сигнатуры и шифрования данных могут опускаться.
Чтобы прочитать данные, сохраненные в потоке System.IO.Stream классом AcedStreamWriter, нужно воспользоваться классом AcedStreamReader. Экземпляр этого класса может быть ассоциирован с потоком типа System.IO.Stream с помощью метода AssignStream. Если данные, помещенные в поток, зашифрованы, при вызове метода AssignStream следует указать ключ шифра в виде значения типа System.Guid. В методе AssignStream сразу считывается длина фрагмента данных, помещенного в поток классом AcedStreamWriter. Это значение возвращается свойством Length класса AcedStreamReader. Длина может быть равна значению -1, если не было возможности сохранить в потоке настоящее значение длины. В экземпляре класса AcedStreamReader также имеется два буфера, каждый размером по 2МБ. Первый предназначен для данных, считанных из потока System.IO.Stream, второй – для распакованных данных. Когда вызывается один из методов Read… класса AcedStreamReader, сначала предпринимается попытка считать значение из буфера распакованных данных. Если достигнут конец буфера, из потока System.IO.Stream считывается следующий фрагмент данных. Для этого фрагмента проверяется значение контрольной суммы Адлера. Затем, если данные зашифрованы, выполняется их дешифрование и проверка цифровой сигнатуры RipeMD-160. Потом, если данные упакованы, производится их распаковка во второй буфер. Теперь значение может быть прочитано и возвращено функцией Read.… При чтении из потока длинных массивов перегруженным методом Read() класса AcedStreamReader возможна ситуация, когда для считывания всего массива приходится несколько раз заполнять внутренний буфер данными из потока System.IO.Stream.
Так как экземпляры классов AcedStreamWriter и AcedStreamReader занимают собой значительный объем памяти (каждый свыше 4МБ), создавать их при каждом чтении из потока нерационально. Сборщик мусора в .NET Framework автоматически относит блоки памяти свыше 85000 байт ко второму поколению (об этом см. в книге Джеффри Рихтера "Программирование на платформе .NET Framework" – M.: Издательско-торговый дом "Русская Редакция", 2003). Такие блоки лучше использовать для ресурсов с длительным временем существования. В противном случае, частое пересоздание больших блоков памяти отрицательно сказывается на общей производительности приложения. Для решения этой проблемы в классах AcedStreamWriter и AcedStreamReader имеется статическое свойство Instance, которое при первом обращении к нему создает экземпляр соответствующего класса, а при следующих обращениях просто возвращает ссылку на существующий экземпляр. Тогда, вместо того, чтобы создавать новые экземпляры классов вызовом соответствующих конструкторов, лучше воспользоваться единственным экземпляром, возвращаемым свойством Instance. Этот подход аналогичен тому, который применяется в классах AcedDeflator и AcedInflator. Чтобы освободить занимаемую память можно вызвать статический метод Release(), освобождающий ссылку на экземпляр соответствующего класса.
После помещения всех данных в поток AcedStreamWriter, а также после чтения необходимых данных из потока AcedStreamReader, нужно вызвать метод Close() для выполнения завершающих действий. Если в параметре closeStream метода Close() передано значение True, поток типа System.IO.Stream, ассоциированный с данным экземпляром класса AcedStreamWriter или AcedStreamReader, закрывается вызовом метода Close() потока.
Классы AcedWriterStream, AcedReaderStream
Эти классы представляют собой оболочку над другими классами, предназначенными для работы с бинарным потоком. Они используются, когда надо представить экземпляры других классов в виде объектов, производных от класса System.IO.Stream.
Класс AcedWriterStream является потомком класса System.IO.Stream и предназначен для записи данных в потоки типа AcedMemoryWriter и AcedStreamWriter. В его конструктор передается ссылка на интерфейс IAcedWriter, который поддерживается классами AcedMemoryWriter и AcedStreamWriter. Класс AcedWriterStream используется только для записи данных в поток, поэтому его свойства CanRead и CanSeek возвращают значение False, а свойство CanWrite – значение True. Вызов методов Write(), WriteByte(), Flush(), Close() перенаправляется соответствующим методам объекта, реализующего интерфейс IAcedWriter. При чтении свойств Length и Position возвращается число байт, помещенное в выходной бинарный поток. Однако, присвоение значения свойству Position или вызов методов Read(), ReadByte(), Seek(), SetLength() приводит к возникновению исключения типа System.NotSupportedException. Свойство Writer класса AcedWriterStream возвращает ссылку на объект, реализующий интерфейс IAcedWriter, которая была передана в конструктор класса при его создании.