Ross n williams a painless guide to crc error detection algorithms перевод

Оглавление Введение: обнаружение ошибок. Основная идея, заложенная в алгоритме CRC . Двоичная арифметика без учета переносов . Полностью рабочий пример. Прямая реализация CRC. Реализация табличного алгоритма. Слегка преобразованный табличный алгоритм. «Зеркальный» табличный алгоритм . Начальные и конечные значения. Полное определение алгоритма . Параметрическая модель CRC алгоритма . Каталог параметров стандартных реализаций CRC алгоритма . […]

Содержание

  1. Оглавление
  2. Предисловие
  3. 1. Введение: обнаружение ошибок
  4. 2. Требования сложности
  5. 3. Основная идея, заложенная в алгоритме CRC
  6. Williams R.N. A Painless Guide to CRC Error Detection Algorithms
  7. Статья на тему: «CRC-алгоритмы обнаружения ошибок»
  8. 1.Обнаружение ошибок
  9. 2. Необходимость использования сложных алгоритмов
  10. 3. Основная идея CRC-алгоритмов
  11. 4. Полиномиальная арифметика
  12. 5. Двоичная арифметика без переноса
  13. 6. Пример вычисления CRC
  14. 7. Выбор делителя
  15. 8. Особенности различных алгоритмов

Оглавление

Введение: обнаружение ошибок.

Основная идея, заложенная в алгоритме CRC .

Двоичная арифметика без учета переносов .

Полностью рабочий пример.

Прямая реализация CRC.

Реализация табличного алгоритма.

Слегка преобразованный табличный алгоритм.

«Зеркальный» табличный алгоритм .

Начальные и конечные значения.

Полное определение алгоритма .

Параметрическая модель CRC алгоритма .

Каталог параметров стандартных реализаций CRC алгоритма .

Реализация модельного алгоритма .

Создай собственную табличную реализацию.

Генерация таблицы просмотра.

C. Другие, обнаруженные мной, но не просмотренные ссылки.

Предисловие

Данная статья посвящена полному и точному описанию CRC (Cyclic Redundancy Codes – Циклические Избыточные Коды) и реализации табличных алгоритмов их вычисления. Большая часть литературы, касающаяся CRC вообще, и их табличным разновидностям особенно, достаточно сложна и запутанна (по крайней мере, мне так показалось). Статья была написана с целью дать простое и вместе с тем точное опи сание CRC, вникающее во все тонкости реализации его высокоскоростных вариан тов. Предложена параметрическая модель CRC алгоритма, названная «Rocksoft Model CRC Algorithm», которая может быть настроена таким образом, чтобы рабо тать подобно большинству стандартных реализаций алгоритмов расчета CRC, и ко торая, одновременно, является хорошим примером для демонстрации особенностей некоторых из них. Кроме того, приведен неоптимизированный пример на языке C, а также 2 варианта высокоскоростной табличной реализации, и программа генерации таблицы поиска для расчета CRC.

Элементарное руководство по CRC алгоритмам обнаружения ошибок

1. Введение: обнаружение ошибок

Методы обнаружения ошибок предназначены для выявления повреждений со общений при их передаче по зашумленным каналам (вносящих эти ошибки). Для этого передающее устройство создает некоторое число, называемое контрольной суммой и являющееся функцией сообщения, и добавляет его к этому сообщению. Приемное устройство, используя тот же самый алгоритм, рассчитывает контроль ную сумму принятого сообщения и сравнивает ее с переданным значением. На пример, если мы для расчета контрольной суммы используем простое сложение байтов сообщения по модулю 256, то может возникнуть примерно следующая си туация. (Все числа примера десятичные.)

Сообщение с контрольной суммой

Сообщение после передачи

Как Вы видите, второй байт сообщения при передаче оказался измененным с 23 на 27. Приемник может обнаружить ошибку, сравнивая переданную контрольную сумму (33) с рассчитанной им самим: 6 + 27 + 4 = 37. Если при правильной пере даче сообщения окажется поврежденной сама контрольная сумма, то такое сооб щение будет неверно интерпретировано, как искаженное. Однако, это не самая плохая ситуация. Более опасно, одновременное повреждение сообщения и кон трольной суммы таким образом, что все сообщение можно посчитать достоверным. К сожалению, исключить такую ситуацию невозможно, и лучшее, чего можно до биться, это снизить вероятность ее появления, увеличивая количество информации в контрольной сумме (например, расширив ее с одного до 2 байт).

Ошибки иного рода возникают при сложных преобразованиях сообщения для удаления из него избыточной информации. Однако, данная статья посвящена толь ко расчетам CRC, которые относятся к классу алгоритмов, не затрагивающих само го сообщения и лишь добавляющих в его конце контрольную сумму:

2. Требования сложности

В предыдущем разделе мы показали, что повреждение сообщения может быть обнаружено, используя в качестве алгоритма контроля простое суммирование бай тов сообщения по модулю 256:

Сообщение с контрольной суммой

Сообщение после передачи

Недостаток этого алгоритма в том, что он слишком прост. Если произойдет не сколько искажений, то в 1 случае из 256 мы не сможем их обнаружить. Например:

Сообщение с контрольной суммой

Сообщение после передачи

Для повышения надежности мы могли бы изменить размер регистра с 8 битного на 16 битный (то есть суммировать по модулю 65536 вместо модуля 256), что скорее

3. Основная идея, заложенная в алгоритме CRC

всего снизит вероятность ошибки с 1/256 до 1/65536. Хотя это и неплохая идея, од нако, она имеет тот недостаток, что применяемая формула расчета не «случайна» в должной степени — каждый суммируемый байт оказывает влияние лишь на один байт суммирующего регистра, при этом ширина самого регистра не имеет никакого значения. Например, во втором случае суммирующий регистр мог бы иметь ширину хоть мегабайт, однако ошибка все равно не была бы обнаружена. Проблема может быть решена лишь заменой простого суммирования более сложной функцией, чтобы каждый новый байт оказывал влияние на весь регистр контрольной суммы.

Таким образом, мы сформулировали 2 требования для формирования надежной контрольной суммы:

Ширина: Размер регистра для вычислений должен обеспечивать изна чальную низкую вероятность ошибки (например, 32 байтный ре гистр обеспечивает вероятность ошибки 1/2 32 ).

Случайность: Необходим такой алгоритм расчета, когда каждый новый байт может оказать влияние на любые биты регистра.

Обратите внимание, что хотя термин «контрольная сумма» изначально описы вал достаточно простые суммирующие алгоритмы, однако, в настоящее время он используется в более широком смысле для обозначения сложных алгоритмов рас чета, таких как CRC. Алгоритмы CRC, которые мы и будем рассматривать в даль нейшем, очень хорошо удовлетворяют второму условию и, кроме того, могут быть адаптированы для работы с различной шириной контрольной суммы.

3. Основная идея, заложенная в алгоритме CRC

С чего нам следует начать в наших поисках алгоритмов более сложных, чем про стое суммирование? На ум приходят самые экзотичные схемы. Мы могли бы соста

вить таблицы с использованием цифр числа , или хешировать каждый поступающий байт по всем байтам содержимого регистра. Мы могли бы даже держать в памяти ог ромный телефонный справочник и использовать каждый поступающий байт, ском бинированный с содержимым регистра, в качестве индекса нового телефонного но мера, который затем загружается в регистр. Возможности безграничны.

Однако, нам нет необходимости заходить столь далеко — надо всего лишь сменить арифметическое действие. Если сложение, очевидно, не пригодно для формирования эффективной контрольной суммы, то таким действием вполне может оказаться деле ние при условии, что делитель имеет ширину регистра контрольной суммы.

Основная идея алгоритма CRC состоит в представлении сообщения виде огром ного двоичного числа, делении его на другое фиксированное двоичное число и ис пользовании остатка этого деления в качестве контрольной суммы. Получив сооб щение, приемник может выполнить аналогичное действие и сравнить полученный остаток с «контрольной суммой» (переданным остатком).

Приведу пример. Предположим, что сообщение состоит из 2 байт (6, 23), как в предыдущем примере. Их можно рассматривать, как шестнадцатеричное число 0167h, или как двоичное число 0000 0110 0001 0111. Предположим, что ширина реги стра контрольной суммы составляет 1 байт, а в качестве делителя используется 1001, тогда сама контрольная сумма будет равна остатку от деления 0000 0110 0001 0111 на 1001. Хотя в данной ситуации деление может быть выполнено с использованием стандартных 32 битных регистров, в общем случае это не верно. Поэтому воспользу емся делением «в столбик», которому нас учили в школе (вспомнили?). Только на этот раз, оно будет выполняться в двоичной системе счисления:

Элементарное руководство по CRC алгоритмам обнаружения ошибок

Источник

Williams R.N. A Painless Guide to CRC Error Detection Algorithms

This document explains CRCs (Cyclic Redundancy Codes) and their table-driven implementations in full, precise detail. Much of the literature on CRCs, and in particular on their table-driven implementations, is a little obscure. This document is an attempt to provide a clear and simple no-nonsense explanation of CRCs and to absolutely nail down every detail of the operation of their high-speed implementations. In addition to this, this document presents a parameterized model CRC algorithm called the «Rocksoft^tm Model CRC Algorithm». The model algorithm can be parameterized to behave like most of the CRC implementations around, and so acts as a good reference for describing particular algorithms. A low-speed implementation of the model CRC algorithm is provided in the C programming language. Lastly there is a section giving two forms of high-speed table driven implementations, and providing a program that generates CRC lookup tables.

Introduction: Error Detection
The Need For Complexity
The Basic Idea Behind CRC Algorithms
Polynomical Arithmetic
Binary Arithmetic with No Carries
A Fully Worked Example
Choosing A Poly
A Straightforward CRC Implementation
A Table-Driven Implementation
A Slightly Mangled Table-Driven Implementation
«Reflected» Table-Driven Implementations
«Reversed» Polys
Initial and Final Values
Defining Algorithms Absolutely
A Parameterized Model For CRC Algorithms
A Catalog of Parameter Sets for Standards
An Implementation of the Model Algorithm
Roll Your Own Table-Driven Implementation
Generating A Lookup Table
Summary
Corrections

Источник

Статья на тему: «CRC-алгоритмы обнаружения ошибок»

1.Обнаружение ошибок

При передаче сообщений по низкокачественным каналам связи, возможно внесение искажений в эти сообщения, и поэтому необходимо предусмотреть методы, с помощью которых приемник сможет определить, были ли ошибки при передаче. При обнаружении ошибок, приемник может запросить повторную передачу сообщения. Для обнаружения применяются методы контрольных сумм. Передатчик подсчитывает некоторое значение, называемое контрольной суммой, являющееся функцией сообщения, и добавляет его в конец сообщения. Приемник использует ту же функцию для подсчета контрольной суммы, и сравнивает полученное значение с записанным в конце сообщения. Например, в качестве контрольной суммы можно взять сумму байт сообщения по модулю 256.

Сообщение 06 23 04
Сообщение с контрольной суммой 06 23 04 33
Сообщение после передачи 06 27 04 33

В этом примере возникла ошибка во втором байте, и вместо числа 23 было передано число 27. Такую ошибку приемник может определить, вычислив контрольную сумму (37) и сравнив ее с полученной (33). Если бы сбой произошел в четвертом байте — в самой контрольной сумме, то верное сообщение было бы забраковано. Но главная опасность в том, что ошибка может произойти как в самом сообщении, так и в контрольной сумме, причем таким образом, что контрольная сумма от неверного сообщения совпадет с неверно переданной контрольной суммой. К сожалению, полностью избежать такой ситуации нельзя, можно лишь уменьшать вероятность ее возникновения, увеличивая длину контрольной суммы.

Существуют различные методы обнаружения ошибок, некоторые из которых преобразуют сообщение, дополняя его избыточной информацией. Методы CRC (Cyclic Redundancy Codes) относятся к методам, которые оставляют без изменения исходный текст сообщения, добавляя контрольную сумму в конец.

2. Необходимость использования сложных алгоритмов

Рассмотрим предыдущий пример в ситуации, когда ошибка не может быть обнаружена:

Сообщение 06 23 04
Сообщение с контрольной суммой 06 23 04 33
Сообщение после передачи 06 27 04 37

