Нужна помощь программистов с оптимизацией PAC-файла

Во-первых, спасибо за всё, что вы делаете!
Регулярно пользуюсь вашими сервисами.

Исходя из требований и объёма данных, предлагаю перейти на сравнение хэшей.
Каждый домен хэшируется каким-нибудь алгоритмом, например, SHA1.
(Мы не занимаемся проверкой целостности и криптографией, поэтому не имеет смысла брать более мощные алгоритмы. Можно вообще взять MD5 и даже CRC32, хоть он и предназначен для другого.)
Потом берём домен пользователя, хэшируем и сравниваем полученный хэш с таблицей хэшей исходных доменов.
Такой подход используется во многих продуктах, например, Safe Search для AdGuard.

Целые хэши занимают много места, поэтому будем брать их часть - 32 бита.
FIPS.180-4 предлагает брать префикс хэша, то есть просто обрезать остаток строки, получаем 4 байта на домен. Я провел тест на исходных 128к доменах, все 4-байтовые значения получились уникальные. Думаю, вполне хватит 4 байта.

Теперь по хранению всего этого безобразия.
Для ускорения поиска нужно разбить данные на части.
От хэша домена возьмём старший байт, который будет использоваться для индекса.
(Индекс лучше хранить в десятичном виде, занимает меньше места.)
Оставшиеся 3 байта можно закодировать алгоритмом BASE64, который для 3 байт выдаст строку 4 байта.
В эту строку добавим граничный символ “#”, чтобы было проще искать.
В результате получается следующая структура:

domains = {
...
123:"#ABCD#EFGH#...",
124:"#IJKL#MNOP#...",
...
};

Оценка размера структуры.
У нас получается 5 байт на домен и накладные 256 по 8 байт (3 на индекс, 2 на кавычки, 1 на двоеточие, 1 на запятую, 1 на новую строку).
Итого 128000 * 5 + 256 * 8 = 642048 байт.

Алгоритм поиска.
На входе домен пользователя host, считаем от него хэш SHA1(host), берём старшие 4 байта WXYZ.
Байты XYZ кодируем: host64 = BASE64(XYZ).
Добавляем в host64 граничный символ: host64 = “#” + host64.
Если domains[W] существует, тогда в строке domains[W] ищем подстроку host64 с помощью indexOf().
Если значение не равно -1, то хэш найден, домен пользователя в списке.

Оценка реализации.
Получается, что браузеру пользователя нужно посчитать хэш SHA1 для домена, BASE64 для 3 байт хэша и выполнить поиск подстроки в строке.
Это лучше, чем использовать регулярные выражения и алгоритмы сжатия для всей таблицы.
Если предположить, что 128к доменов распределены равномерно по таблице, тогда размер строки равен 128000 * 5 / 256 = 2500 байт.
Пусть даже строка будет в 10 раз длиннее, всё равно поиск будет выполняться достаточно быстро.
Реализация SHA1 занимает около 3 КБ, где-то видел варианты поменьше.
BASE64 в современных браузерах реализован в виде функций atob()/btoa(), для старых можно реализовать отдельно (1, 2).
Думаю, с учётом размера структуры и реализации этих алгоритмов получится вписаться в 700 КБ.

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

Оптимизация 1.
Можно отказаться от граничного символа и хранить все хэши подряд “ABCDEFGH…”, это уменьшит размер структуры до 500 КБ.
Но тогда придётся искать все совпадения подстроки и смотреть, чтобы подстрока приходилась на начало блока. Если индекс совпадает с началом блока (index % 4 == 0), тогда хэш найден, иначе искать дальше вплоть до конца строки.
Размер уменьшается, но скорость обработки увеличивается.

Оптимизация 2.
Вариант оптимизации 1 можно улучшить сортировкой хэшей и использованием бинарного поиска.

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

Вот насчёт этого сомневаюсь, кстати. Заблокированных доменов очень намного меньше, чем незаблокированных, поэтому не уверен, что решение с false positives тут подойдёт.

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

