четверг, 19 мая 2016 г.

Как документировать функцию

Документировать функции не обязательно. Но это хорошая идея в следующих сценариях:
  • Вы публикуете свою библиотеку, чтобы ей пользовались разработчики других продуктов. Чтобы разработчики могли пользоваться вашей библиотекой, им нужно разъяснение, что ожидать от ваших функций и классов.
  • Вы пишете проект в сотрудничестве с кем-то. Ключ успешного сотрудничества в хорошей коммуникации, и документация здесь первостепенна.
  • Вы хотите написать поддерживаемый код. Вы когда-то перестанете работать в текущей фирме, и ваш продукт будут поддерживать другие люди. Чтобы ваш проект не умер одновременно с вашим уходом, следует позаботиться о нём заранее.
За образец документации функции или класса можно взять любую систему документации успешных коммерческих продуктов. Например:
  • Microsoft поддерживает документацию к средствам разработки приложений под Windows на высочайшем уровне. Для этого существует специальный сайт, MicroSoft Developer Network (MSDN). Пример документации функции: MessageBox.
  • Oracle подробно документирует классы стандартной библиотеки языка Java. Из-за специфики языка функции сгруппированы по классам, часто описание функций одно- и двух-строчное. Пример: GroupLayout.setAutoCreateGaps.
  • Сайт cplusplus.com описывает стандартную библиотеку языков C и C++. Пример: malloc.
  • Google поддерживает документацию к всё новым версиям системы Android
  • и т. п.
Общим местом в документации функций являются следующие стандартные разделы:
  • Выжимка. В одно-два предложения излагает суть, что функция или класс делает.
  • Синтаксис. Как вызывать функцию.
  • Подробное описание. В случае, если выжимки недостаточно, приводится более развёрнутое объяснение.
  • Параметры функции. Объясняется смысл каждого параметра, правила для него, ожидания и т. п.
  • Значение возврата функции. Если функция что-то вычисляет, это надо объяснить.
По желанию могут включаться дополнительные разделы:
  • Какие ошибки могут происходить при работе функции
  • Примеры кода, как использовать эту функцию или класс
  • Дискуссия и комментарии пользователей

Обход дерева

Обходом дерева называется цикл, пробегающий по всем вершинам дерева по одному разу. В каждой вершине дерева выполняется определённое действие.
Имеет смысл говорить о порядке обхода: в каком порядке вершины присутствуют в обходе. Наиболее естественным является порядок, который можно назвать порядком наследования царского престола: первыми после царя будет править старший сын и дети старшего сына, и внуки старшего сына. Лишь когда потомки старшего сына исчерпались, приходит очередь второго сына царя и его потомков. Затем третьего и т. п. Среди детей старшего сына царя действуют те же правила.
Чтобы реализовать обход дерева в C, нужно написать рекурсивную функцию. Она делает определённое действие для текущей вершины, а затем вызывает саму себя для каждого ребёнка текущей вершины.
void visit_tree (Tree *node) {
    сделать желаемое действие для node;
    для каждого ребёнка узла node сделать (где child — очередной ребёнок)
        visit_tree (child);
}

понедельник, 16 мая 2016 г.

Как работает алгоритм шифрования VMPC

VMPC (Variably Modified Permutation Composition, переменно-изменённое сочетание перестановок) — простой в реализации шифр, появившийся в 2004-м году. В самой базовой версии его можно запрограммировать за 10 строчек кода, приведённых в Википедии. Я встречал мнение, что он ненадёжен — его можно взломать не слишком большими трудозатратами.

Базовые термины

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

Внутреннее состояние

Внутреннее состояние шифра характеризуется тремя составляющими:
  • Ключ
  • Вектор иницилизации
  • Массив перестановок

Ключ

Под ключом понимается массив из байтов K длины k, причём .
Ключ — это секретный пароль, вводимый пользователем. Если злоумышленника получил доступ к ключу, он сможет расшифровать все сообщения, зашифрованные этим ключом.
Ключ надо определять один раз перед шифровкой или расшифровкой сообщения произвольной длины.

Вектор инициализации

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

Массив перестановок

К внутреннему состоянию шифра также относятся массив из 256 байтов P («массив перестановок») и целочисленный индекс s, . В процессе подготовки, шифрования и расшифрования значения P и s постоянно меняются — в отличие от ключа и вектора инициализации, которые фиксируются на всё время работы алгоритма.

Этапы алгоритма

Алгоритм разделён на два этапа:
  • Изначальная подготовка внутреннего состояния алгоритма
  • Шифрование либо расшифрование сообщения