Вероятность возникновения такой ошибки — 1/256. Для улучшения детекции ошибок можно перейти от 8-битной контрольной суммы к 16-битной, т.е. суммируя по модулю 65536. Это, очевидно, уменьшит вероятность пропуска ошибки до 1/65536. Но в данном примере увеличение длины контрольной суммы не позволит отловить ошибку. Происходит это в результате того, что при использовании в качестве контрольной функции операции сложения, почти каждый байт исходного сообщения влияет только на один байт контрольной суммы, вне зависимости от ее длины. Поэтому, помимо длины контрольной суммы, обращают внимание на такой фактор как хаотичность: формула должна позволять любому байту сообщения изменять любое количество бит контрольной суммы.

3. Основная идея CRC-алгоритмов

Тем не менее в основе контрольной функции CRC-алгоритмов тоже лежит арифметическая операция — деление. Последовательность бит исходного сообщения рассматривается как огромное двоичное число, производится его целочисленное деление на фиксированное значение, и остаток берется в качестве контрольной суммы.

4. Полиномиальная арифметика

Схема деления методов CRC несколько отличается от обычной. В этом пункте приводятся некие идеи, позволяющие глубже понять сущность применяемых схем.

В связи с алгоритмами CRC часто можно встретить термин «полиномиальный». Говорят, что данный CRC алгоритм использует некий полином, и.т.п. И, вообще, говорят, что CRC алгоритмы используют полиномиальную арифметику. Что же это такое?

Делимое, делитель, частное и остаток рассматриваются не как положительные целые, а как полиномы с двоичными коэффициентами. То есть число записывается в виде двоичной строки, биты которой служат коэффициентами полинома. Например числу 23 (010111b) отвечает полином:

1*x^4 + 0*x^3 + 1*x^2 + 1*x^1 + 1*x^0, или проще:

Мы можем осуществлять арифметические операции, понимая их как операции над полиномами. Например, умножим 13 (01101b) на 11 (01011b):

(x^3 + x^2 + x^0)(x^3 + x^1 + x^0) =

(x^6 + x^4 + x^3 + x^5 + x^3 + x^2 + x^3 + x^1 + x^0) =

x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0

Для того, чтобы получить в ответе 143, мы должны в качестве x взять 2 и привести коэффициенты к двоичным, перенеся 1 из 3*x^3 в старшие разряды. Получаем:

x^7 + x^3 + x^2 + x^1 + x^0 , что соответствует 010001111b = 143

Это обычная арифметика, просто основание системы счисления здесь выписано явно. Суть в том, что если мы не знаем х, то мы не можем производить переносы. Мы не знаем, что 3*x^3 это то x^4+x^3, потому что мы не знаем, что х = 2. В полиномиальной арифметике отношения между коэффициентами не определены, и коэффициенты при разных степенях полностью изолированы.

Можно придумать различные типы полиномиальной арифметики, определяя правила работы с коэффициентами. Для нас важна одна из схем — «полиномиальная арифметика по модулю 2», где все коэффициенты вычисляются по модулю 2, и отсутствуют переносы. Возвращаясь к предыдущему примеру:

(x^3 + x^2 + x^0)(x^3 + x^1 + x^0) =

(x^6 + x^4 + x^3 + x^5 + x^3 + x^2 + x^3 + x^1 + x^0) =

x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0 =

x^6 + x^5 + x^4 + x^3 + x^2 + x^1 + x^0

В дальнейшем мы не будем упоминать полиномиальное представление, поскольку это может только усложнить изложение, и будем говорить об эквивалентной системе, назывемой «двоичная арифметика без переноса».

5. Двоичная арифметика без переноса

Как нетрудно заметить с отменой переноса исчезают и различия между сложением и вычитанием. Для каждой позиции есть 4 варианта:

        0 + 0 = 0 — 0 = 0 0 + 1 = 0 — 1 = 1 1 + 0 = 1 — 0 = 1 1 + 1 = 1 — 1 = 0

То есть, фактически, сложение и вычитание в CRC-арифметике эквивалентны операции XOR, которая является обратной самой себе, и это очень удобно.

Объединение сложения и вычитания приводит также к исчезновению строгого отношения порядка. Действительно, то что 1010b больше чем 10b остается верным, но уже нет причин полагать, что 1010b больше чем 1001b. Поэтому вводится слабое отношение порядка: число X больше числа Y если номер позиции старшей 1-ы числа X больше номера позиции старшей 1-ы числа Y ( этот номер, отсчитываемый с 0, называется длиной числа ). Умножение и деление выполняются по обычным схемам с учетом новых операций сложения и вычитания.

Полезно ввести понятия кратности и делимости. Будем говорить, что число A кратно числу B ( или A делится на B) в смысле CRC-арифметики, если A можно получить складывая (XOR) результаты различных сдвигов ( влево) числа B. Например, 0111010110b кратно 011b:

Однако 0111010111b нельзя составить из сдвигов 011b, поэтому говорят, что 0111010111b не делится на 011b.

6. Пример вычисления CRC

Теперь, когда построена CRC-арифметика, мы можем интерпретировать вычисление CRC как взятие остатка от деления. В этом пункте мы обсудим некоторые детали и приведем пример.

Во первых, необходимо выбрать делитель. Его длина должна на один бит превосходить требуемую длину CRC. То есть, для получения CRC16 необходимо взять 17-битный делитель (в соответствии с данным выше определением его длина будет равна 16-и). Поскольку остаток должен быть меньше делителя (иначе можно выполнить деление еще раз), мы получим CRC требуемой длины.

Взяв произвольный делитель, мы придем к некоему CRC-алгоритму. Однако не все делители дают хорошие результаты, поэтому следует с осторожностью подходить к их выбору.

Перед делением сообщение дополняют W нулями ( где W — длина делителя)

Исходное сообщение 1101011011
Делитель 10011
Дополненное сообщение 11010110110000

CRC дописывается в конец, так что передаваемое сообщение будет таким: 11010110111110. Приемник может либо:

  • Отделить контрольную сумму от сообщения, добавить нули в конец сообщения, сосчитать CRC, и сравнить с полученным.
  • Расчитать CRC всей переданной цепочки ( не добавляя нули ) и посмотреть получится ли ноль.

Далее мы будем использовать второй способ.

Итак операции которые необходимо произвести, при использовании CRC-алгоритмов:

  • Выбрать длину W и делитель G ( длиной W ).
  • Добавить W нулей к исходному сообщению М. ( Полученное обозначим M’ )
  • Разделить M’ на G используя CRC-арифметику. Остаток — контрольная сумма.

7. Выбор делителя

Вопрос о правильном выборе делителя крайне нетривиален. В большинстве случаев лучше положиться на стандартные испытанные значения. В этом разделе не содержится исчерпывающих сведений по выбору делителя, его цель, скорее, в том, чтобы отбить желание выдумывать собственные делители.

Во-первых отметим, что передаваемое сообщение Т кратно делителю. Действительно, последние W бит сообщения T — остаток от деления сообщения M’ на делитель. То есть Т=М’+CRC ( + означает сложение, а не добавление в конец ), а поскольку сложение одновременно является вычитанием, то оно «уменьшает» M’ до ближайшего кратного G.

Теперь предположим, что сообщение пришло с ошибками. Это можно записать так T+E, где E — вектор ошибки. Приемник делит T+E на G. Поскольку T mod G = 0, то (T+E) mod G = E mod G. Поэтому способность метода отлавливать специфические ошибки будет определяться количеством E кратных G, поскольку такие помехи не могут быть обнаружены.

Перечислим некоторые типы ошибок, которые мы в первую очередь можем ожидать при передаче.

  • Ошибка в одном бите. E = 100. 000. Такие ошибки можно отловить если в G не менее 2-х битов установлено в 1. Как бы мы ни складывали сдвиги таких G, всегда получим строку в которой по крайней мере 2 еденичных бита, значит E не может быть кратно G.
  • Ошибка в двух битах. Надо подобрать G для которых не служат кратными числа типа 011, 0101, 01001, и.т.д. О таких G см. Tanenbaum A.S. «Computer Networks».
  • Ошибка в нечетном количестве бит. Такие ошибки можно отловить, взяв G, в котором количество бит четно. Это следует из того,что:
    • CRC-умножение это просто XOR константы в аккумулятор с разными сдвигами.
    • XOR — поразрядная операция, инвертирующая те биты аккумулятора, напротив которых в прибавляемой строке стоит 1.
    • Если сделать XOR значения с четным числом едениц, то в аккумуляторе инвертируется четное количество едениц, в результате чего четность количества едениц аккумулятора сохранится.
  • Ошибка пакета. E = 000. 000111. 111000. 000. То есть ошибка локализована в непрерывном пакете внутри сообщения. Такое E можно представить в виде произведения: E=(1000. 00)*(111111111), где в первой скобке Z нулей, а во второй — N едениц. Если в G младший бит — 1, то левый множитель не может быть делителем G, тогда, если G длиннее правого множителя, то ошибка будет отловлена.

Приводим ниже наиболее известные делители ( в скобках номера единичных битов ):

8. Особенности различных алгоритмов

Основная задача данного обзора — дать полную спецификацию CRC-16. Этот вариант — один из наиболее простых из класса CRC. Он полностью укладывается в теоретическую модель, описанную выше. Однако тема связанная с алгоритмами CRC этим далеко не исчерпывается. Например, реализация этих алгоритмов составляет отдельный вопрос, не менее, а, скорее, и более сложный чем теоретические основы. На первый взгляд с реализацией не может возникнуть никаких вопросов, поскольку операции XOR и SHL включены в систему команд любого процессора, что позволяет создать простую и эффективную реализацию на машинном языке. Однако в последнее время стали популярными сложные алгоритмы, далеко отстоящие от обычной чхемы деления. В первую очередь это так называемая табличная реализация и ее модификации, цель которых в переходе от цикла по всем битам к циклу по большим порциям данных — байтам, словам, и.т.д. Особенности различных реализаций привели к новым модификациям самого алгоритма. Появились такие понятия как начальное и конечное значения.

Начальное ( стартовое ) значение записывается в аккумулятор перед началом генерации CRC. Алгоритм CRC16 в инициализирует аккумулятор нулем, выполняя обыкновенную схему деления. Однако выбор нулевого стартового значения не самый удачный. Нетрудно видеть, что нули в начале сообщения являются «слепым пятном» алгоритма ( т. е. их число не повлияет на значение контрольной суммы ), и, в то же время, такие сообщения достаточно часто встречаются. Введение начального значения, которое с точки зрения схемы деления эквивалентно дописыванию в начало сообщения ненулевого значения позволяет обойти эту проблему.

Конечное значение просто складывается (XOR) с остатком деления.

Модификация CRC16/CITT использует стартовое значение FFFFh. Алгоритм CRC32 использует FFFFFFFFh в качестве начального и конечного значений.

Источник

Рубрика:

Карьера/Образование / 
Кафедра

Facebook

Twitter

Мой мир

Вконтакте

Одноклассники

Google+

КРИС КАСПЕРСКИ

Могущество кодов Рида-Соломона,

или Информация, воскресшая из пепла

Энтропия слепа, но терпелива. Рано или поздно, обстреливая наши позиции по квадратам, она нанесет удар по штабу

 по центру связи. И тогда первая линия обороны уничтожена. И приходится отходить на запасные позиции.

Иными словами, доставать из магнитотеки пакет дисков с копией тома. 

Е. В. Лишак

«Тридцать второй день года. (Записки парасистемного программиста)».

Все вы наверняка слышали о существовании помехозащитных кодов Рида-Соломона, широко использующихся в устройствах передачи и хранения данных для обнаружения и исправления как одиночных, так и групповых (!) ошибок. Область их применения необычайно широка – кодеры/декодеры Рида-Соломона можно найти и в ленточных запоминающих устройствах, и в контроллерах оперативной памяти, и в модемах, и в жестких дисках, и в CD-ROM/DVD-приводах и т. д. Благодаря им некоторые продвинутые архиваторы безболезненно переносят порчу нескольких секторов носителя, содержащего архив, а подчас и полное разрушение целого тома многотомного архива. Еще коды Рида-Соломона позволяют защитному механизму автоматически восстанавливать байтики, хакнутые взломщиком и/или искаженные в результате сбоя программного/аппаратного обеспечения. Короче говоря, если владение техникой помехозащитного кодирования не превращает вас в Бога, то, по крайней мере, поднимает на Олимп, где среди бесшумных вентиляторов и безглючных операционных систем снуют великие компьютерные гуру.

В то же время лишь немногие программисты могут похвастаться собственной реализацией алгоритмов Рида-Соломона. Да и зачем? Готовых библиотек море: от прагматичных коммерческих пакетов до бесплатных исходников, распространяемых по лицензии GNU. Как говорится, бери – не хочу. Что ж, в использовании библиотек есть вполне определенный практический смысл, но никакой хакер не доверит управление программе до тех пор, пока не поймет, как именно она работает (а эта публикация именно для хакеров и предназначена, естественно, «хакеров» в хорошем значении этого слова).

