# set:version what is happening inside [set:version](https://git.altlinux.org/gears/r/rpm.git?a=blob;f=lib/set.c) Актуальный код с наработками расположен в [репозитории](https://github.com/kr0sh512/alt-rpm-set-version.git) ## Зачем set:version в alt-rpm позволяет сопоставлять Provides и Requires пакетов не обычным сравнением версий, а сравнением специальных set-строк зависимостей вида ```text libfoo.so.X = set: ``` `encoded-set` формируется на основе символов, необходимых/предоставляемых пакетом. Данный механизм позволяет гарантировать (с точностью до коллизий хэша, об этом будет далее) наличие всех требуемых символов в библиотеке. Это исключает ситуации, при которых ">= версий" ломается при удалении символа из библиотеки, а также ситуаций совпадения SONAME библиотек с разным набором символов. set-строки (являющиеся перекодированным списком символов) генерируются способом, который позволяет их сравнивать между собой на предмет включения одного множества символов в другой. ## Реализация `set.c` `set.c` предоставляет 5 "публичных" API для работы set:version ```c int rpmsetcmp(const char *set1, const char *set2); struct set *set_new(void); void set_add(struct set *set, const char *sym); const char *set_fini(struct set *set, int bpp); struct set *set_free(struct set *set); ``` ### `rpmsetcmp()` Основная функция, [сравнивающая строки](#сравнение-set-строк) и выдающая результат в зависимости от включения: - 1: set1 > set2 - 0: set1 == set2 - -1: set1 < set2 (aka set1 \subset set2) - -2: set1 != set2 - -3: set1 decoder error - -4: set2 decoder error на основе [данной](https://github.com/svpv/rpmss/blob/4256d86cc9ba1aa4ceb8c0f03f7d48675d9d27bb/set.h#L12) заметки, `set1` лучше делать как `Provides` для лучшей производительности. ### `set_new()` Создаёт пустой объект `struct set`. Внутри `struct set` — это временный контейнер для строк символов и их будущих hash-значений. Из релизации: > internally struct set is just a bag of strings and their hash values. ### `set_add()` Добавляет строковый символ в set. Использование: ```c set_add(s, "printf@@GLIBC_2.2.5"); set_add(s, "malloc@@GLIBC_2.2.5"); ``` ### `set_fini()` Финализирует множество и возвращает готовую set-version строку. В качестве параметров принимает `struct set *set` и `int bpp` При `bpp` < 10 или `bpp` > 32 функция возвращает `NULL`. Работа функции [описана далее](#работа-внутри-set_fini). ### `set_free()` Освобождает struct set и его внутренние строки. !!! NOTE В данный момент не освобождается сама структура `struct set`, память под которую в `set_new()` выделяется с `xmalloc` ## Работа внутри `set_fini()` Основной процесс преобразования массива строк в set-строку происходит следующим образом: ``` массив строк | (Jenkins OATT) v массив хэшей | (qsort) v отсортированный массив хэшей | (вычисление разницы между элементами) v массив delta | (Rice-Golomb преобразование) v битовый массив | (base62 преобразование) v set-строка ``` ### hash Для каждого элемента (строки) высчитывается 32-битный хэш и обрезается до `bpp` бит ```c set->sv[i].v = hash(set->sv[i].s) & mask; ``` В качестве хэш-функции используется `Jenkins OAAT`. Получившийся массив сортируется по хэш значениям по возрастанию. При коллизии, печатается `warning: hash collision` в `stderr` С помощью функции `uniqv()` в массиве остаются только уникальные значения, возвращаемое значение - размер массива после удаления повторов. Все дальнейшие преобразования происходят внутри функции `encode_set()`. Возвращаемое значение - длина итоговой set-строки, включая bpp и Mshift первыми двумя символами. !!! NOTE Возвращаемое значение `encode_set()` может использоваться как код ошибки при отрицательных значениях ### delta Внутри функции `encode_delta()` происходит преобразование массива отсортированных по возрастанию чисел в массив дельт между числами, т.е. Пример исходного массива: ```c unsigned *v = {1, 4, 16, 22}; ``` Пример результирующего массива: ```c unsigned *v = {1, 3, 12, 8}; ``` ### Rice-Golomb > `encode_golomb()` - Main golomb encoding routine: package integers into bits. Для сокращения длины итоговой строки применяется Rice-Golomb кодирование, которое позволяет с параметром `Mshift` записать число `n` как `n >> Mshift`, записанный последовательностью нулей и `n & 2^Mshift` записанный стандартным двоичным кодированием. Целая часть и остаток разделяется единичном битом. `Mshift` выбирается как `bpp - log2(c) - 1`, т.к. при равномерном распределении `c` хэшей по диапазону `2^bpp`, средняя `delta` будет равна `2^bpp / c` Пример: ``` Mshift = 8 n = 553 q = 553 >> 8 = 2 r = 553 & 255 = 42 bits = 00 1 00101010 ``` Функция `encode_golomb()` возвращает длину итоговой битовой последовательности. Сама последовательность находится в `char *bitv` (`char = [1,0]`) ### base62 > `encode_base62()` - pack bitv into base62 string Последним шагом является преобразование битовой последовательности в `base62` строку Алфавитом для кодирования является `0-9,a-z,A-Z`, но символ `Z` кодирует сразу 61, 62 и 63 следующим образом: 1. в битовую последовательность после кодированием `Z` добавляется 2 бита: - `00` для 61 - `01` для 62 - `10` для 63 2. битовая последовательность продолжает кодироваться стандартным образом Заметим, что данным образом невозможно получить поседовательность `ZZ`, т.к. символ `Z` требует двух старших битов выставленных в `11` Пример преобразования: ``` Mshift = 5 bits = 1 01111 00 1 00010 ``` Или же ``` bits = 101111 001000 10 ``` Биты читаются младшим битом вперёд. Первые 6 бит: ``` 101111 = 1*1 + 0*2 + 1*4 + 1*8 + 1*16 + 1*32 = 61 = Z ``` Тогда в последовательность добавляется два бита `00` сразу после битов, кодирующих Z. ``` bits = 101111 000010 0010 000010 = 16 = g 0010(00) = 4 = 4 ``` Итоговая строка в `base62`: ``` 10111100100010 = Zg4 ``` ## Сравнение set-строк При сравнении set-строк происходит обратный процесс преобразования до получений хэш-значений ``` set-строка | (обратное base62 преобразование) v битовый массив | (обратное Rice-Golomb преобразование) v массив delta | (вычисление изначальных значений) v массив хэшей ``` После массивы хэшей сравниваются между собой на наличие жлементов одного массива в другом. ## Комментарии ## additional [коды](https://altlinux.space/arseny/atsv-research) от Арсения для наглядности происходящего some funny [msg's](https://lists.pld-linux.org/mailman/pipermail/pld-devel-en/2013-November/012467.html) по коду неоднократно раскидано `bpp < 10 || bpp > 32` проверки запись `char = [1,0]` мне не нравится. про "отрицательные значения = код ошибки" встречается в многих местах, стоит вынести отдельно проверить кодом примеры, особенно base62