Re[6]: Протокол на основе UDP
От: fk0 Россия https://fk0.name
Дата: 28.02.10 19:10
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, fk0, Вы писали:


fk0>> Пример ниже. Прекрасно видно, что на случайных данных, на совершенно разных последовательностях (случайных), даже на коротких (несколко байт) пакетах можно нарваться на ситуацию, когда CRC совпадает, а хвост пакета (а то и весь пакет!) испорчен нулём или FF. Позапускай программу несколько раз (./a.out | less). Что характерно -- CRC должен быть равен заполняющему (всё портящему) байту. Так вот в реальном приборе, где проблемы были, там, догадайся...


N>Во-первых, с самого начала: твой пример попросту с багом. Если его исправить как следует:

N>- ncrc=crc_byte(copy[n], crc);
N>+ ncrc=crc_byte(copy[n], ncrc);

N>то у меня получается в самом коротком случае 3 байта испорченных, иногда 4, обычно же оно не находит случаев короче чем 8 байт. Для CRC-8 это очень хороший результат. А твой пример умудряется находить даже последовательности с изменением одного последнего байта, что заведомо показывает на ошибочность твоего кода (собственно почему я и стал рыть).


Исправил. Так ведь получается. Вот пример для 1000 запусков:

$ (for x in `seq 1000`; do ./a.out | grep -m1 length; done) | sort -u
length=  4, tail=  2, fill=00, crc=88
length=  4, tail=  4, fill=FF, crc=DE
length=  5, tail=  2, fill=FF, crc=F0
length=  6, tail=  2, fill=FF, crc=82
length=  6, tail=  6, fill=00, crc=00
length=  7, tail=  3, fill=00, crc=52
length=  7, tail=  4, fill=00, crc=51
length=  7, tail=  5, fill=FF, crc=68
length=  7, tail=  6, fill=FF, crc=FA
length=  8, tail=  5, fill=FF, crc=61
length=  8, tail=  6, fill=FF, crc=4A
length=  8, tail=  7, fill=00, crc=5E
length=  9, tail=  7, fill=00, crc=5B
...


Как и ожидалось, ошибки не может быть только для однобайтовых пакетов. К слову, функция crc_byte здесь применена медленная (побитная) на всякий случай.


N>Далее по частям программы:


fk0>>/* compute CRC-8 for SMBUS (x^8 + x^2 + x + 1) */

fk0>>uint_fast8_t crc_byte(uint_fast8_t byte, uint_fast8_t crc)
fk0>>{
fk0>>uint_fast8_t b;
fk0>> b=byte^crc;
fk0>> if (b&(1<<0)) crc^=0x7;
fk0>> if (b&(1<<1)) crc^=0xe;
fk0>> if (b&(1<<2)) crc^=0x1c;
fk0>> if (b&(1<<3)) crc^=0x38;
fk0>> if (b&(1<<4)) crc^=0x70;
fk0>> if (b&(1<<5)) crc^=0xe0;
fk0>> if (b&(1<<6)) crc^=0xc7;
fk0>> if (b&(1<<7)) crc^=0x89;
fk0>> return crc;
fk0>>}
N>Не знаю, что ты здесь хотел нарисовать, но это никак не CRC в обычном понимании.

Это CRC с указанным полиномом и его результат для любых входных значений полностью совпадает с побитным алгоритмом CRC. Указанный полином на сайте smbus.org... Указанные коэффициенты для ксорки берутся из результатов вычисления таблицы CRC побитным алгоритмом и взятия каждого 2^N-го члена для N=0..7. Это оптимизированный алгоритм просто. И, кстати, на мелких контроллерах он может работать быстрее табличного (на гарвардских архитектурах доступ к данным в программной памяти -- медленный).

Тут закралась действительно ошибка и CRC считается неправильно:

 uint_fast8_t crc_byte(uint_fast8_t byte, uint_fast8_t crc)
 {
 uint_fast8_t b;
         b=byte^crc;
+        crc=0;
         if (b&(1<<0)) crc^=0x7;


Это патч какбы.

N>В обычном было бы примерно так:


Вот он, гарантированно работающий, но медленный (к слову, в приборе был он, не оптимизированный):

uint_fast8_t smbus_crc_byte(uint_fast8_t byte, uint_fast8_t crc)
{
uint_fast8_t y=8;
        do {
                if ((byte^crc)&0x80) crc<<=1, crc^=0x7;
                        else crc<<=1;
                byte<<=1;
        } while (--y);
        return crc;
}


Результаты вычислений совпадают (после исправления crc=0).

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


См. выше, моя ошибка. Алгоритм переделывался с другого, для Maxim (бывший Dallas) 1-Wire, там биты в другую сторону сдвигаются, оттого и...

N>Откуда код? Ты это взял из спецификации SMBus? Кстати, там точно передача начинается со старшего бита?


Там точно (в I2C вообще) передача 7-м битом вперёд. Вот в 1-Wire -- наоборот, начиная с младшего.
IMHO начиная со старшего правильнее. Их удобнее на осциллографе разглядывать -- сразу можно в голове байт сложить...

fk0>> Вообще-то CRC считается и бестабличными методами достаточно быстро, если такая задача стоит. Опять же смотри пример. Все возражения "почему не 16-32 бита" -- потому что блоки не по 4 килобайта, а по десятку байт. А 16 бит принципиально ситуацию тут не изменит вообще.

N>Думаю, таки изменит. Не зря на Ethernet при типичном размере пакета в пару сотен байт уже применялся CRC-32.

N>Но твоя ситуация вообще-то показывает контекст не для CRC. CRC предназначен для случаев именно одиночных независимых искажений потока (изменения битов, выпадения/добавления целых байт).


Увы и ах. Именно так, именно для того он и предназначен. И в RS-232 будет замечательно работать. А где пакетная передача с обрывами посереди пакета и заполнением конца пакета одинаковым битом -- не канает CRC.

N> Причём на SMBus, аналогично RS-232, невозможно держать AFAIK линию данных всё время на одном уровне — потому что тогда не будут видны границы байтов. Ситуация, когда хвост пакета целиком становится битами одного значения, означает сбой уже в кодере или декодере протокола, а не помеху/сбой на линии. Такие вещи должны отлавливаться на другом уровне и обвинять CRC в их неотлове — нелепость.


Ничего подобного. Ситуация там такая: приём данных мастером, слейв отвалился по электрическим причинам (стоп-сигнал увидел и т.п.) На шине соответственно единица (обеспечивается резистором подтяжки) всё время. А мастер так уверенно читает все биты до конца пакета и *никак* не может знать, что слейв уже обмен закончил. Между байтами разделения в I2C нет, как и границ байтов в данном случае. В случае когда мастер записывал бы -- да, разделение есть, слейв должен на каждый байт ACKnowledge нулевым битом давать. А тут мастер должен ACK давать -- ну вот он и даёт, и читает дальше... Это в значительной степени проблема шины и протокола SMBUS (обязательный нулевой бит в конце пакета кардинально решал бы проблему).

N>Если ты хочешь проверить CRC на корректность детекта в условиях, в которых он предназначен работать — меняй произвольно 1 из ~100 байтов не обязательно в хвосте.


Если я поменяю 1 байт -- CRC-8 это всегда обнаружит. Надо 2 менять.

Кстати ещё пример неправильного применения CRC -- контрольный код записи в FLASH-памяти. Догадываешься уже почему? Ровно та же проблема. Массовое FF-чивание отдельных блоков. Контрольная сумма подходящей разрядности (весьма небольшой) опять же справляется гарантировано и это очевидно математически. Хотя и не справляется так хорошо с единичными сбоями, как CRC.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.