С другой стороны, при анализе программного обеспечения, распространяемого без исходных кодов, вы не сможете идентифицировать алгоритм Рида-Соломона, если только заранее не разберетесь во всех его тонкостях. Допустим, вам встретилась защита, хитрым образом манипулирующая с EDC/ECC-полями ключевых секторов, считанных ею с лазерного диска, и каждый такой сектор содержит две умышленно внесенные ошибки (плюс еще ошибки, естественным путем возникающие при небрежном обращении с CD), причем одна из этих ошибок ложная и исправлять ее не нужно. При штатном копировании защищенного диска микропроцессорная начинка CD-ROM автоматически исправляет все ошибки, которые она только может исправить, в результате чего происходит искажение ключевых меток и, как следствие, защищенная программа перестанет работать. Можно, конечно, скопировать диск в «сыром» режиме, т.е. без исправления ошибок, но тогда копия будет содержать как непредумышленные, так и предумышленные ошибки, в результате чего даже при незначительном повреждении оригинала корректирующих возможностей кодов Рида-Соломона уже окажется недостаточно, и диск просто перестанет читаться (а как вы хотели? копирование дисков в сыром режиме ведет к накоплению ошибок и потому крайне непрактично с любой точки зрения).

Владение базовыми принципами помехозащитного кодирования позволит вам разобраться с логикой работы защитного механизма и понять, какие конкретные ошибки следует исправлять, а какие нет.

К сожалению, подавляющее большинство публикаций на тему кодов Рида-Соломона написаны на языке высшей математики, для постижения которой и университетских знаний подчас оказывается недостаточно (да и все ли хакеры знают математику?), в результате чего все эти сильно теоретизированные руководства забрасываются на полку.

Программная реализация корректирующих кодов Рида-Соломона действительно очень сложна и действительно требует определенной математической подготовки, изложение основ которой может показаться скучным и неинтересным для «системщиков» и «железячников», но иного пути, видимо, нет. В конце концов никто не обещал вам, что быть программистом – легко, а хорошим программистом быть еще труднее. Так что не говорите потом, что я вас не предупреждал! Шутка! Расслабтесь и разгоните свой страх перед высшей математикой прочь. По ходу описания вам встретится пара формул (ну куда же в математике без формул?), но во всех остальных случаях я буду говорить на интернациональном программистском языке – языке Си, понятным любому системщику. В общем, пристегивайте ремни и поднимайте свои головы с клавиатуры – мы поехали! Конечной целью нашего путешествия станет построение отказоустойчивого RAID-массива 5 уровня на базе… нескольких обыкновенных IDE/SCSI-контроллеров жестких дисков. 4/5 объема такого массива будут отданы непосредственно под полезные данные, а 1/5 – под избыточную информацию, позволяющую восстановить содержимое любого жесткого диска из пяти данных, даже если он будет полностью уничтожен!

Корректирующие коды и помехоустойчивое кодирование (азы)

Персональные компьютеры с их битами и байтами настолько прочно вошли в нашу жизнь, что программисты вообще перестали задумываться о теории кодирования информации, принимая ее как должное. Между тем, здесь все не так просто, как может показаться на первый взгляд.

Фактически кодирование есть ни что иное, как преобразование сообщения в последовательность кодовых символов, так же называемых кодовыми словами. Любое дискретное сообщение состоит из конечного числа элементов: в частности, текст состоит из букв, изображение состоит из пикселей, машинная программа состоит из команд и т. д., – все они образуют алфавит источника сообщения. При кодировании происходит преобразование элементов сообщения в соответствующие им числа – кодовые символы, причем каждому элементу сообщения присваивается уникальная совокупность кодовых символов, называемая кодовой комбинацией. Совокупность кодовых комбинаций, образующих сообщение, и есть код. Множество возможных кодовых символов называется кодовым алфавитом, а их количество (далее по тексту обозначаемое малой латинской m) – основанием кода.

Впрочем, все это вы уже наверняка знаете (а если не знаете, то без труда найдете исчерпывающее объяснение основ кодирования в любом учебнике по информатике), но знаете ли вы, что такое расстояние Хемминга? Это минимальное количество различий между двумя различными допустимыми кодовыми словами и в теории помехоустойчивого кодирования расстояние Хемминга играет основополагающую роль. Рассмотрим, например, следующий 4-битный код:

Листинг 1. Пример простейшего 4-битного кода с расстоянием Хемминга, равным единице. Такой код широко используется в вычислительной технике, несмотря на его невозможность обнаружить ошибки. 

0 –> 0000; 4 –> 0100;   8 –> 1000;   12 –> 1100;

1 –> 0001; 5 –> 0101;   9 –> 1001;   13 –> 1101;

2 –> 0010; 6 –> 0110;   10 –> 1010;  14 –> 1110;

3 –> 0011; 7 –> 0111;   11 –> 1011;  15 –> 1111;

Это обыкновенный двоичный код, который можно встретить в некоторых однокристаллках, вмещающий в свои 4 бита 16 символов (т.е. с его помощью можно закодировать 16 букв алфавита). Как нетрудно убедиться, два любых символа отличаются по меньшей мере на один бит, следовательно, расстояние Хемминга для такого кода равно единице (что условно обозначает как d = 1).

А вот другой 4-битный код:

Листинг 2. Пример 4-битного кода с расстоянием Хемминга, равным двум, способного обнаруживать одиночные ошибки.

0 –> 0000; 4 –> 1001;

1 –> 0011; 5 –> 1010;

2 –> 0101; 6 –> 1100;

3 –> 0110; 7 –> 1111;

На этот раз два произвольных символа отличаются как минимум в двух позициях, за счет чего информационная емкость такого кода сократилась с 16 до 8 символов. «Постойте-постойте! – воскликнет иной читатель. – Что это за бред? Куда делась комбинация 0001 или 0010, например?». Нет, это не бред, и указанных комбинаций бит в данном коде действительно нет, точнее, они есть, но объявлены запрещенными. Благодаря этому обстоятельству наш подопечный код способен обнаруживать любые одиночные ошибки. Возьмем, например, символ «1010» и исказим в нем произвольный бит (но только один!). Пусть это будет второй слева бит, тогда искаженный символ станет выглядеть так: «1110». Поскольку комбинация «1110» является запрещенной, декодер может засвидетельствовать наличие ошибки. Увы, только засвидетельствовать, но не исправить, т.к. для исправления даже одного-единственного сбойного байта требуется увеличить расстояние Хемминга как минимум до трех. Поскольку 4-битный код с d = 3 способен вмещать в себя лишь два различных символа, то он крайне ненагляден и потому нам лучше выбрать код с большей разрядностью. Хорошо, пусть это будет 10-битный код с d = 5.

Листинг 3. Пример 10-битного кода, с расстоянием Хемминга, равным пяти, способного обнаруживать 4-битные ошибки, а исправлять 2-битные.

0000000000 0000011111   1111100000   1111111111

Возьмем, к примеру, символ «0000011111» и искорежим два любых бита, получив в итоге что-то наподобие: «0100110111». Поскольку такая комбинация является запрещенной, декодер понимает, что произошла ошибка. Достаточно очевидно, что если количество сбойных бит меньше расстояния Хемминга хотя бы наполовину, то декодер может гарантированно восстановить исходный символ. Действительно, если между двумя любыми разрешенными символами существует не менее пяти различий, то искажение двух бит всякого такого символа приведет к образованию нового символа (обозначим его k), причем расстояние Хемминга между k и оригинальным символом равно числу непосредственно искаженных бит (т.е. в нашем случае двум), а расстояние до ближайшего соседнего символа равно: d – k (т.е. в нашем случае трем). Другими словами, пока d – k > k декодер может гарантированно восстановить искаженный символ. В тех случаях, когда d > k > d – k, успешное восстановление уже не гарантируется, но при удачном стечении обстоятельств все-таки оказывается в принципе возможным.

Возвращаясь к нашему символу «0000011111», давайте на этот раз исказим не два бита, а четыре: «0100110101» и попробуем его восстановить. Изобразим процесс восстановления графически:

Листинг 4. Восстановление 4-битной ошибки. 

0000000000 0000011111   1111100000   1111111111

0100110101 0100110101   0100110101   0100110101

———-  ———-  ———-   ———-

5 отличий   4 отличия   6 отличий    5 отличий

Грубо говоря, обнаружив ошибку, декодер последовательно сличает искаженный символ со всеми разрешенными символами алфавита, стремясь найти символ наиболее «похожий» на искаженный. Точнее, символ с наименьшим числом различий, а еще точнее, символ, отличающийся от искаженного не более чем в (d – 1) позициях. Легко видеть, что в данном случае нам повезло, и восстановленный символ совпал с истинным. Однако если бы четыре искаженных бита распределились бы так: «0111111111», то декодер принял бы этот символ за «1111111111» и восстановление оказалось бы неверным.

Таким образом, исправляющая способность кода определяется по следующей формуле: для обнаружения r ошибок расстояние Хемминга должно быть больше или равно r, а для коррекции r ошибок расстояние Хемминга должно быть по крайней мере на единицу больше удвоенного количества r.

Листинг 5. Корректирующие способности простого кода Хемминга. 

обнаружение ошибок:     d >= r

исправление ошибок:     d > 2r

информационная емкость: 2n/d

Теоретически количество обнаруживаемых ошибок неограничено, практически же информационная емкость кодовых слов стремительно тает с ростом d. Допустим, у нас есть 24 байта данных, и мы хотели бы исправлять до двух ошибок на каждый такой блок. Тогда нам придется добавить к этому блоку еще 49 байт, в результате чего реальная информационная емкость блока сократится всего… до 30%! Хорошенькая перспектива, не так ли? Столь плачевный результат объясняется тем, что биты кодового слова изолированы друг от друга и изменение одного из них никак не сказывается на окружающих. А что если…

Пусть все биты, номера которых есть степень двойки, станут играть роль контрольных битов, а оставшиеся и будут обычными («информационными») битами сообщения. Каждый контрольный бит должен отвечать за четность суммы[2] некоторой принадлежащей ему группы битов, причем один и тот же информационный бит может относиться к различным группам. Тогда один информационный бит сможет влиять на несколько контрольных и потому информационная емкость слова значительно (можно даже сказать, чудовищно) возрастет. Остается только выбрать наиболее оптимальное разделение сфер влияния.

Согласно методу помехозащитного кодирования, предложенного Хеммингом, для того чтобы определить, какие контрольные биты контролируют информационный бит, стоящий в позиции k, мы должны разложить k по степеням двойки:

Таблица 1. Разделение бит на контрольные и информационные

Позиция

Какими битами контролируется

1 (A)

20 = 1

Это контрольный бит, никто его не контролирует

2 (B)

21 = 2

Это контрольный бит, никто его не контролирует

3

20+21 = 1 + 2 = 3

Контролируется 1 и 2 контрольными битами

4 (C)

22 = 4

Это контрольный бит, никто его не контролирует

5

20+22 = 1 + 4 = 5

Контролируется 1 и 4 контрольными битами

6

21+22 = 2 + 4 = 6

Контролируется 2 и 4 контрольными битами

7

20+21+22 = 1 + 2 + 4 = 7

Контролируется 1, 2 и 4 контрольными битами

8 (D)

23 = 8

Это контрольный бит, никто его не контролирует

Давайте в порядке закрепления материала попробуем пощупать коды Хемминга «вживую» и вручную рассчитаем контрольную сумму 4-битного символа «0101». После резервирования «квартир» для контрольных битов (выделенных в тексте жирным шрифтом) наш символ будет выглядеть так: AB0C101D. Теперь остается только рассчитать значения битов A, B, C и D:

  • Бит A, контролирующий биты 3, 5 и 7 равен нулю, т.к. их сумма (0 + 1 + 1) четна.
  • Бит B, контролирующий биты 3, 6 и 7 равен одному, т.к. их сумма (0 + 0 + 1) нечетна.
  • Бит C, контролирующий биты 5, 6 и 7 равен нулю, т.к. их сумма (1 + 0 + 1) четна.
  • Бит D никакой роли не играет, т.к. он контролирует лишь те биты, которые расположены справа от него, а никаких битов справа от него уже и нет. И приведен он лишь затем, чтобы получить 8 бит – общепринятый байт.

Таким образом, «новоиспеченное» кодовое слово будет выглядеть так: «0100101», где жирным шрифтом выделены контрольные биты.