Изначальная подготовка

  1. Пользователь вводит секретный ключ.
  2. Вектор инициализации. В случае шифрования — генерируется случайным образом. В случае расшифрования — берётся тот, который был использован при шифровании сообщения. 
  3. Стартовые значения: массив P заполнен нарастающими с 0 числами, от 0 до 255. Индекс s изначально равен 0.
  4. Для изначальной подготовки массива P и индекса s надо вызвать алгоритм VMPC-KSA (Key Scheduling Algorithm, алгоритм планирования ключа). Создатель VMPC предлагает вызывать KSA дважды в стандартном подходе, либо трижды — для повышенной надёжности. Алгоритм KSA работает либо на ключе, либо на векторе инициализации — лишь бы был массив байтов некоторой длины. KSA ничего не производит: единственная цель алгоритма — это изменить состояние P и s.
  5. Если мы решили вызывать KSA дважды, то в первый раз его надо вызвать с ключом, а второй раз с вектором инициализации.
  6. Если мы решили вызывать KSA трижды, то как и ранее, первый раз вызываем его с ключом, второй раз с вектором инициализации, а в третий раз снова с ключом.

Алгоритм KSA

Алгоритму KSA на вход подаются массив, обозначим его A, и длина массива a. На практике это будет либо ключ (K, k), либо вектор инициализации (V, v).
Алгоритм использует уже имеющиеся значения P и s и изменяет их в процессе работы.
for j = 0 to 767
    i = j mod 256
    s = P[(s + P[i] + K[j mod c]) mod 256]
    Поменять местами значения P[i] и P[s]

Шифрование / расшифрование

Алгоритмы шифрования и расшифрования полностью совпадают. Суть в том, что алгоритм шифрования производит поток псевдослучайно перемешанных байтов. Эти байты мы называем маской. Маска складывается побитовым исключающим или с исходным сообщением, получаем зашифрованное сообщение.
При расшифровке мы пользуемся абсолютно теми же ключом и вектором инициализации, что и при шифровании сообщения, поэтому алгоритм производит в точности такую же маску. Складывая зашифрованное сообщение с маской побитово, получим исходное сообщение.
Пусть длина сообщения равна l. Алгоритм производит маску — массив байтов L длины l.
j = 0
for j = 0 to l - 1
    i = j mod 256
    s = P[(s + P[i]) mod 256]
    L[j] = P[(P[P[s]] + 1) mod 256]
    Поменять местами значения P[i] и P[s]

суббота, 14 мая 2016 г.

Побитовые операции в C

Язык C содержит группу операций над целыми числами. Эти операции называются побитовыми, и они чаще встречаются в старых языках программирования. Молодые языки вытесняют побитовые операции на периферию как неинтуитивные.
Побитовые операции
ОперацияОбозначениеИспользование
И&a & b
Или|a | b
Исключающее или^a ^ b
Не~~a
Побитовое «не» — операция с одним аргументом (унарная). Три остальные работают над двумя аргументами — они бинарные.

Смысл

Суть всех побитовых операций схожа. Целые числа a и b представлены в компьютере последовательностями нулей и единиц фиксированной длины (по 32 бита для типа int). Процессор применяет требуемую логическую операцию к соответствующим битам обоих чисел, и кладёт результат в соответствующий бит. Получается новое целое число, являющееся какой-то комбинацией исходных чисел.

Логические операции
aba & ba | ba ^ b~a
000001
010111
100110
111100
Операция «И»: результат будет 1, если и первый, и второй аргумент равен 1.
Операция «Или»: результат будет 1, если первый или второй аргумент равен 1, или если оба.
Операция «Исключающее или»: результат будет 1, если из двух аргументов ровно один равен 1. Либо первый — либо второй, обе единицы не допускаются. Поэтому «исключающее или».
Операция «Не» смотрит только на a (естественно) и выворачивает его наизнанку: 0 переходит в 1, 1 переходит в 0.

Приоритет

Унарная операция ~ находится на том же приоритете, что и другие унарные операции, вроде ++, !, *.
Остальные три операции находятся каждая на своём приоритете, причём почему-то ниже операций равенства == и !=. Это довольно неудобно, и программистам часто приходится ставить группирующие скобки при использовании побитовых операций.

Зачем нужны побитовые операции?

Три операции, а именно: ~, & и |, активно используются при операциях с битовыми масками. Битовые маски — это числа, сформированные таким образом, чтобы у них была ровно одна 1 в двоичной записи, а все остальные нули. (По секрету, все такие числа — степени двойки.) Несколько битовых масок можно скомбинировать с помощью операции побитового «или», и получить одно целое число. Этот приём был широко распространён во время бума С/С++. Его использовали, чтобы передать по смыслу массив значений «да/нет», только компактно.
Операция ^ в битовых масках не участвовала, её судьба оказалась — работать самым примитивным шифрованием из возможных: если побитово сложить исходное сообщение и ключ, получится нечитаемая человеком каша.