а исключения собирать как?

Видимо, автоматически никак, только по жалобам пользователей.

Если 32 бита хэша кажется мало, можно взять 40 бит.
Тогда придётся заменить BASE64 на ASCII85, который 4 байта кодирует в 5 байт.
В этом случае размер таблицы данных будет 128000 * 6 + 256 * 8 = 770048 байт.

А что, если исключать сайты и адреса, которые действительно являются scam, spam, срам… Которые в базах, например, virustotal содержатся.

Идея мало практичная, но тем не менее, если можно резолвить dns, можно складывать данные в поддомен, получать обратно 32 бита в виде айпишки с собственного ns сервера. Без понятия какие данные так гонять (сами адреса стрёмно, может хеши или что-то такое? по 32 хеша?), оно будет оседать в кешах dns, что и хорошо и плохо.
Плохо, что у большенства юзеров dns открытым текстом

Сейчас в списке примерно так и сделано: домены группируются по длине и добавляются в строку без разделителей, а затем разделяются (также по известной длине) в массив строк. Это требует инициализации, но не затратной.

Идея а-ля DNSBL рабочая, но лучше к ней не прибегать: это заметно повысит latency запросов, добавит еще одну точку отказа, да и браузеры иногда с DNS в PAC-файле плохо работают.

Проще уж тогда на VPN-серверах каждый день собирать статистику по доменам и генерировать PAC-лист только из тех, к которым регулярно обращаются. Что тоже дерьмовый вариант.

Кстати, а что на счет расширения броузера ?
Расширение имеет куда большие полномочия и не имеет столь жестких ограничений
Да, недоступно для мобильников, но там и так в основном пользуют VPN

Вот тут нагуглилось “supports inline PAC script”

Значит он его в себя подгружает, и там уже нет ограничений

В расширениях такого ограничения нет. PAC-файлы и так используются в «Обходе блокировок Рунета» и CensorTracker.

Тогда чего мы мучаемся. Возможности PAC исчерпаны. Загоняем всех на расширение и дело с концом.
Расширения есть на всех современных броузерах для PC, а ослика давно пора отправить на покой

Кстати, в условиях отсутствия публичного списка заблокированных сайтов, как планируется обновлять список доменов ? Основные значимые - руками вносить ?

PAC-файл работает не только в браузерах.

У меня есть пара идей по автоматическому обнаружению блокировок, напишу позже.

Но ведь ограничение на 1 мб только хромовское ? В других местах пусть будет большой pac

смотрите какая красота. в list.csv примерно 500 тыс записей

если сделать вот так

cat list.csv | awk 'BEGIN{FS=";"} NR >1 {print $1}' | sort | uniq -c | sort -rn | more

в топе значительная доля клаудфлера

Electron’овское ещё.

Как предлагаете использовать этот факт?

схлопнуть клаудфлеровские адреса в ноль и через isInNet() маршрутизировать их на прокси

Не анализируя домен? Проксироваться будет пол интернета в таком случае.

А IP-адреса в PAC-файле и так небольшую часть занимают, меньше 20 КБ.

awk: cmd. line:1: warning: regexp escape sequence _’ is not a known regexp operator`

а как это фиксить?

Так и должно быть, это не ошибка.

Я попробовал другую библиотеку но с тем же алгоритмом. Изначальный файл 2.2 мб, выходит 1.3 мб после сжатия. Попробовал сжать уже сжатое и выходит теже 1.3 при том что сжатый файл 1.1 мб.

Пока есть идея сжать просто по модулю. Всего 39 символов то есть в 128 бит ASCII можно вместить 3 символа в одном. И сейчас те 2 мегабайта как 700 килобайт представить + алгоритм разжатия довольно простой. Тьфу в 128 возможных чисел. Взять по модулю. И будет сжатие в 3 раза по сути. Нужно конечно проскипать символы закрывающих кавычек, экранироваия и перевода строки.