Листинг 6. Кодовое слово вместе с информационными битами. 

AB0C101D

12345678

Допустим, при передаче наше слово было искажено в одной позиции и стало выглядеть так: 0100111. Сможем ли мы обнаружить такую ошибку? А вот сейчас и проверим! Так, бит A должен быть равен: (0 + 1 + 1) % 2 = 0, что соответствует истине. Бит B должен быть равен (0 + 1 + 1) % 2 = 0, а в нашем слове он равен единице. Запомним номер «неправильного» контрольного бита и продолжим. Бит C должен быть равен (1 + 1 + 1) % 2 = 1, а он равен нулю! Ага, значит, контрольные биты в позициях 2 (бит B) и 4 (бит C) обнаруживают расхождение с действительностью. Их сумма (2 + 4 = 6) и дает позицию сбойного бита. Действительно, в данном случае номер искаженного бита равен 6, инвертируем его, тем самым восстанавливая наше кодовое слово в исходный вид.

А что если искажение затронет не информационный, а контрольный бит? Проверка показывает, что позиция ошибки успешно обнаруживается и в этом случае, и контрольный бит при желании может быть легко восстановлен по методике, уже описанной выше (только есть ли в этом смысл? ведь контрольные биты все равно «выкусываются» в процессе декодирования кодового слова).

На первый взгляд кажется, что коды Хемминга жутко неэффективны, ведь на 4 информационных бита у нас приходится 3 контрольных, однако поскольку номера контрольных бит представляют собой степень двойки, то с ростом разрядности кодового слова они начинают располагаться все реже и реже. Так, ближайший к биту C контрольный бит D находится в позиции 8 (т.е. в трех шагах), зато контрольный бит E отделен от бита D уже на (24 — 23 — 1) = 7 «шагов», а контрольный бит F и вовсе на (25 — 24 — 1) = 15 «шагов».

Таким образом, с увеличением разрядности обрабатываемого блока, эффективность кодов Хемминга стремительно нарастает, что и показывает следующая программа:

Листинг 7. Расчет эффективной информационной емкости кодов Хемминга для слов различной длины.

main()

{

    int a;

    int _pow = 1;

    int old_pow = 1;

    int N, old_N = 1;

    printf( «* * * hamming code efficiency test * * * by Kris Kaspersky «

           » BLOCK_SIZE    FUEL UP   EFFICIENCY «

           «———————————— «);

    for (a = 0; a < MAX_POW; a++)

    {

           N = _pow — old_pow — 1 + old_N;

           printf(«%8d   %8d   %8.1f%% «,_pow, N, (float) N/_pow*100);  

           // NEXT

           old_pow = _pow; _pow = _pow * 2; old_N = N;

    } printf(«———————————— «);

}

Листинг 8. Результат расчета эффективной информационной емкости кодов Хемминга для слов различной длины.  

BLOCK_SIZE    FUEL UP   EFFICIENCY

————————————

       1          0        0.0%

       2          0        0.0%

       4          1       25.0%

       8          4       50.0%

      16         11       68.8%

      32         26       81.3%

      64         57       89.1%

     128        120       93.8%

     256        247       96.5%

     512        502       98.0%

    1024       1013       98.9%

    2048       2036       99.4%

    4096       4083       99.7%

    8192       8178       99.8%

   16384      16369       99.9%

   32768      32752      100.0%

   65536      65519      100.0%

  131072     131054      100.0%

  262144     262125      100.0%

  524288     524268      100.0%

————————————

Из приведенной выше распечатки видно, что при обработке блоков, дотягивающихся хотя бы до 1024 бит, накладными расходами на контрольные биты можно полностью пренебречь.

К сожалению, коды Хемминга способны исправлять лишь одиночные ошибки, т.е. допускают искажение всего лишь одного сбойного бита на весь обрабатываемый блок. Естественно, с ростом размеров обрабатываемых блоков увеличивается и вероятность ошибок. Поэтому выбор оптимальной длины кодового слова является весьма нетривиальной задачей, как минимум требующей знания характера и частоты возникновения ошибок используемых каналов передачи информации. В частности, для ленточных накопителей, лазерных дисков, винчестеров и тому подобных устройств коды Хемминга оказываются чрезвычайно неэффективными. Зачем же тогда мы их рассматривали? А затем, что понять прогрессивные системы кодирования (к которым в том числе относятся и коды Рида-Соломона), ринувшись атаковать их «с нуля», практически невозможно, ибо они завязаны на сложной, действительно высшей математике, но ведь не Боги горшки обжигают, верно?

Идея кодов Рида-Соломона

Если говорить упрощенно, то основная идея помехозащитного кодирования Рида-Соломона заключается в умножении информационного слова, представленного в виде полинома D, на неприводимый полином G, известный обеим сторонам, в результате чего получается кодовое слово C, опять-таки представленное в виде полинома.

Декодирование осуществляется с точностью до наоборот: если при делении кодового слова C на полином G декодер внезапно получает остаток, то он может рапортовать наверх об ошибке. Соответственно, если кодовое слово разделилось нацело, его передача завершилась успешно.

Если степень полинома G (называемого так же порождающим полиномом) превосходит степень кодового слова по меньшей мере на две степени, то декодер может не только обнаруживать, но и исправлять одиночные ошибки. Если же превосходство степени порождающего полинома над кодовым словом равно четырем, то восстановлению поддаются и двойные ошибки. Короче говоря, степень полинома k связана с максимальным количеством исправляемых ошибок t следующим образом: k = 2*t. Следовательно, кодовое слово должно содержать два дополнительных символа на одну исправляемую ошибку. В то же время максимальное количество распознаваемых ошибок равно t, т.е. избыточность составляет один символ на каждую распознаваемую ошибку.

В отличие от кодов Хемминга, коды Рида-Соломона могут исправлять любое разумное количество ошибок при вполне приемлемом уровне избыточности. Спрашиваете, за счет чего это достигается? Смотрите, в кодах Хемминга контрольные биты контролировали лишь те информационные биты, что находятся по правую сторону от них и игнорировали всех «левосторонних» товарищей. Обратимся к таблице 1: добавление восьмого контрольного бита D ничуть не улучшило помехозащищенность кодирования, поскольку контрольному биту D было некого контролировать. В кодах же Рида-Соломона контрольные биты распространяют свое влияние на все информационные биты и потому с увеличением количества контрольных бит увеличивается и количество распознаваемых/устраняемых ошибок. Именно благодаря последнему обстоятельству, собственно, и вызвана ошеломляющая популярность корректирующих кодов Рида-Соломона.

Теперь о грустном. Для работы с кодами Рида-Соломона обычная арифметика, увы, не подходит и вот почему. Кодирование предполагает вычисления по правилам действия над многочленами, с коэффициентами которых надо выполнять операции сложения, вычитания, умножения и деления, причем все эти действия не должны сопровождаться каким-либо округлением промежуточных результатов (даже при делении!), чтобы не вносить неопределенность. Причем и промежуточные, и конечные результаты не имеют права выходить за пределы установленной разрядной сетки… «Постойте! – воскликнет внимательный читатель. – Да ведь это невозможно! Чтобы при умножении и не происходило «раздувания» результатов, кто же в этот бред поверит?!»

Впрочем, если как следует подумать головой, частично призвав на помощь и другие части тела, можно сообразить, что умножать информационное слово на порождающий полином вовсе не обязательно, можно поступить гораздо хитрее:

  • Добавляем к исходному информационному слову D справа k нулей, в результате чего у нас получается слово длины n = m + r и полином Xr*D, где m – длина информационного слова.
  • Делим полученный полином Xr*D на порождающий полином G и вычисляем остаток от деления R, такой что: Xr*D = G*Q + R, где Q – частное, которое мы благополучно игнорируем за ненадобностью, – сейчас нас интересует только остаток.
  • Добавляем остаток R к информационному слову D, в результате чего получаем симпатичное кодовое слово C, информационные биты которых хранятся отдельно от контрольных бит. Собственно, тот остаток, который мы получили в результате деления, и есть корректирующие коды Рида-Соломона. Между нами говоря, способ кодирования, при котором информационные и контрольные символы хранятся раздельно, называется систематическим кодированием и такое кодирование весьма удобно с точки зрения аппаратной реализации.
  • Мысленно прокручиваем предыдущие пункты, пытаясь обнаружить, на какой же стадии вычислений происходит выход за разрядную сетку и… такой стадии нет! Остается лишь отметить, что информационное слово + корректирующие коды можно записать как: T = Xr*D + R = G*Q.

Декодирование полученного слова T осуществляется точно так же, как уже и было описано ранее. Если при делении T (которое в действительности является произведением G на Q) на порождающий полином G образуются остаток, то слово T искажено и, соответственно, наоборот.

Теперь вопрос на засыпку. Как вы собираетесь осуществлять деление полиномов в рамках общепринятой алгебры? В целочисленной арифметике деление определено не для всех пар чисел (вот, в частности, 2 нельзя разделить на 3, а 9 нельзя разделить на 4, без потери значимости, естественно). Что же касается «плавучки», то ее точность еще та (в смысле точность катастрофически недостаточная для эффективного использования кодов Рида-Соломона), к тому же она достаточно сложна в аппаратной реализации. Ладно, в IBM PC с процессором Pentium быстродействующий математический сопроцессор всем нам дан по дефолту, но что делать разработчикам ленточных накопителей, винчестеров, CD-приводов, наконец? Пихать в них четвертый Пень?! Нет уж, увольте, лучше воспользоваться специальной арифметикой – арифметикой конечных групп, называемых полями Галуа. Достоинство этой арифметики в том, что операции сложения, вычитания, умножения и деления определены для всех членов поля (естественно, исключая ситуацию деления на ноль), причем число, полученное в результате любой из этих операций, обязательно присутствует в группе! Т.е. при делении любого целого числа A, принадлежащего множеству 0…255, на любое целое число B из того же множества (естественно, B не должно быть равно нулю), мы получим число C, входящее в данное множество. А потому потерь значимости не происходит, и никакой неопределенности не возникает!

Таким образом, корректирующие коды Рида-Соломона основаны на полиномиальных операциях в полях Галуа и требуют от программиста владения сразу несколькими аспектами высшей математики из раздела теории чисел. Как и все «высшее», придуманное математиками, поля Галуа есть абстракция, которую невозможно ни наглядно представить, ни «пощупать» руками. Ее просто надо принять как набор аксиом, не пытаясь вникнуть в его смыл, достаточно всего лишь знать, что она работает, вот и все. А еще есть полиномы немеряных степеней и матрицы в пол-Европы, от которых нормальный системщик не придет в восторг (увы, программист-математик скорее исключение, чем правило).

Поэтому, прежде чем ринуться в непроходимые джунгли математического леса абстракций, давайте сконструируем макет кодера/декодера Рида-Соломона, работающий по правилам обычной целочисленной алгебры. Естественно, за счет неизбежного в этом случае расширения разрядной сетки такому кодеру/декодеру будет очень трудно найти практическое применение, но… зато он нагляден и позволяет не только понять, но и почувствовать принцип работы корректирующих кодов Рида-Соломона.

Мы будем исходить из того, что если g = 2n + 1, то для любого a из диапазона 0…2n, произведение a*g = c (где с – кодовое слово), будет представлять, по сути, полную мешанину битов обоих исходных чисел.

Допустим n = 2, тогда g = 3. Легко видеть: на что бы мы не умножали g – хоть на 0, хоть на 1, хоть на 2, хоть на – 3, полученный результат делится нацело на g в том и только  том случае, если никакой из его бит не инвертирован (то есть, попросту говоря, одиночные ошибки отсутствуют).

Остаток от деления однозначно указывает на позицию ошибки (при условии, что ошибка одиночная, групповые же ошибки данный алгоритм исправлять не способен). Точнее, если ошибка произошла в позиции x, то остаток от деления k будет равен k = 2x. Для быстрого определения x по k можно воспользоваться тривиальным табличным алгоритмом. Впрочем, для восстановления сбойного бита знать его позицию совершенно необязательно, достаточно сделать R = e ^ k, где e – искаженное кодовое слово, ^ – операция XOR, а R – восстановленное кодовое слово.

В общем, законченная реализация кодера/декодера может выглядеть так:

Листинг 9. Простейший пример реализации кодера/декодера Рида-Соломна, работающего по обычной арифметике (т.е. с неоправданным расширением разрядной сетки), и исправляющим любые одиночные ошибки в одном 8-битном информационном слове (впрочем, программу легко адаптировать и под 16-байтовые информационные слова). Обратите внимание, что кодер реализуется чуть ли не на порядок проще декодера. В настоящем декодере Рида-Соломна, способном исправлять групповые ошибки, этот разрыв еще значительнее.

// ВНИМАНИЕ! данный кодер/декодер построен на основе обычной арифметики, не арифметики полей Галуа,

// в результате чего его практические возможности более чем ограничены, тем не менее он нагляден и удобен для

// изучения

#include

// ширина входного информационного символа (бит)

#define SYM_WIDE 8 

// входные данные (один байт)

#define DATAIN   0x69

// номер бита, который будет разрушен сбоем

#define ERR_POS  3

// неприводимый полином

#define MAG (1<<(SYM_WIDE*1) + 1<<(SYM_WIDE*0))

// ——————————————————

// определение позиции ошибки x по остатку k от деления кодового слова на полином k = 2^x, где «^» – возведение

// в степень; функция принимает k и возвращает x

// ——————————————————

int pow_table[9] = {1,2,4,8,16,32,64,128,256};

lockup(int x) {int a;for(a=0;a<9;a++) if(pow_table[a]==x)return a; return -1;}

main()

{

    int i; int g; int c; int e; int k;

    fprintf(stderr,»simplest Reed-Solomon endoder/decoder by Kris Kaspersky «);

    // входные данные (информационное слово)

    i = DATAIN;

    // неприводимый полином

    g = MAG;

    printf(«i = %08x    DATAIN) g = %08x   (POLYNOM) «, i, g);   

    // КОДЕР РИДА-СОЛОМОНА (простейший, но все-таки кое-как работающий).

    // Вычисляем кодовое слово, предназначенное для передачи

    c = i * g;   printf(«c = %08x    (CODEWORD) «, c);

    // конец КОДЕРА  

    // передаем с искажениями

    e = c ^ (1<

    /* ^^^^ искажаем один бит, имитируя ошибку передачи */   

    // ДЕКОДЕР РИДА-СОЛОМОНА

    // проверяем на наличие ошибок передачи (фактически это простейший декодер Рида-Соломона)

    if (e % g)

    {

           // ошибки обнаружены, пытаемся исправить

           printf(«RS decoder says: (%x) error detected { «, e % g);

           // k = 2^x, где x — позиция сбойного бита

           k = (e % g);

           printf(» 0 to 1 err  position: %x «, lockup(k));

           printf(» restored codeword is: %x } «, (e ^= k));

    }

    printf(«RECEIVED DATA IS: %x «, e / g);

    // КОНЕЦ ДЕКОДЕРА

} 

Листинг 10. Результат работы простейшего кодера/декодера Рида-Соломона. Обратите внимание: искаженный бит удалось успешно исправить, однако для этого к исходному информационному слову пришлось добавить не два, а целых три бита (если вы возьмете в качестве входного слова максимально допустимое 8-битное значение 0xFF, то кодовое слово будет равно 0x1FE00, а так как 210 = 10000, то свободных разрядов уже не хватает и приходится увеличивать разрядную сетку до 211, в то время как младшие биты кодового слова фактически остаются незадействованными и «правильный» кодер должен их «закольцевать», грубо говоря замкнув обрабатываемые разряды на манер кольца.    

i = 00000069    (DATAIN)

g = 00000200    (POLYNOM)

c = 0000d200    (CODEWORD)

e = 0000d208    (RAW RECIVED DATA+ERR)

RS decoder says: (8) error detected

{

    0 to 1 err  position: 3

    restored codeword is: d200

}

RECEIVED DATA IS: 69

Заключение

Вот мы и познакомились с азами кодирования информации и разобрались с основными идеями, лежащими в основе построения помехозащитных кодов. Следующая статья этого цикла подробно расскажет о превратностях полиномиальной арифметики и (о ужас!) полях Галуа. Затем, на фундаменте данного математического аппарата, мы сможем возвести дворец отказоустойчивых RAID-систем, да и не только их…

Общее представление

Коды Рида-Соломона – недвоичные совершенные систематические линейные блочные коды, относящиеся к классу циклических кодов с числовым полем, отличным от GF(2), и являющиеся подмножеством кодов Боуза-Чоудхури-Хоквингема. Корректирующие способности кодов Рида-Соломона напрямую зависят от количества контрольных байт. Добавление r контрольных байт позволяет обнаруживать r произвольным образом искаженных байт, гарантированно восстанавливая r/2 байт из них.

Что читать

Несмотря на то что данный цикл статей является вполне самодостаточным и весь минимально необходимый математический аппарат излагает самостоятельно без отсылок к сторонней литературе, желание углубить свои знания вполне естественно и его можно только приветствовать. А потому будет лучше, если вы не ограничитесь одной этой статьей, а перевернете целые горы специализированной литературы, с каждым разом все больше и больше ужасаясь глубине той пропасти, что отделяет ваши поверхностные представления от действительно настоящих знаний. Теория помехоустойчивого кодирования столь обширна, что для ее изучения потребуется как минимум целая жизнь.

  1. Blahut Richard. Theory and Practice of Error Control Codes. Mass.: Addison-Wesley, 1983. – Очень хорошая книжка из категории «must have»; по слухам, есть в электронном виде в сети, однако, к сожалению, самой книжки я так и не нашел, но тучи ссылок на нее убедительно свидетельствуют о высоком качестве последней. Также имеется ее русскоязычный перевод, выпущенный издательством «Мир» (см. ниже).
  2. Блейхут Р. Теория и практика кодов, контролирующих ошибки. М.: Мир, 1986. – 576с. – Технически грамотный и добротный перевод уже упомянутой выше книги Блейхута (ах, какие в издательстве Мир были переводчики!), электронной копии в сети, к сожалению, нет.
  3. James Plank. A tutorial on Reed-Solomon Coding for fault-tolerance in RAID-like systems. – Неплохое руководство по использованию кодов Рида-Соломона для построения отказоустойчивых RAID-подобных систем, ориентированное на математически неподготовленных системных программистов и доходчиво объясняющее суть помехоустойчивого кодирования с примерами исходных текстов на Си. Электронная копия доступна по адресу: http://www.cs.utk.edu/~plank/plank/papers/CS-96-332.pdf. Настоятельно рекомендую прочитать, даже если вы и не собираетесь заниматься сборкой RAID.
  4. Joel Sylvester. Reed Solomon Codes. – Предельно кратное описание принципов работы кодов Рида-Соломона с блок-схемами вместо исходных текстов. На практическое руководство не тянет, но общую картину все-таки дает, почитайте: http://www.elektrobit.co.uk/pdf/reedsolomon.pdf.
  5. Tom Moore. REED-SOLOMON PACKAGE. (old tutorial). – Роскошный сборник разнообразных руководств по кодам Рида-Соломона, наверное, лучший из всех, что я видел. Включает в себя кратное описание основ теории полей Галуа, базовые принципы построения кодеров/декодеров Рида-Соломона и законченные примеры реализации самих кодеров/декодеров на языке Си (правда, недостаточно добросовестно прокомментированные). Сей stuff неоднократно промелькивал в ФИДО и последний раз постился 28 декабря 1994 года в конференции comp.compression. Его легко найти в «Google» по ключевым словам «Reed-Solomon+main+ECC». Настоятельно рекомендую.
  6. Ross N.Williams. A painless guide to CRC error detection algorithms. – Подробное руководство по CRC, полезное достаточно внятным и доступным описанием полиномиальной арифметики, без которой работа с кодами Рида-Соломона просто немыслима. Доступно в электронной форме по следующему адресу: ftp://www.internode.net.au/clients/rocksoft/papers/crc_v3.txt. Также имеется его неплохой перевод на русский язык, легко отыскивающийся в сети по запросу «Элементарное руководство по CRC-алгоритмам обнаружения ошибок». Настоятельно рекомендую.
  7. ftape (драйвер ленточного накопителя из дистрибутива Linux). – Ну какая же запись на магнитную ленту обходится без корректирующих кодов? Представить себе такое, довольно затруднительно. Поэтому анализ исходных текстов драйверов ленточных накопителей дает довольно-таки богатую пищу для размышлений (при условии, конечно, что исследуемый драйвер действительно использует коды Рида-Соломона, а не что-нибудь другое). Драйвер ftape как раз и является тем драйвером, что вам нужен, а непосредственно сам код, ответственный за кодирование/декодирование кодов Рида-Соломона вынесен в файл ftape-ECC.c/ftape-ECC.h. Это достаточно аккуратный, хорошо структурированный и даже местами слегка комментируемый код, также рекомендую.
  8. James S. Plank GFLIB. C Procedures for Galois Field Arithmetic and Reed-Solomon Coding. – Библиотечка для работы с кодами Рида-Соломона. Содержит в себе полные исходные тексты всех необходимых функций и распространяется по лицензии GPL. Найти ее можно на любом GNU-сайте, например, здесь: http://www.cs.utk.edu/~plank/plank/gflib/gflib.tar.

Facebook

Twitter

Мой мир

Вконтакте

Одноклассники

Google+

В интернете существует большое количество вариантов расчёта контрольной суммы CRC. Но что же собственно такое контрольная сумма и почему она рассчитывается именно так? Давайте разберёмся. А заодно напишем программу, которая будет рассчитывать CRC с заданными параметрами.

1Теория, лежащая в основе расчёта CRC

Для начала давайте немного разберёмся в теории. Итак, что же такое CRC? Если кратко, это одна из разновидностей подсчёта контрольной суммы. Контрольная сумма – это метод проверки целостности принятой информации на стороне приёмника при передаче по каналам связи. Например, одна из простейших проверок – использование бита чётности. Это когда суммируются все биты передаваемого сообщения, и если сумма оказывается чётной, то в конец сообщения добавляется 0, если нечётной – то 1. При приёме также подсчитывается сумма битов сообщения, и сравнивается с принятым битом чётности. Если они отличаются, значит при передаче возникли ошибки, и передаваемая информация была искажена.

Но такой способ определения наличия ошибок – очень неинформативный и срабатывает не всегда, т.к. при искажении нескольких битов сообщения, чётность суммы может не измениться. Поэтому существует множество более «продвинутых» проверок, в том числе CRC.

По сути, CRC – это не сумма, а результат деления некого объёма информации (информационного сообщения) на константу, а точнее – остаток от деления сообщения на константу. Тем не менее, CRC исторически также называют «контрольная сумма». В значение CRC вносит вклад каждый бит сообщения. То есть, если хотя бы один бит исходного сообщения изменится при передаче, контрольная сумма тоже изменится, причём существенно. Это большой плюс такой проверки, так как он позволяет однозначно определить, исказилось исходное сообщение при передаче или нет.

Что такое исходное сообщение – понятно. Это непрерывная последовательность битов произвольной длины.

Что за константа, на которую мы должны делить исходное сообщение? Это некоторое число также любой длины, но обычно используются числа, кратные 1 байту – 8, 16 или 32 бита. Просто так легче считать, ведь компьютеры работают именно с байтами, а не с битами.

Константу-делитель обычно записывают в виде полинома (многочлена) вот таким образом: x8 + x2 + x1 + x0. Здесь степень числа «x» означает позицию бита-единицы в числе, начиная с нулевой, а старший разряд указывает на степень полинома и отбрасывается при интерпретации числа. То есть записанное ранее число – это не что иное как 100000111 в двоичной системе счисления.

Обычно при записи многочлена старший разряд подразумевается, но не пишется. То есть вышеуказанный многочлен можно было бы записать в двоичной системе как (1)00000111. В скобках я указал подразумеваемый старший разряд числа. Поэтому говорят, что многочлен равен 7 в десятичной системе счисления (111b = 7d).

Вот ещё пример: (x16 +) x15 + x2 + x0 = (1)1000000000000101 = 0x8005 = 32773.

Обычно используются некие стандартные многочлены для разных типов CRC. Вот некоторые из них:

Алгоритм CRC Образующий многочлен
CRC-16 0x8005
CRC-16-CCITT 0x1021
CRC-16-DNP 0x3D65
CRC-32-IEEE 802.3 0x04C11DB7
CRC-32C 0x1EDC6F41
CRC-32K 0x741B8CD7

В посвящённой расчёту CRC статье на Википедии есть большая таблица образующих полиномов.

Так как же считать контрольную сумму? Существует базовый метод – деление сообщения на полином «в лоб» – и его модификации в целях уменьшения количества вычислений и, соответственно, ускорения расчёта CRC. Для начала мы рассмотрим именно базовый метод.

В общем виде деление числа на многочлен выполняется по такому алгоритму. Алгоритм вычисления контрольной суммы CRC:

  1. Создаётся массив (регистр), заполненный нулями, равный по длине разрядности (степени) полинома.
  2. Исходное сообщение дополняется нулями в младших разрядах, в количестве, равном числу разрядов полинома.
  3. В младший разряд регистра заносится один старший бит сообщения, а из старшего разряда регистра выдвигается один бит.
  4. Если выдвинутый бит равен «1», то производится инверсия битов (операция XOR, исключающее ИЛИ) в тех разрядах регистра, которые соответствуют единицам в полиноме.
  5. Если в сообщении ещё есть биты, переходим к шагу 3).
  6. Когда все биты сообщения поступили в регистр и были обработаны этим алгоритмом, в регистре остаётся остаток от деления, который и является контрольной суммой CRC.

Назовём этот метод расчёта CRC метод побитового сдвига или простой метод.

Рисунок иллюстрирует деление исходной последовательности битов на число (1)00000111, или многочлен x8 + x2 + x1 + x0.

Схематичное представление вычисления CRC

Схематичное представление вычисления CRC на примере деления на многочлен x8 + x2 + x1 + x0

Кстати, проверить правильность расчёта CRC очень просто. В пункте (2) описанного алгоритма мы должны вместо дополнения исходного сообщения нулями дополнить его битами рассчитанной контрольной суммы, а остальное оставить как есть. Теперь остаток от деления дополненного сообщения на полином должен равняться нулю – это и есть признак верно рассчитанной контрольной суммы. Отличный от нуля остаток свидетельствует об ошибке.

Осталась ещё пара моментов, о которых стоит сказать. Как вы могли заметить, сообщение можно разделить на любое число. Как его выбрать? Существует ряд стандартных полиномов, которые используются при вычислении CRC. Например, для CRC32 это может быть число 0x04C11DB7, а для CRC16 это может быть 0x8005.

Кроме того, в регистр в начале вычислений можно записать не нули, а какое-то другое число. (И это рекомендуется делать: так повышается надёжность определения начала передачи сообщения, если, например, сообщение имеет в начале нулевые биты).

Также при расчётах непосредственно перед выдачей финальную контрольную сумму CRC можно поделить на какое-то другое число.

И последнее. Байты сообщения при записи в регистр могут помещаться как старшим битом «вперёд», так и наоборот, младшим. И результирующая CRC также может выдаваться, начиная со старшего бита или с младшего.

Изменение порядка битов в байте на обратный назовём «обращение», «реверс» или «отзеркаливание» байта.

Итого имеются 6 параметров, которые влияют на значение контрольной суммы:

  • порядок CRC;
  • образующий многочлен (его иногда называют «генераторный полином», переводя с английского буквально);
  • начальное содержимое регистра;
  • значение, с которым производится финальное XOR;
  • реверс байтов информационного сообщения;
  • реверс байтов CRC перед финальным XOR.

2Расчёт контрольной суммы CRC методом побитового сдвига

На основании всего вышеизложенного, давайте напишем функцию на языке Visual Basic .NET, которая будет рассчитывать контрольную сумму CRC, принимая ряд параметров, которые я описал выше, и возвращая значение CRC в виде 32-разрядного беззнакового числа.

Код расчёта CRC методом побитового сдвига на языке VB.NET

''' <summary>
''' Возвращает контрольную сумму типа CRC, рассчитанную методом побитового сдвига.
''' </summary>
''' <param name="bytes">Входная последовательность байтов (исходное сообщение).</param>
''' <param name="poly">Образующий многочлен разрядности <paramref name="width">width</paramref>.</param>
''' <param name="width">Порядок CRC в битах, 8/16/32.</param>
Public Shared Function GetCrc_Simple(ByVal bytes As Byte(), ByVal poly As UInteger, Optional ByVal width As Integer = 32, Optional ByVal initReg As UInteger = &HFFFFFFFFUI, Optional ByVal finalXor As UInteger = &HFFFFFFFFUI, Optional ByVal reverseBytes As Boolean = True, Optional ByVal reverseCrc As Boolean = True) As UInteger

  Dim widthInBytes As Integer = width  8
  
  'Дополняем сообщение width нулями (расчёт в байтах):
  ReDim Preserve bytes(bytes.Length - 1 + widthInBytes)
  
  'Создаём очередь битов из сообщения:
  Dim msgFifo As New Queue(Of Boolean)(bytes.Count * 8 - 1)
  For Each b As Byte In bytes
    Dim ba As New BitArray({b})
    If reverseBytes Then
      For i As Integer = 0 To 7
          msgFifo.Enqueue(ba(i))
      Next
    Else
      For i As Integer = 7 To 0 Step -1
          msgFifo.Enqueue(ba(i))
      Next
    End If
  Next
  
  'Создаём очередь из битов начального заполнения регистра:
  Dim initBytes As Byte() = BitConverter.GetBytes(initReg)
  Dim initBytesReversed As IEnumerable(Of Byte) = (From b As Byte In initBytes Take widthInBytes).Reverse
  Dim initFifo As New Queue(Of Boolean)(width - 1)
  For Each b As Byte In initBytesReversed
    Dim ba As New BitArray({b})
    If Not reverseBytes Then
       For i As Integer = 0 To 7
           initFifo.Enqueue(ba(i))
       Next
    Else
      For i As Integer = 7 To 0 Step -1
          initFifo.Enqueue(ba(i))
      Next
    End If
  Next
  
  'Сдвиг и XOR:
  Dim register As UInteger = 0 'заполняем width-разрядный регистр нулями.
  Do While msgFifo.Count > 0
    
    Dim poppedBit As Integer = CInt(register >> (width - 1)) And 1 'определить перед сдвигом регистра.
    
    Dim shiftedBit As Byte = Convert.ToByte(msgFifo.Dequeue)
    If initFifo.Count > 0 Then
      Dim b As Byte = Convert.ToByte(initFifo.Dequeue)
      shiftedBit = shiftedBit Xor b
    End If
    
    register = register << 1
    register = register Or shiftedBit
    
    If poppedBit = 1 Then
      register = register Xor poly
    End If
  Loop
  
  'Финальные преобразования:
  Dim crc As UInteger = register 'Регистр содержит остаток от деления - контрольную сумму.
  If reverseCrc Then
    crc = reflect(crc, width)
  End If
  crc = crc Xor finalXor
  crc = crc And (&HFFFFFFFFUI >> (32 - width)) 'маскируем младшие разряды.
  
  Return crc

End Function

''' <summary>
''' Обращает заданное число младших битов переданного числа.
''' </summary>
''' <param name="inpValue">Число, которое требуется «отзеркалить».</param>
''' <param name="bitsToReflect">Сколько младших битов обратить, 0..32.</param>
''' <returns></returns>
''' <remarks>Например: reflect(&H3E23, 3) == &H3E26.</remarks>
Private Shared Function reflect(ByVal inpValue As UInteger, Optional ByVal bitsToReflect As Integer = 32) As UInteger
    Dim t As UInteger = inpValue
    Dim reflected As UInteger = inpValue
    For i As Integer = 0 To bitsToReflect - 1
        Dim bm As UInteger = bitMask(bitsToReflect - 1 - i)
        If (t And 1) = 1 Then
            reflected = reflected Or bm
        Else
            reflected = reflected And Not bm
        End If
        t >>= 1
    Next
    Return reflected
End Function

''' <summary>
''' Возвращает наибольший разряд числа.
''' </summary>
''' <param name="number">Число, разрядность которого следует определить.</param>
''' <returns></returns>
Private Shared Function bitMask(ByVal number As Integer) As UInteger
    Dim res As UInteger = (1UI << number)
    Return res
End Function

Как вы могли заметить, в данной реализации расчёта CRC используется LINQ, так что соответствующая ссылка должна быть добавлена в проект.

Предлагаемая программа плохо масштабируема. То есть она работает хорошо при вычислении контрольной суммы CRC для коротких сообщений, длиной до нескольких десятков килобайтов. Я писал её с целью только продемонстрировать работу простого алгоритма, и не занимался оптимизацией. При расчёте CRC для длинного сообщения, размером десятки или сотни мегабайтов, программа будет сильно загружать процессор и память, т.к. всё сообщение целиком загружается в очередь. Этому способствует метод преобразования числа в битовую последовательность, используя Queue(Of Boolean). Для работы с такими большими сообщениями желательно реализовать промежуточный буфер, который будет передавать сообщение в программу небольшими порциями.

Зато у этой программы есть одно преимущество: она может быть использована для расчёта CRC любого порядка, не обязательно 8, 16 или 32. Это может быть CRC5 или CRC49. Только для чисел больше 32-х разрядов нужно изменить соответствующим образом входные параметры – допустим, poly передавать не как UInteger, а как ULong, или передавать его в виде битового массива (тогда теоретически порядок CRC вообще будет неограничен).

3Расчёт контрольной суммы CRC табличным методом

Для сокращения числа вычислений из предыдущего метода – метода побитового сдвига – придуманы некоторые оптимизации.

В частности, сдвигают не по одному биту за раз, а сразу по несколько. Наибольшую популярность снискали варианты, в которых сообщение сдвигается на число битов, кратное числу битов в байте: 8, 16 или 32, т.к. с байтами легче работать (не нужны дополнительные преобразования). При этом идея алгоритма осталась та же: сдвиг и исключающее ИЛИ с содержимым регистра.

Кроме того, оказывается, что часть расчётов можно провести заранее и записать в массив – таблицу, из которой по мере необходимости будет браться нужное число. Такой метод расчёта назвали табличный метод расчёта CRC.

Я не буду здесь вдаваться в теорию, она довольно сложна и много раз описана в других статьях. В частности, очень хорошее и подробное описание бинарной арифметики, лежащей в основе расчёта CRC, и описание табличного метода, даётся в статье Ross N. Williams: «A Painless Guide to CRC Error Detection Algorithms». Рекомендую к прочтению обязательно! Оригинальный текст – в приложении к статье, а русский перевод легко найти в интернете.

Ну что же, пришло время для самой программы. Она будет несколько длиннее предыдущей. По сути, это реализация алгоритма из указанной статьи в стиле объектно-ориентированного программирования. Опять же будем писать программу на моём любимом языке программирования VB.NET. Я назвал этот класс RocksoftCrcModel, по названию компании, в которой работал автор указанной статьи.

Код расчёта CRC табличным методом на языке VB.NET

    ''' <summary>
    ''' Реализует алгоритм расчёта CRC методом Rocksoft^tm Model CRC.
    ''' </summary>
    Public Class RocksoftCrcModel

#Region "PROPS AND FIELDS"

        ''' <summary>
        ''' Таблица предвычисленных значений для расчёта контрольной суммы.
        ''' </summary>
        Public ReadOnly CrcLookupTable(255) As UInteger

        ''' <summary>
        ''' Порядок CRC, в битах (строго 8, 16 или 32).
        ''' Изменение этого свойства ведёт к пересчёту таблицы.
        ''' </summary>
        Public Property CrcWidth As Integer
            Get
                Return _CrcWidth
            End Get
            Set(value As Integer)
                If _CrcWidth <> value Then
                    _CrcWidth = value
                    _TopBit = getBitMask(_CrcWidth - 1)
                    _WidMask = (((1UI << (_CrcWidth - 1)) - 1UI) << 1) Or 1UI
                    generateLookupTable()
                End If
            End Set
        End Property
        Private _CrcWidth As Integer = 32

        ''' <summary>
        ''' Образующий многочлен.
        ''' Изменение этого свойства ведёт к пересчёту таблицы.
        ''' </summary>
        Public Property Polynom As UInteger
            Get
                Return _Polynom
            End Get
            Set(value As UInteger)
                If _Polynom <> value Then
                    _Polynom = value
                    generateLookupTable()
                End If
            End Set
        End Property
        Private _Polynom As UInteger = &H4C11DB7

        ''' <summary>
        ''' Обращать ли байты сообщения?
        ''' Изменение этого свойства ведёт к пересчёту таблицы.
        ''' </summary>
        Public Property ReflectIn As Boolean
            Get
                Return _ReflectIn
            End Get
            Set(value As Boolean)
                If _ReflectIn <> value Then
                    _ReflectIn = value
                    generateLookupTable()
                End If
            End Set
        End Property
        Private _ReflectIn As Boolean = True

        ''' <summary>
        ''' Начальное содержимое регистра.
        ''' </summary>
        Public Property InitRegister As UInteger
            Get
                Return _InitRegister
            End Get
            Set(value As UInteger)
                If _InitRegister <> value Then
                    _InitRegister = value
                End If
            End Set
        End Property
        Private _InitRegister As UInteger = &HFFFFFFFFUI

        ''' <summary>
        ''' Обращать выходное значение CRC?
        ''' </summary>
        Public Property ReflectOut As Boolean
            Get
                Return _ReflectOut
            End Get
            Set(value As Boolean)
                If _ReflectOut <> value Then
                    _ReflectOut = value
                End If
            End Set
        End Property
        Private _ReflectOut As Boolean = True

        ''' <summary>
        ''' Значение, с которым XOR-ится выходное значение CRC.
        ''' </summary>
        Public Property XorOut As UInteger
            Get
                Return _XorOut
            End Get
            Set(value As UInteger)
                If _XorOut <> value Then
                    _XorOut = value
                End If
            End Set
        End Property
        Private _XorOut As UInteger = &HFFFFFFFFUI

#End Region '/PROPS AND FIELDS

#Region "READ-ONLY PROPS"

        ''' <summary>
        ''' Возвращает старший разряд полинома.
        ''' </summary>
        ReadOnly Property TopBit As UInteger
            Get
                Return _TopBit
            End Get
        End Property
        Private _TopBit As UInteger = getBitMask(CrcWidth - 1)

        ''' <summary>
        ''' Возвращает длинное слово со значением (2^width)-1.
        ''' </summary>
        Private ReadOnly Property WidMask As UInteger
            Get
                Return _WidMask
            End Get
        End Property
        Private _WidMask As UInteger = (((1UI << (CrcWidth - 1)) - 1UI) << 1) Or 1UI

#End Region '/READ-ONLY PROPS

#Region "CTOR"

        ''' <summary>
        ''' Конструктор, инициализированный параметрами по умолчанию для алгоритма CRC32.
        ''' </summary>
        Public Sub New()
            generateLookupTable()
        End Sub

        ''' <summary>
        ''' Инициализирует новый экземпляр параметрической модели CRC с настраиваемыми параметрами.
        ''' </summary>
        ''' <param name="width">Разрядность контрольной суммы в битах.</param>
        ''' <param name="poly">Полином.</param>
        ''' <param name="initReg">начальное содержимое регистра.</param>
        ''' <param name="isReflectIn">Обращать ли входящие байты сообщения?</param>
        ''' <param name="isReflectOut">Обратить ли CRC перед финальным XOR.</param>
        ''' <param name="xorOut">Конечное значение XOR.</param>
        Public Sub New(ByVal width As Integer, ByVal poly As UInteger, Optional ByVal initReg As UInteger = &HFFFFFFFFUI, Optional ByVal isReflectIn As Boolean = True, Optional ByVal isReflectOut As Boolean = True, Optional ByVal xorOut As UInteger = &HFFFFFFFFUI)
            Me.CrcWidth = width
            Me.Polynom = poly
            Me.InitRegister = initReg
            Me.ReflectIn = isReflectIn
            Me.ReflectOut = isReflectOut
            Me.XorOut = xorOut
            generateLookupTable()
        End Sub

#End Region '/CTOR

#Region "ВЫЧИСЛЕНИЕ CRC"

        ''' <summary>
        ''' Вычисляет значение контрольной суммы переданного сообщения.
        ''' </summary>
        ''' <param name="message">Исходное сообщение, для которого нужно посчитать контрольную сумму.</param>
       Public Function ComputeCrc(ByRef message As Byte()) As UInteger
            Dim registerContent As UInteger = InitRegister 'Содержимое регистра в процессе пересчёта CRC.
            For Each b As Byte In message
                registerContent = getNextRegisterContent(registerContent, b)
            Next
            Dim finalCrc As UInteger = getFinalCrc(registerContent)
            Return finalCrc
        End Function

        ''' <summary>
        ''' Вычисляет значение контрольной суммы переданного сообщения и возвращает его в виде массива байтов.
        ''' </summary>
        ''' <param name="message">Исходное сообщение, для которого нужно посчитать контрольную сумму.</param>
        Public Function ComputeCrcAsBytes(ByRef message As Byte()) As Byte()
            Dim crc As UInteger = ComputeCrc(message)
            Dim crcBytes As Byte() = BitConverter.GetBytes(crc)
            Dim crcBytesOrdered(crcBytes.Length - 1) As Byte
            For i As Integer = 0 To crcBytes.Length - 1
                crcBytesOrdered(i) = crcBytes(crcBytes.Length - 1 - i)
            Next
            Return crcBytesOrdered
        End Function

        ''' <summary>
        ''' Обрабатывает один байт сообщения (0..255).
        ''' </summary>
        ''' <param name="prevRegContent">Содержимое регистра на предыдущем шаге.</param>
        ''' <param name="value">Значение очередного байта из сообщения.</param>
        Private Function getNextRegisterContent(ByVal prevRegContent As UInteger, ByVal value As Byte) As UInteger
            Dim uValue As UInteger = value
            If ReflectIn Then
                uValue = reflect(uValue, 8)
            End If
            Dim reg As UInteger = prevRegContent
            reg = reg Xor (uValue << (CrcWidth - 8))
            For i As Integer = 0 To 7
                If (reg And TopBit) = TopBit Then
                    reg = (reg << 1) Xor Polynom
                Else
                    reg <<= 1
                End If
                reg = reg And WidMask()
            Next
            Return reg
        End Function

        ''' <summary>
        ''' Возвращает значение CRC для обработанного сообщения.
        ''' </summary>
        ''' <param name="regContent">Значение регистра до финального обращения и XORа.</param>
        Private Function getFinalCrc(ByVal regContent As UInteger) As UInteger
            If ReflectOut Then
                Dim res As UInteger = XorOut Xor reflect(regContent, CrcWidth)
                Return res
            Else
                Dim res As UInteger = XorOut Xor regContent
                Return res
            End If
        End Function

#End Region '/ВЫЧИСЛЕНИЕ CRC

#Region "РАСЧЁТ ТАБЛИЦЫ"

        ''' <summary>
        ''' Вычисляет таблицу предвычисленных значений для расчёта контрольной суммы.
        ''' </summary>
        Private Sub generateLookupTable()
            For i As Integer = 0 To 255
                CrcLookupTable(i) = generateTableItem(i)
            Next
        End Sub

        ''' <summary>
        ''' Рассчитывает один байт таблицы значений для расчёта контрольной суммы
        ''' по алгоритму Rocksoft^tm Model CRC Algorithm.
        ''' </summary>
        ''' <param name="index">Индекс записи в таблице, 0..255.</param>
        Private Function generateTableItem(ByVal index As Integer) As UInteger

            Dim inbyte As UInteger = CUInt(index)

            If ReflectIn Then
                inbyte = reflect(inbyte, 8)
            End If

            Dim reg As UInteger = inbyte << (CrcWidth - 8)

            For i As Integer = 0 To 7
                If (reg And TopBit) = TopBit Then
                    reg = (reg << 1) Xor Polynom
                Else
                    reg <<= 1
                End If
            Next

            If ReflectIn Then
                reg = reflect(reg, CrcWidth)
            End If

            Dim res As UInteger = reg And WidMask
            Return res

        End Function

#End Region '/РАСЧЁТ ТАБЛИЦЫ

#Region "ВСПОМОГАТЕЛЬНЫЕ"

        ''' <summary>
        ''' Возвращает наибольший разряд числа.
        ''' </summary>
        ''' <param name="number">Число, разрядность которого следует определить, степень двойки.</param>
        Private Function getBitMask(ByVal number As Integer) As UInteger
            Dim res As UInteger = 1UI << number
            Return res
        End Function

        ''' <summary>
        ''' Обращает заданное число младших битов переданного числа.
        ''' </summary>
        ''' <param name="value">Число, которое требуется обратить («отзеркалить»).</param>
        ''' <param name="bitsToReflect">Сколько младших битов числа обратить, 0..32.</param>
        ''' <remarks>Например: reflect(0x3E23, 3) == 0x3E26.</remarks>
        Private Function reflect(ByVal value As UInteger, Optional ByVal bitsToReflect As Integer = 32) As UInteger
            Dim t As UInteger = value
            Dim reflected As UInteger = value
            For i As Integer = 0 To bitsToReflect - 1
                Dim bm As UInteger = getBitMask(bitsToReflect - 1 - i)
                If (t And 1) = 1 Then
                    reflected = reflected Or bm
                Else
                    reflected = reflected And Not bm
                End If
                t >>= 1
            Next
            Return reflected
        End Function

#End Region '/ВСПОМОГАТЕЛЬНЫЕ

    End Class 

Этот код полностью готов к использованию, можно брать и применять. Пользоваться данной программой так:

  • создать экземпляр класса RocksoftCrcModel(), передав в конструктор параметры модели CRC;
  • для расчёта контрольной суммы, вызвать метод данного объекта ComputeCrc() или ComputeCrcAsBytes(), передав в качестве параметра информационное сообщение, для которого необходимо посчитать контрольную сумму;
  • если меняются параметры модели CRC, таблица автоматически пересчитывается, и новый экземпляр класса можно не создавать.

Приведу пример использования данного класса для алгоритма CRC16. В качестве сообщения message будем использовать массив байтов, который представляет собой строку «123456789» в коде ASCII, которая используется во многих онлайн-калькуляторах CRC:

Dim crcModel As New RocksoftCrcModel(16, &H8005, 0, True, True, 0)
Dim message as Byte() = {&H31, &H32, &H33, &H34, &H35, &H36, &H37, &H38, &H39}
Dim crc As UInteger = crcModel.ComputeCrc(message)

Данная реализация расчёта CRC была проверена мною путём сличения со многими онлайн-калькуляторами CRC (назовём это «слабой» проверкой, именно такое определение дано в вышеуказанной статье, когда проверка осуществляется на основании сравнения рассчитанной контрольной суммы с эталонной, при одинаковых исходных параметрах и сообщении).

Для любителей C# перепишем данный класс таким образом:

Код расчёта CRC табличным методом на языке C# (разворачивается)

using System;

namespace CRC
{
  /// <summary>Реализует алгоритм расчёта CRC методом Rocksoft^tm Model CRC.</summary>
  public class RocksoftCrcModel
  {
    /// <summary>Таблица предвычисленных значений для расчёта контрольной суммы.</summary>
    public readonly uint[] CrcLookupTable;
    private int _CrcWidth;
    private uint _Polynom;
    private bool _ReflectIn;
    private uint _InitRegister;
    private bool _ReflectOut;
    private uint _XorOut;
    private uint _TopBit;
    private uint _WidMask;

    /// <summary>
    /// Порядок CRC, в битах (8/16/32).
    /// Изменение этого свойства ведёт к пересчёту таблицы.
    /// </summary>
    public int CrcWidth
    {
      get
      {
        return this._CrcWidth;
      }
      set
      {
        if (this._CrcWidth == value)
          return;
        this._CrcWidth = value;
        this._TopBit = this.getBitMask(checked (this._CrcWidth - 1));
        this._WidMask = (uint) ((int) checked (unchecked ((uint) (1 << checked (this._CrcWidth - 1))) - 1U) << 1 | 1);
        this.generateLookupTable();
      }
    }

    /// <summary>
    /// Образующий многочлен.
    /// Изменение этого свойства ведёт к пересчёту таблицы.
    /// </summary>
    public uint Polynom
    {
      get
      {
        return this._Polynom;
      }
      set
      {
        if ((int) this._Polynom == (int) value)
          return;
        this._Polynom = value;
        this.generateLookupTable();
      }
    }

    /// <summary>
    /// Обращать байты сообщения?
    /// Изменение этого свойства ведёт к пересчёту таблицы.
    /// </summary>
    public bool ReflectIn
    {
      get
      {
        return this._ReflectIn;
      }
      set
      {
        if (this._ReflectIn == value)
          return;
        this._ReflectIn = value;
        this.generateLookupTable();
      }
    }

    /// <summary>Начальное содержимое регистра.</summary>
    public uint InitRegister
    {
      get
      {
        return this._InitRegister;
      }
      set
      {
        if ((int) this._InitRegister == (int) value)
          return;
        this._InitRegister = value;
      }
    }

    /// <summary>Обращать выходное значение CRC?</summary>
    public bool ReflectOut
    {
      get
      {
        return this._ReflectOut;
      }
      set
      {
        if (this._ReflectOut == value)
          return;
        this._ReflectOut = value;
      }
    }

    /// <summary>Значение, с которым XOR-ится выходное значение CRC.</summary>
    public uint XorOut
    {
      get
      {
        return this._XorOut;
      }
      set
      {
        if ((int) this._XorOut == (int) value)
          return;
        this._XorOut = value;
      }
    }

    /// <summary>Возвращает старший разряд полинома.</summary>
    public uint TopBit
    {
      get
      {
        return this._TopBit;
      }
    }

    /// <summary>Возвращает длинное слово со значением (2^width)-1.</summary>
    /// <returns></returns>
    /// <remarks></remarks>
    private uint WidMask
    {
      get
      {
        return this._WidMask;
      }
    }

    /// <summary>
    /// Конструктор, инициализированный параметрами по умолчанию для алгоритма CRC32.
    /// </summary>
    public RocksoftCrcModel()
    {
      base..ctor();
      this.CrcLookupTable = new uint[256];
      this._CrcWidth = 32;
      this._Polynom = 79764919U;
      this._ReflectIn = true;
      this._InitRegister = uint.MaxValue;
      this._ReflectOut = true;
      this._XorOut = uint.MaxValue;
      this._TopBit = this.getBitMask(checked (this.CrcWidth - 1));
      this._WidMask = (uint) ((int) checked (unchecked ((uint) (1 << checked (this.CrcWidth - 1))) - 1U) << 1 | 1);
      this.generateLookupTable();
    }

    /// <summary>
    /// Инициализирует новый экземпляр параметрической модели CRC с настраиваемыми параметрами.
    /// </summary>
    /// <param name="width">Разрядность контрольной суммы в битах.</param>
    /// <param name="poly">Полином.</param>
    /// <param name="initReg">начальное содержимое регистра.</param>
    /// <param name="isReflectIn">Обращать ли входящие байты сообщения?</param>
    /// <param name="isReflectOut">Обратить ли CRC перед финальным XOR.</param>
    /// <param name="xorOut">Конечное значение XOR.</param>
    public RocksoftCrcModel(int width, uint poly, uint initReg = 4294967295, bool isReflectIn = true, bool isReflectOut = true, uint xorOut = 4294967295)
    {
      base..ctor();
      this.CrcLookupTable = new uint[256];
      this._CrcWidth = 32;
      this._Polynom = 79764919U;
      this._ReflectIn = true;
      this._InitRegister = uint.MaxValue;
      this._ReflectOut = true;
      this._XorOut = uint.MaxValue;
      this._TopBit = this.getBitMask(checked (this.CrcWidth - 1));
      this._WidMask = (uint) ((int) checked (unchecked ((uint) (1 << checked (this.CrcWidth - 1))) - 1U) << 1 | 1);
      this.CrcWidth = width;
      this.Polynom = poly;
      this.InitRegister = initReg;
      this.ReflectIn = isReflectIn;
      this.ReflectOut = isReflectOut;
      this.XorOut = xorOut;
      this.generateLookupTable();
    }

    /// <summary>Вычисляет значение контрольной суммы переданного сообщения.</summary>
    /// <param name="message">Исходное сообщение, для которого нужно посчитать контрольную сумму.</param>
    /// <returns></returns>
    public uint ComputeCrc(ref byte[] message)
    {
      uint num1 = this.InitRegister;
      byte[] numArray = message;
      int index = 0;
      while (index < numArray.Length)
      {
        byte num2 = numArray[index];
        num1 = this.getNextRegisterContent(num1, num2);
        checked { ++index; }
      }
      return this.getFinalCrc(num1);
    }

    /// <summary>
    /// Вычисляет значение контрольной суммы переданного сообщения и возвращает его в виде массива байтов.
    /// </summary>
    /// <param name="message">Исходное сообщение, для которого нужно посчитать контрольную сумму.</param>
    /// <returns></returns>
    public byte[] ComputeCrcAsBytes(byte[] message)
    {
      byte[] bytes = BitConverter.GetBytes(this.ComputeCrc(ref message));
      byte[] numArray = new byte[checked (bytes.Length - 1 + 1)];
      int num1 = 0;
      int num2 = checked (bytes.Length - 1);
      int index = num1;
      while (index <= num2)
      {
        numArray[index] = bytes[checked (bytes.Length - 1 - index)];
        checked { ++index; }
      }
      return numArray;
    }

    /// <summary>Обрабатывает один байт сообщения (0..255).</summary>
    /// <param name="prevRegContent">Содержимое регистра на предыдущем шаге.</param>
    /// <param name="value">Значение очередного байта из сообщения.</param>
    private uint getNextRegisterContent(uint prevRegContent, byte value)
    {
      uint num1 = (uint) value;
      if (this.ReflectIn)
        num1 = this.reflect(num1, 8);
      uint num2 = prevRegContent ^ num1 << checked (this.CrcWidth - 8);
      int num3 = 0;
      do
      {
        num2 = (((int) num2 & (int) this.TopBit) != (int) this.TopBit ? num2 << 1 : num2 << 1 ^ this.Polynom) & this.WidMask;
        checked { ++num3; }
      }
      while (num3 <= 7);
      return num2;
    }

    /// <summary>Возвращает значение CRC для обработанного сообщения.</summary>
    /// <param name="regContent">Значение регистра до финального обращения и XORа.</param>
    /// <returns></returns>
    private uint getFinalCrc(uint regContent)
    {
      if (this.ReflectOut)
        return this.XorOut ^ this.reflect(regContent, this.CrcWidth);
      return this.XorOut ^ regContent;
    }

    /// <summary>Вычисляет таблицу предвычисленных значений для расчёта контрольной суммы.</summary>
    private void generateLookupTable()
    {
      int index = 0;
      do
      {
        this.CrcLookupTable[index] = this.generateTableItem(index);
        checked { ++index; }
      }
      while (index <= (int) byte.MaxValue);
    }

    /// <summary>
    /// Рассчитывает один байт таблицы значений для расчёта контрольной суммы
    /// по алгоритму Rocksoft^tm Model CRC Algorithm.
    /// </summary>
    /// <param name="index">Индекс записи в таблице, 0..255.</param>
    private uint generateTableItem(int index)
    {
      uint num1 = checked ((uint) index);
      if (this.ReflectIn)
        num1 = this.reflect(num1, 8);
      uint num2 = num1 << checked (this.CrcWidth - 8);
      int num3 = 0;
      do
      {
        if (((int) num2 & (int) this.TopBit) == (int) this.TopBit)
          num2 = num2 << 1 ^ this.Polynom;
        else
          num2 <<= 1;
        checked { ++num3; }
      }
      while (num3 <= 7);
      if (this.ReflectIn)
        num2 = this.reflect(num2, this.CrcWidth);
      return num2 & this.WidMask;
    }

    /// <summary>Возвращает наибольший разряд числа.</summary>
    /// <param name="number">Число, разрядность которого следует определить, степень двойки.</param>
    /// <returns></returns>
    private uint getBitMask(int number)
    {
      return (uint) (1 << number);
    }

    /// <summary>Обращает заданное число младших битов переданного числа.</summary>
    /// <param name="value">Число, которое требуется обратить ("отзеркалить").</param>
    /// <param name="bitsToReflect">Сколько младших битов числа обратить, 0..32.</param>
    /// <returns></returns>
    /// <remarks>Например: reflect(0x3E23, 3) == 0x3E26.</remarks>
    private uint reflect(uint value, int bitsToReflect = 32)
    {
      uint num1 = value;
      uint num2 = value;
      int num3 = 0;
      int num4 = checked (bitsToReflect - 1);
      int num5 = num3;
      while (num5 <= num4)
      {
        uint bitMask = this.getBitMask(checked (bitsToReflect - 1 - num5));
        if (((long) num1 & 1L) == 1L)
          num2 |= bitMask;
        else
          num2 &= ~bitMask;
        num1 >>= 1;
        checked { ++num5; }
      }
      return num2;
    }
  }
}

Данная программа на C# не тестировалась мной, в отличие от предыдущей, написанной на VB.NET. Этот код получен через декомпиляцию предыдущего. Если в нём обнаружатся какие-то ошибки, то пишите в комментариях или мне на почту, исправлю.

Прикладываю к статье полностью рабочий и готовый к использованию файл RocksoftCrcModel.vb с реализацией расчёта контрольной суммы CRC, который мы тут рассмотрели, а также RocksoftCrcModel.cs на C#.

Полную и самую последнюю версию кода можно скачать с репозитория на GitHub.

4«Взлом» контрольной суммы CRC32 и CRC16

Кратко затронем вопрос «взлома» CRC32. И прежде всего давайте определимся с понятием «взлом» применительно к данному вопросу.

Если задача определения контрольной суммы некоторого массива данных – прямая задача, то «взлом» – это обратная задача, а именно: подгонка контрольной суммы под определённый массив данных.

Допустим, вы имеете файл и рассчитали его контрольную сумму. Вам нужно изменить в нём произвольное число байтов, сохранив при этом контрольную сумму. Сделать это совсем не сложно.

Для начала нужно посчитать обычным образом контрольную сумму CRC32, CRC16 или любую другую, какая вам нужна, для этого изменённого файла. Пусть это будет C1. Теперь нужно добавить такое же число нулевых байтов в конец файла, которое содержится в контрольной сумме (для CRC32 – 4 байта, для CRC16 – 2 байта, и т.д.). Можно простым перебором подобрать такое число C2, которое мы и запишем в эти нулевые байты. Ведь понятно, что полный диапазон всех допустимых значений CRC32 укладывается в 232 ~ 4,295 млрд. То есть за 4 с небольшим миллиарда итераций расчёта контрольной суммы с начальным содержимым регистра, равным С1, мы брутфорсом («в лоб», методом грубой силы) подберём нужное значение. При современных вычислительных мощностях это не составит проблемы. А уж «взломать» с помощью перебора CRC16 вообще дело нескольких секунд.

Можно ли разместить нулевые байты в середине или начале файла? Можно. К операции XOR применим сочетательный закон: a XOR (b XOR c) = (a XOR b) XOR c, поэтому можно с успехом разбить файл на 3 части: до вставки, после вставки, и сама вставка. Посчитать CRC для первых двух частей (C1 и C2 на иллюстрации), объединить их операцией XOR, заполнить этим числом начальное содержимое регистра, а затем «сбрутфорсить» CRC оставшейся третьей части X.

+-----------------+-----+---------+
|        c1       |  x  |   c2    |
+-----------------+-----+---------+

Есть более интеллектуальный и изящный способ подогнать CRC под нужное значение. Суть его в том, что вместо последовательного перебора всех подряд значений мы «прокручиваем назад» несколько раз (по числу байтов или битов контрольной суммы) наш табличный алгоритм или алгоритм побитового сдвига до тех пор, пока CRC не будет желаемой. На эту тему есть подробные и качественные материалы в сети.

Таким образом, напрашивается вывод: контрольная сумма типа CRC хорошо подходит для проверки целостности данных при случайных искажениях информации в канале передачи данных, но совершенно не подходит для защиты от намеренного взлома.

5Программа для расчёта контрольной суммы по алгоритмам CRC32, CRC16 и CRC8

На основе приведённого алгоритма была написана программа – калькулятор для расчёта контрольных сумм по алгоритмам CRC32, CRC16 и CRC8. Внешний вид окна приведён на рисунке. Программа работает под ОС Windows и требует .NET версии 3.5.

Программа для расчёта контрольной суммы по алгоритмам CRC32, CRC16 и CRC8

Интерфейс программы для расчёта контрольной суммы по алгоритмам CRC32, CRC16 и CRC8

Программа позволяет рассчитывать CRC массива байтов (введённого в поле «Сообщение») или указанного файла. Все рассмотренные выше параметры контрольной суммы настраиваются через интерфейс программы.

Ну и напоследок выкладываю ссылки на архив, в архиве лежат: программа «Калькулятор CRC», оригинальная статья «A Painless Guide to CRC Error Detection Algorithms», класс RocksoftCrcModel() на Visual Basic.NET и на C#.

Содержимое архива «CRC calculator»

Итак, подведём итоги. В этой статье мы:
– узнали, что такое контрольная сумма CRC и какие бывают её виды;
– научились считать CRC методом побитового сдвига и табличным методом;
– узнали алгоритмы «взлома» CRC и сделали вывод об области применимости контрольной суммы типа CRC.

Скачать программу «Калькулятор контрольной суммы CRC»

  • Скачать CRC calculator с Depositfiles.com

Понравилась статья? Поделить с друзьями:
  • Rosh online ошибка
  • Rosetta error failed to allocate vm space for aot
  • Rolling back action ошибка cisco как исправить
  • Rootmy tv ошибка
  • Role is above bot yagpdb как исправить